aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/include
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-23 12:53:58 +0100
committerGitHub Enterprise <[email protected]>2026-03-23 12:53:58 +0100
commite12feda1272922f6ad3567dd27509f0ec53d3a7b (patch)
tree8d238687c643a6790f9f307db3008a0e400caf42 /src/zencore/include
parentDocumentation updates (#882) (diff)
downloadzen-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.h86
-rw-r--r--src/zencore/include/zencore/logging/logger.h4
-rw-r--r--src/zencore/include/zencore/logging/logmsg.h12
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;
};