diff options
Diffstat (limited to 'src/zencore/logging/backlogsink.cpp')
| -rw-r--r-- | src/zencore/logging/backlogsink.cpp | 118 |
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 |