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