aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/logging/registry.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-09 10:50:47 +0100
committerGitHub Enterprise <[email protected]>2026-03-09 10:50:47 +0100
commit19a117889c2db6b817af9458c04c04f324162e75 (patch)
treefbbd0d01c5bf40be90cec88e1d02c6a3c529a2f5 /src/zencore/logging/registry.cpp
parentzenstore bug-fixes from static analysis pass (#815) (diff)
downloadzen-19a117889c2db6b817af9458c04c04f324162e75.tar.xz
zen-19a117889c2db6b817af9458c04c04f324162e75.zip
Eliminate spdlog dependency (#773)
Removes the vendored spdlog library (~12,000 lines) and replaces it with a purpose-built logging system in zencore (~1,800 lines). The new implementation provides the same functionality with fewer abstractions, no shared_ptr overhead, and full control over the logging pipeline. ### What changed **New logging core in zencore/logging/:** - LogMessage, Formatter, Sink, Logger, Registry - core abstractions matching spdlog's model but simplified - AnsiColorStdoutSink - ANSI color console output (replaces spdlog stdout_color_sink) - MsvcSink - OutputDebugString on Windows (replaces spdlog msvc_sink) - AsyncSink - async logging via BlockingQueue worker thread (replaces spdlog async_logger) - NullSink, MessageOnlyFormatter - utility types - Thread-safe timestamp caching in formatters using RwLock **Moved to zenutil/logging/:** - FullFormatter - full log formatting with timestamp, logger name, level, source location, multiline alignment - JsonFormatter - structured JSON log output - RotatingFileSink - rotating file sink with atomic size tracking **API changes:** - Log levels are now an enum (LogLevel) instead of int, eliminating the zen::logging::level namespace - LoggerRef no longer wraps shared_ptr - it holds a raw pointer with the registry owning lifetime - Logger error handler is wired through Registry and propagated to all loggers on registration - Logger::Log() now populates ThreadId on every message **Cleanup:** - Deleted thirdparty/spdlog/ entirely (110+ files) - Deleted full_test_formatter (was ~80% duplicate of FullFormatter) - Renamed snake_case classes to PascalCase (full_formatter -> FullFormatter, json_formatter -> JsonFormatter, sentry_sink -> SentrySink) - Removed spdlog from xmake dependency graph ### Build / test impact - zencore no longer depends on spdlog - zenutil and zenvfs xmake.lua updated to drop spdlog dep - zentelemetry xmake.lua updated to drop spdlog dep - All existing tests pass, no test changes required beyond formatter class renames
Diffstat (limited to 'src/zencore/logging/registry.cpp')
-rw-r--r--src/zencore/logging/registry.cpp330
1 files changed, 330 insertions, 0 deletions
diff --git a/src/zencore/logging/registry.cpp b/src/zencore/logging/registry.cpp
new file mode 100644
index 000000000..3ed1fb0df
--- /dev/null
+++ b/src/zencore/logging/registry.cpp
@@ -0,0 +1,330 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/logging/registry.h>
+
+#include <zencore/logging/ansicolorsink.h>
+#include <zencore/logging/messageonlyformatter.h>
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+
+namespace zen::logging {
+
+struct Registry::Impl
+{
+ Impl()
+ {
+ // Create default logger with a stdout color sink
+ SinkPtr DefaultSink(new AnsiColorStdoutSink());
+ m_DefaultLogger = Ref<Logger>(new Logger("", DefaultSink));
+ m_Loggers[""] = m_DefaultLogger;
+ }
+
+ ~Impl() { StopPeriodicFlush(); }
+
+ void Register(Ref<Logger> InLogger)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ if (m_ErrorHandler)
+ {
+ InLogger->SetErrorHandler(m_ErrorHandler);
+ }
+ m_Loggers[std::string(InLogger->Name())] = std::move(InLogger);
+ }
+
+ void Drop(const std::string& Name)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ m_Loggers.erase(Name);
+ }
+
+ Ref<Logger> Get(const std::string& Name)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ auto It = m_Loggers.find(Name);
+ if (It != m_Loggers.end())
+ {
+ return It->second;
+ }
+ return {};
+ }
+
+ void SetDefaultLogger(Ref<Logger> InLogger)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ if (InLogger)
+ {
+ m_Loggers[std::string(InLogger->Name())] = InLogger;
+ }
+ m_DefaultLogger = std::move(InLogger);
+ }
+
+ Logger* DefaultLoggerRaw() { return m_DefaultLogger.Get(); }
+
+ Ref<Logger> DefaultLogger()
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ return m_DefaultLogger;
+ }
+
+ void SetGlobalLevel(LogLevel Level)
+ {
+ m_GlobalLevel.store(Level, std::memory_order_relaxed);
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ for (auto& [Name, CurLogger] : m_Loggers)
+ {
+ CurLogger->SetLevel(Level);
+ }
+ }
+
+ LogLevel GetGlobalLevel() const { return m_GlobalLevel.load(std::memory_order_relaxed); }
+
+ void SetLevels(Registry::LogLevels Levels, LogLevel* DefaultLevel)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+
+ if (DefaultLevel)
+ {
+ m_GlobalLevel.store(*DefaultLevel, std::memory_order_relaxed);
+ for (auto& [Name, CurLogger] : m_Loggers)
+ {
+ CurLogger->SetLevel(*DefaultLevel);
+ }
+ }
+
+ for (auto& [LoggerName, Level] : Levels)
+ {
+ auto It = m_Loggers.find(LoggerName);
+ if (It != m_Loggers.end())
+ {
+ It->second->SetLevel(Level);
+ }
+ }
+ }
+
+ void FlushAll()
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ for (auto& [Name, CurLogger] : m_Loggers)
+ {
+ try
+ {
+ CurLogger->Flush();
+ }
+ catch (const std::exception&)
+ {
+ }
+ }
+ }
+
+ void FlushOn(LogLevel Level)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ m_FlushLevel = Level;
+ for (auto& [Name, CurLogger] : m_Loggers)
+ {
+ CurLogger->SetFlushLevel(Level);
+ }
+ }
+
+ void FlushEvery(std::chrono::seconds Interval)
+ {
+ StopPeriodicFlush();
+
+ m_PeriodicFlushRunning.store(true, std::memory_order_relaxed);
+
+ m_FlushThread = std::thread([this, Interval] {
+ while (m_PeriodicFlushRunning.load(std::memory_order_relaxed))
+ {
+ {
+ std::unique_lock<std::mutex> Lock(m_PeriodicFlushMutex);
+ m_PeriodicFlushCv.wait_for(Lock, Interval, [this] { return !m_PeriodicFlushRunning.load(std::memory_order_relaxed); });
+ }
+
+ if (m_PeriodicFlushRunning.load(std::memory_order_relaxed))
+ {
+ FlushAll();
+ }
+ }
+ });
+ }
+
+ void SetFormatter(std::unique_ptr<Formatter> InFormatter)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ for (auto& [Name, CurLogger] : m_Loggers)
+ {
+ CurLogger->SetFormatter(InFormatter->Clone());
+ }
+ }
+
+ void ApplyAll(void (*Func)(void*, Ref<Logger>), void* Context)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ for (auto& [Name, CurLogger] : m_Loggers)
+ {
+ Func(Context, CurLogger);
+ }
+ }
+
+ void SetErrorHandler(ErrorHandler* Handler)
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ m_ErrorHandler = Handler;
+ for (auto& [Name, CurLogger] : m_Loggers)
+ {
+ CurLogger->SetErrorHandler(Handler);
+ }
+ }
+
+ void Shutdown()
+ {
+ StopPeriodicFlush();
+ FlushAll();
+
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ m_Loggers.clear();
+ m_DefaultLogger = nullptr;
+ }
+
+private:
+ void StopPeriodicFlush()
+ {
+ if (m_FlushThread.joinable())
+ {
+ m_PeriodicFlushRunning.store(false, std::memory_order_relaxed);
+ {
+ std::lock_guard<std::mutex> Lock(m_PeriodicFlushMutex);
+ m_PeriodicFlushCv.notify_one();
+ }
+ m_FlushThread.join();
+ }
+ }
+
+ std::mutex m_Mutex;
+ std::unordered_map<std::string, Ref<Logger>> m_Loggers;
+ Ref<Logger> m_DefaultLogger;
+ std::atomic<LogLevel> m_GlobalLevel{Trace};
+ LogLevel m_FlushLevel{Off};
+ ErrorHandler* m_ErrorHandler = nullptr;
+
+ // Periodic flush
+ std::atomic<bool> m_PeriodicFlushRunning{false};
+ std::mutex m_PeriodicFlushMutex;
+ std::condition_variable m_PeriodicFlushCv;
+ std::thread m_FlushThread;
+};
+
+Registry&
+Registry::Instance()
+{
+ static Registry s_Instance;
+ return s_Instance;
+}
+
+Registry::Registry() : m_Impl(std::make_unique<Impl>())
+{
+}
+
+Registry::~Registry() = default;
+
+void
+Registry::Register(Ref<Logger> InLogger)
+{
+ m_Impl->Register(std::move(InLogger));
+}
+
+void
+Registry::Drop(const std::string& Name)
+{
+ m_Impl->Drop(Name);
+}
+
+Ref<Logger>
+Registry::Get(const std::string& Name)
+{
+ return m_Impl->Get(Name);
+}
+
+void
+Registry::SetDefaultLogger(Ref<Logger> InLogger)
+{
+ m_Impl->SetDefaultLogger(std::move(InLogger));
+}
+
+Logger*
+Registry::DefaultLoggerRaw()
+{
+ return m_Impl->DefaultLoggerRaw();
+}
+
+Ref<Logger>
+Registry::DefaultLogger()
+{
+ return m_Impl->DefaultLogger();
+}
+
+void
+Registry::SetGlobalLevel(LogLevel Level)
+{
+ m_Impl->SetGlobalLevel(Level);
+}
+
+LogLevel
+Registry::GetGlobalLevel() const
+{
+ return m_Impl->GetGlobalLevel();
+}
+
+void
+Registry::SetLevels(LogLevels Levels, LogLevel* DefaultLevel)
+{
+ m_Impl->SetLevels(Levels, DefaultLevel);
+}
+
+void
+Registry::FlushAll()
+{
+ m_Impl->FlushAll();
+}
+
+void
+Registry::FlushOn(LogLevel Level)
+{
+ m_Impl->FlushOn(Level);
+}
+
+void
+Registry::FlushEvery(std::chrono::seconds Interval)
+{
+ m_Impl->FlushEvery(Interval);
+}
+
+void
+Registry::SetFormatter(std::unique_ptr<Formatter> InFormatter)
+{
+ m_Impl->SetFormatter(std::move(InFormatter));
+}
+
+void
+Registry::ApplyAllImpl(void (*Func)(void*, Ref<Logger>), void* Context)
+{
+ m_Impl->ApplyAll(Func, Context);
+}
+
+void
+Registry::SetErrorHandler(ErrorHandler* Handler)
+{
+ m_Impl->SetErrorHandler(Handler);
+}
+
+void
+Registry::Shutdown()
+{
+ m_Impl->Shutdown();
+}
+
+} // namespace zen::logging