// Copyright Epic Games, Inc. All Rights Reserved. #include #include 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 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