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. Discards log messages if stdout is not available.
135 *
136 * @see log_set_global_logger
137 * @see log_logger_default
138 */
139void log_set_global_logger_default();
140
141/**
142 * @ingroup Log
143 *
144 * Notify global loggers of impending abnormal exit.
145 *
146 * This function is automatically called on normal exit. It notifies the global
147 * logger of the impending shutdown via `GlobalFinish`, the logger is supposed
148 * to flush its buffers and shut down.
149 *
150 * Don't call this except right before an abnormal exit.
151 */
152void log_global_logger_finish();
153
154/**
155 * @ingroup Log
156 *
157 * Get the logger active in the current scope. This might be the global default
158 * logger or some other logger set via `log_set_scope_logger`.
159 */
160ILogger *log_get_scope_logger();
161
162/**
163 * @ingroup Log
164 *
165 * Set the logger for the current thread. The logger isn't managed by the
166 * logging system, it still needs to be kept alive or freed by the caller.
167 *
168 * Consider using `CLogScope` if you only want to set the logger temporarily.
169 *
170 * @see CLogScope
171 */
172void log_set_scope_logger(ILogger *logger);
173
174/**
175 * @ingroup Log
176 *
177 * Logger for sending logs to the Android system log.
178 *
179 * Should only be called when targeting the Android platform.
180 */
181std::unique_ptr<ILogger> log_logger_android();
182
183/**
184 * @ingroup Log
185 *
186 * Logger combining a vector of other loggers.
187 */
188std::unique_ptr<ILogger> log_logger_collection(std::vector<std::shared_ptr<ILogger>> &&vpLoggers);
189
190/**
191 * @ingroup Log
192 *
193 * Sane default logger. This is logging to stdout on most platforms and to the
194 * system log on Android. Discards log messages if stdout is not available.
195 */
196std::unique_ptr<ILogger> log_logger_default();
197
198/**
199 * @ingroup Log
200 *
201 * Logger for writing logs to the given file.
202 *
203 * @param file File to write to, must be opened for writing.
204 */
205std::unique_ptr<ILogger> log_logger_file(IOHANDLE file);
206
207/**
208 * @ingroup Log
209 *
210 * Logger for writing logs to the standard output (stdout).
211 *
212 * @remark This function can return `nullptr` if the standard output is not available.
213 */
214std::unique_ptr<ILogger> log_logger_stdout();
215
216/**
217 * @ingroup Log
218 *
219 * Logger for sending logs to the debugger on Windows via `OutputDebugStringW`.
220 *
221 * Should only be called when targeting the Windows platform.
222 */
223std::unique_ptr<ILogger> log_logger_windows_debugger();
224
225/**
226 * @ingroup Log
227 *
228 * Logger which discards all logs.
229 */
230std::unique_ptr<ILogger> log_logger_noop();
231
232/**
233 * @ingroup Log
234 *
235 * Logger that collects log messages in memory until it is replaced by another
236 * logger.
237 *
238 * Useful when you want to set a global logger without all logging targets
239 * being configured.
240 *
241 * This logger forwards `SetFilter` calls, `SetFilter` calls before a logger is
242 * set have no effect.
243 */
244class CFutureLogger : public ILogger
245{
246private:
247 std::shared_ptr<ILogger> m_pLogger;
248 std::vector<CLogMessage> m_vPending;
249 CLock m_PendingLock;
250
251public:
252 /**
253 * Replace the `CFutureLogger` instance with the given logger. It'll
254 * receive all log messages sent to the `CFutureLogger` so far.
255 */
256 void Set(std::shared_ptr<ILogger> pLogger) REQUIRES(!m_PendingLock);
257 void Log(const CLogMessage *pMessage) override REQUIRES(!m_PendingLock);
258 void GlobalFinish() override;
259 void OnFilterChange() override;
260};
261
262/**
263 * @ingroup Log
264 *
265 * Logger that collects messages in memory. This is useful to collect the log
266 * messages for a particular operation and show them in a user interface when
267 * the operation failed. Use only temporarily with @link CLogScope @endlink
268 * or it will result in excessive memory usage.
269 *
270 * Messages are also forwarded to the parent logger if it's set, regardless
271 * of this logger's filter.
272 */
273class CMemoryLogger : public ILogger
274{
275 ILogger *m_pParentLogger = nullptr;
276 std::vector<CLogMessage> m_vMessages GUARDED_BY(m_MessagesMutex);
277 CLock m_MessagesMutex;
278
279public:
280 void SetParent(ILogger *pParentLogger) { m_pParentLogger = pParentLogger; }
281 void Log(const CLogMessage *pMessage) override REQUIRES(!m_MessagesMutex);
282 std::vector<CLogMessage> Lines() REQUIRES(!m_MessagesMutex);
283 std::string ConcatenatedLines() REQUIRES(!m_MessagesMutex);
284};
285
286/**
287 * @ingroup Log
288 *
289 * RAII guard for temporarily changing the logger via `log_set_scope_logger`.
290 *
291 * @see log_set_scope_logger
292 */
293class CLogScope
294{
295 ILogger *old_scope_logger;
296 ILogger *new_scope_logger;
297
298public:
299 CLogScope(ILogger *logger) :
300 old_scope_logger(log_get_scope_logger()),
301 new_scope_logger(logger)
302 {
303 log_set_scope_logger(logger: new_scope_logger);
304 }
305 ~CLogScope()
306 {
307 log_set_scope_logger(logger: old_scope_logger);
308 }
309 CLogScope(const CLogScope &) = delete;
310};
311#endif // BASE_LOGGER_H
312