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 that collects log messages in memory until it is replaced by another
217 * logger.
218 *
219 * Useful when you want to set a global logger without all logging targets
220 * being configured.
221 *
222 * This logger forwards `SetFilter` calls, `SetFilter` calls before a logger is
223 * set have no effect.
224 */
225class CFutureLogger : public ILogger
226{
227private:
228 std::shared_ptr<ILogger> m_pLogger;
229 std::vector<CLogMessage> m_vPending;
230 CLock m_PendingLock;
231
232public:
233 /**
234 * Replace the `CFutureLogger` instance with the given logger. It'll
235 * receive all log messages sent to the `CFutureLogger` so far.
236 */
237 void Set(std::shared_ptr<ILogger> pLogger) REQUIRES(!m_PendingLock);
238 void Log(const CLogMessage *pMessage) override REQUIRES(!m_PendingLock);
239 void GlobalFinish() override;
240 void OnFilterChange() override;
241};
242
243/**
244 * @ingroup Log
245 *
246 * RAII guard for temporarily changing the logger via `log_set_scope_logger`.
247 *
248 * @see log_set_scope_logger
249 */
250class CLogScope
251{
252 ILogger *old_scope_logger;
253 ILogger *new_scope_logger;
254
255public:
256 CLogScope(ILogger *logger) :
257 old_scope_logger(log_get_scope_logger()),
258 new_scope_logger(logger)
259 {
260 log_set_scope_logger(logger: new_scope_logger);
261 }
262 ~CLogScope()
263 {
264 //dbg_assert(log_get_scope_logger() == new_scope_logger, "loggers weren't properly scoped");
265 log_set_scope_logger(logger: old_scope_logger);
266 }
267};
268#endif // BASE_LOGGER_H
269