1#ifndef BASE_LOGGER_H
2#define BASE_LOGGER_H
3
4#include "lock.h"
5#include "log.h"
6
7#include <atomic>
8#include <memory>
9#include <vector>
10
11typedef void *IOHANDLE;
12
13/**
14 * @ingroup Log
15 *
16 * Metadata and actual content of a log message.
17 */
18class CLogMessage
19{
20public:
21 /**
22 * Severity
23 */
24 LEVEL m_Level;
25 bool m_HaveColor;
26 /**
27 * The requested color of the log message. Only useful if `m_HaveColor`
28 * is set.
29 */
30 LOG_COLOR m_Color;
31 char m_aTimestamp[80];
32 char m_aSystem[32];
33 /**
34 * The actual log message including the timestamp and the system.
35 */
36 char m_aLine[4096];
37 int m_TimestampLength;
38 int m_SystemLength;
39 /**
40 * Length of the log message including timestamp and the system.
41 */
42 int m_LineLength;
43 int m_LineMessageOffset;
44
45 /**
46 * The actual log message excluding timestamp and the system.
47 */
48 const char *Message() const
49 {
50 return m_aLine + m_LineMessageOffset;
51 }
52};
53
54class CLogFilter
55{
56public:
57 /**
58 * The highest `LEVEL` that is still logged, -1 corresponds to no
59 * printing at all.
60 */
61 std::atomic_int m_MaxLevel{LEVEL_INFO};
62
63 bool Filters(const CLogMessage *pMessage);
64};
65
66class ILogger
67{
68protected:
69 CLogFilter m_Filter;
70
71public:
72 virtual ~ILogger() {}
73
74 /**
75 * Set a new filter. It's up to the logger implementation to actually
76 * use the filter.
77 */
78 void SetFilter(const CLogFilter &Filter)
79 {
80 m_Filter.m_MaxLevel.store(i: Filter.m_MaxLevel.load(m: std::memory_order_relaxed), m: std::memory_order_relaxed);
81 OnFilterChange();
82 }
83
84 /**
85 * Send the specified message to the logging backend.
86 *
87 * @param pMessage Struct describing the log message.
88 */
89 virtual void Log(const CLogMessage *pMessage) = 0;
90 /**
91 * Flushes output buffers and shuts down.
92 * Global loggers cannot be destroyed because they might be accessed
93 * from multiple threads concurrently.
94 *
95 * This function is called on the global logger by
96 * `log_global_logger_finish` when the program is about to shut down
97 * and loggers are supposed to finish writing the log messages they
98 * have received so far.
99 *
100 * The destructor of this `ILogger` instance will not be called if this
101 * function is called.
102 *
103 * @see log_global_logger_finish
104 */
105 virtual void GlobalFinish() {}
106 /**
107 * Notifies the logger of a changed `m_Filter`.
108 */
109 virtual void OnFilterChange() {}
110};
111
112/**
113 * @ingroup Log
114 *
115 * Registers a logger instance as the default logger for all current and future
116 * threads. It will only be used if no thread-local logger is set via
117 * `log_set_scope_logger`.
118 *
119 * This function can only be called once. The passed logger instance will never
120 * be freed.
121 *
122 * @param logger The global logger default.
123 */
124void log_set_global_logger(ILogger *logger);
125
126/**
127 * @ingroup Log
128 *
129 * Registers a sane default as the default logger for all current and future
130 * threads.
131 *
132 * This is logging to stdout on most platforms and to the system log on
133 * Android.
134 *
135 * @see log_set_global_logger
136 */
137void log_set_global_logger_default();
138
139/**
140 * @ingroup Log
141 *
142 * Notify global loggers of impending abnormal exit.
143 *
144 * This function is automatically called on normal exit. It notifies the global
145 * logger of the impending shutdown via `GlobalFinish`, the logger is supposed
146 * to flush its buffers and shut down.
147 *
148 * Don't call this except right before an abnormal exit.
149 */
150void log_global_logger_finish();
151
152/**
153 * @ingroup Log
154 *
155 * Get the logger active in the current scope. This might be the global default
156 * logger or some other logger set via `log_set_scope_logger`.
157 */
158ILogger *log_get_scope_logger();
159
160/**
161 * @ingroup Log
162 *
163 * Set the logger for the current thread. The logger isn't managed by the
164 * logging system, it still needs to be kept alive or freed by the caller.
165 *
166 * Consider using `CLogScope` if you only want to set the logger temporarily.
167 *
168 * @see CLogScope
169 */
170void log_set_scope_logger(ILogger *logger);
171
172/**
173 * @ingroup Log
174 *
175 * Logger for sending logs to the Android system log.
176 *
177 * Should only be called when targeting the Android platform.
178 */
179std::unique_ptr<ILogger> log_logger_android();
180
181/**
182 * @ingroup Log
183 *
184 * Logger combining a vector of other loggers.
185 */
186std::unique_ptr<ILogger> log_logger_collection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers);
187
188/**
189 * @ingroup Log
190 *
191 * Logger for writing logs to the given file.
192 *
193 * @param file File to write to, must be opened for writing.
194 */
195std::unique_ptr<ILogger> log_logger_file(IOHANDLE file);
196
197/**
198 * @ingroup Log
199 *
200 * Logger for writing logs to the standard output (stdout).
201 */
202std::unique_ptr<ILogger> log_logger_stdout();
203
204/**
205 * @ingroup Log
206 *
207 * Logger for sending logs to the debugger on Windows via `OutputDebugStringW`.
208 *
209 * Should only be called when targeting the Windows platform.
210 */
211std::unique_ptr<ILogger> log_logger_windows_debugger();
212
213/**
214 * @ingroup Log
215 *
216 * Logger which discards all logs.
217 */
218std::unique_ptr<ILogger> log_logger_noop();
219
220/**
221 * @ingroup Log
222 *
223 * Logger that collects log messages in memory until it is replaced by another
224 * logger.
225 *
226 * Useful when you want to set a global logger without all logging targets
227 * being configured.
228 *
229 * This logger forwards `SetFilter` calls, `SetFilter` calls before a logger is
230 * set have no effect.
231 */
232class CFutureLogger : public ILogger
233{
234private:
235 std::shared_ptr<ILogger> m_pLogger;
236 std::vector<CLogMessage> m_vPending;
237 CLock m_PendingLock;
238
239public:
240 /**
241 * Replace the `CFutureLogger` instance with the given logger. It'll
242 * receive all log messages sent to the `CFutureLogger` so far.
243 */
244 void Set(std::shared_ptr<ILogger> pLogger) REQUIRES(!m_PendingLock);
245 void Log(const CLogMessage *pMessage) override REQUIRES(!m_PendingLock);
246 void GlobalFinish() override;
247 void OnFilterChange() override;
248};
249
250/**
251 * @ingroup Log
252 *
253 * RAII guard for temporarily changing the logger via `log_set_scope_logger`.
254 *
255 * @see log_set_scope_logger
256 */
257class CLogScope
258{
259 ILogger *old_scope_logger;
260 ILogger *new_scope_logger;
261
262public:
263 CLogScope(ILogger *logger) :
264 old_scope_logger(log_get_scope_logger()),
265 new_scope_logger(logger)
266 {
267 log_set_scope_logger(logger: new_scope_logger);
268 }
269 ~CLogScope()
270 {
271 //dbg_assert(log_get_scope_logger() == new_scope_logger, "loggers weren't properly scoped");
272 log_set_scope_logger(logger: old_scope_logger);
273 }
274};
275#endif // BASE_LOGGER_H
276