diff options
| author | Stefan Boberg <[email protected]> | 2026-03-23 12:53:58 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-23 12:53:58 +0100 |
| commit | e12feda1272922f6ad3567dd27509f0ec53d3a7b (patch) | |
| tree | 8d238687c643a6790f9f307db3008a0e400caf42 /src/zencore/include | |
| parent | Documentation updates (#882) (diff) | |
| download | zen-e12feda1272922f6ad3567dd27509f0ec53d3a7b.tar.xz zen-e12feda1272922f6ad3567dd27509f0ec53d3a7b.zip | |
Logger simplification (#883)
- **`Logger` now holds a single `SinkPtr`** instead of a `std::vector<SinkPtr>`. The `SetSinks`/`AddSink` API is replaced with a single `SetSink`. This removes complexity from `Logger` itself and makes `Clone()` cheaper (no vector copy).
- **New `BroadcastSink`** (`zencore/logging/broadcastsink.h`) acts as a thread-safe, shared indirection point that fans out to a dynamic list of child sinks. Adding or removing a child sink via `AddSink`/`RemoveSink` is immediately visible to every `Logger` that holds a reference to it — including cloned loggers — without requiring each logger to be updated individually.
- **`GetDefaultBroadcastSink()`** (exposed from `zenutil/logging.h`) gives server-layer code access to the shared broadcast sink so it can register optional sinks (OTel, TCP log stream) after logging is initialized, without going through `Default()->AddSink()`.
### Motivation
Previously, dynamically adding sinks post-initialization mutated the default logger's internal sink vector directly. This was fragile: cloned loggers (created before `AddSink` was called) would not pick up the new sinks. `BroadcastSink` fixes this by making the sink list a shared, mutable object that all loggers sharing the same broadcast instance observe uniformly.
Diffstat (limited to 'src/zencore/include')
| -rw-r--r-- | src/zencore/include/zencore/logging/broadcastsink.h | 86 | ||||
| -rw-r--r-- | src/zencore/include/zencore/logging/logger.h | 4 | ||||
| -rw-r--r-- | src/zencore/include/zencore/logging/logmsg.h | 12 |
3 files changed, 89 insertions, 13 deletions
diff --git a/src/zencore/include/zencore/logging/broadcastsink.h b/src/zencore/include/zencore/logging/broadcastsink.h new file mode 100644 index 000000000..c2709d87c --- /dev/null +++ b/src/zencore/include/zencore/logging/broadcastsink.h @@ -0,0 +1,86 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/logging/sink.h> +#include <zencore/thread.h> + +#include <algorithm> +#include <vector> + +namespace zen::logging { + +/// A sink that broadcasts log messages to a dynamic list of child sinks. +/// +/// BroadcastSink acts as a shared indirection point: multiple Loggers can +/// reference the same BroadcastSink instance, and adding or removing a child +/// sink is immediately visible to all of them. This is the recommended way +/// to manage "default" sinks that should be active on most loggers. +/// +/// Each child sink owns its own Formatter — BroadcastSink::SetFormatter() is +/// intentionally a no-op so that per-sink formatting is not accidentally +/// overwritten by registry-wide formatter changes. +class BroadcastSink : public Sink +{ +public: + BroadcastSink() = default; + explicit BroadcastSink(std::vector<SinkPtr> InSinks) : m_Sinks(std::move(InSinks)) {} + + void Log(const LogMessage& Msg) override + { + RwLock::SharedLockScope Lock(m_Lock); + for (auto& Child : m_Sinks) + { + if (Child->ShouldLog(Msg.GetLevel())) + { + try + { + Child->Log(Msg); + } + catch (const std::exception&) + { + // logging here would be sketchy since we could cause a recursive loop + // if we wanted to surface this error, we would need to have some sort + // of internal error handling mechanism that doesn't rely on sinks + } + } + } + } + + void Flush() override + { + RwLock::SharedLockScope Lock(m_Lock); + for (auto& Child : m_Sinks) + { + try + { + Child->Flush(); + } + catch (const std::exception&) + { + // must not log here (see above) + } + } + } + + /// No-op — child sinks manage their own formatters. + void SetFormatter(std::unique_ptr<Formatter> /*InFormatter*/) override {} + + void AddSink(SinkPtr InSink) + { + RwLock::ExclusiveLockScope Lock(m_Lock); + m_Sinks.push_back(std::move(InSink)); + } + + void RemoveSink(const SinkPtr& InSink) + { + RwLock::ExclusiveLockScope Lock(m_Lock); + m_Sinks.erase(std::remove(m_Sinks.begin(), m_Sinks.end(), InSink), m_Sinks.end()); + } + +private: + RwLock m_Lock; + std::vector<SinkPtr> m_Sinks; +}; + +} // namespace zen::logging diff --git a/src/zencore/include/zencore/logging/logger.h b/src/zencore/include/zencore/logging/logger.h index c94bc58fa..1706a4455 100644 --- a/src/zencore/include/zencore/logging/logger.h +++ b/src/zencore/include/zencore/logging/logger.h @@ -21,7 +21,6 @@ class Logger final : public LoggerBase { public: Logger(std::string_view InName, SinkPtr InSink); - Logger(std::string_view InName, std::span<const SinkPtr> InSinks); ~Logger(); Logger(const Logger&) = delete; @@ -31,8 +30,7 @@ public: std::string_view Name() const; - void SetSinks(std::vector<SinkPtr> InSinks); - void AddSink(SinkPtr InSink); + void SetSink(SinkPtr InSink); void SetFormatter(std::unique_ptr<Formatter> InFormatter); diff --git a/src/zencore/include/zencore/logging/logmsg.h b/src/zencore/include/zencore/logging/logmsg.h index a1acb503b..4a777c71e 100644 --- a/src/zencore/include/zencore/logging/logmsg.h +++ b/src/zencore/include/zencore/logging/logmsg.h @@ -41,22 +41,14 @@ struct LogMessage void SetSource(const SourceLocation& InSource) { m_Source = InSource; } private: - static constexpr LogPoint s_DefaultPoints[LogLevelCount] = { - {{}, Trace, {}}, - {{}, Debug, {}}, - {{}, Info, {}}, - {{}, Warn, {}}, - {{}, Err, {}}, - {{}, Critical, {}}, - {{}, Off, {}}, - }; + static constexpr LogPoint s_DefaultPoint{{}, Off, {}}; std::string_view m_LoggerName; LogLevel m_Level = Off; std::chrono::system_clock::time_point m_Time; SourceLocation m_Source; std::string_view m_Payload; - const LogPoint* m_Point = &s_DefaultPoints[Off]; + const LogPoint* m_Point = &s_DefaultPoint; int m_ThreadId = 0; }; |