aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/logging/backlogsink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zencore/logging/backlogsink.cpp')
-rw-r--r--src/zencore/logging/backlogsink.cpp118
1 files changed, 118 insertions, 0 deletions
diff --git a/src/zencore/logging/backlogsink.cpp b/src/zencore/logging/backlogsink.cpp
new file mode 100644
index 000000000..f7c02c2e4
--- /dev/null
+++ b/src/zencore/logging/backlogsink.cpp
@@ -0,0 +1,118 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/logging/backlogsink.h>
+
+#include <zencore/logging/logmsg.h>
+
+namespace zen::logging {
+
+BacklogSink::BacklogSink(size_t MaxEntries) : m_MaxEntries(MaxEntries)
+{
+ // Reserve up front so push_back never reallocates. The reserve itself
+ // takes one chunk from the arena; subsequent string duplications carve
+ // from the same arena, so the entire backlog footprint is one
+ // contiguous allocation domain released wholesale when this object is
+ // destroyed (see DisableLogBacklog: it drops the last Ref).
+ m_Entries.reserve(MaxEntries);
+}
+
+BacklogSink::~BacklogSink() = default;
+
+void
+BacklogSink::Log(const LogMessage& Msg)
+{
+ // Cheap acquire-load gate: once Disable() has been called we want
+ // further log calls to do as little as possible. This is the common
+ // case after the bootstrap window closes — every log line in the
+ // process flows through here for the rest of the run.
+ if (!m_Enabled.load(std::memory_order_acquire))
+ {
+ return;
+ }
+
+ RwLock::ExclusiveLockScope Lock(m_Lock);
+
+ // Re-check under the lock: a Disable() racing with Log() may have
+ // flipped the flag between our acquire and the lock acquisition.
+ if (!m_Enabled.load(std::memory_order_relaxed))
+ {
+ return;
+ }
+
+ if (m_Entries.size() >= m_MaxEntries)
+ {
+ m_DroppedCount.fetch_add(1, std::memory_order_relaxed);
+ return;
+ }
+
+ Entry& Captured = m_Entries.emplace_back();
+ Captured.Point = &Msg.GetLogPoint();
+ Captured.LoggerName = m_Arena.DuplicateString(Msg.GetLoggerName());
+ Captured.Payload = m_Arena.DuplicateString(Msg.GetPayload());
+ Captured.Time = Msg.GetTime();
+ Captured.ThreadId = Msg.GetThreadId();
+}
+
+void
+BacklogSink::Flush()
+{
+ // Nothing to do — we're an in-memory buffer, not an output device.
+}
+
+void
+BacklogSink::SetFormatter(std::unique_ptr<Formatter> InFormatter)
+{
+ // We don't format anything — entries are replayed verbatim into the
+ // target sink, which applies its own formatter. Accept and discard
+ // for interface compatibility.
+ (void)InFormatter;
+}
+
+void
+BacklogSink::Replay(Sink& Target, size_t MaxEntries)
+{
+ RwLock::SharedLockScope Lock(m_Lock);
+ const size_t Limit = std::min(MaxEntries, m_Entries.size());
+ for (size_t Index = 0; Index < Limit; ++Index)
+ {
+ const Entry& Captured = m_Entries[Index];
+ if (!Captured.Point)
+ {
+ continue;
+ }
+ const std::string_view LoggerName = Captured.LoggerName ? std::string_view(Captured.LoggerName) : std::string_view{};
+ const std::string_view Payload = Captured.Payload ? std::string_view(Captured.Payload) : std::string_view{};
+ LogMessage Msg(*Captured.Point, LoggerName, Payload);
+ Msg.SetTime(Captured.Time);
+ Msg.SetThreadId(Captured.ThreadId);
+ if (Target.ShouldLog(Msg.GetLevel()))
+ {
+ try
+ {
+ Target.Log(Msg);
+ }
+ catch (const std::exception&)
+ {
+ // Match the swallow-on-replay semantics of AsyncSink's
+ // dispatcher so a misbehaving target doesn't poison the
+ // rest of the replay.
+ }
+ }
+ }
+}
+
+void
+BacklogSink::Disable()
+{
+ RwLock::ExclusiveLockScope Lock(m_Lock);
+ m_Enabled.store(false, std::memory_order_release);
+}
+
+size_t
+BacklogSink::Size() const
+{
+ RwLock::SharedLockScope Lock(m_Lock);
+ return m_Entries.size();
+}
+
+} // namespace zen::logging