aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/logging/backlogsink.cpp
blob: f7c02c2e4ac92ac1a98cad257650195f83c21d8c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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