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 | |
11 | typedef void *IOHANDLE; |
12 | |
13 | /** |
14 | * @ingroup Log |
15 | * |
16 | * Metadata and actual content of a log message. |
17 | */ |
18 | class CLogMessage |
19 | { |
20 | public: |
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 | |
54 | class CLogFilter |
55 | { |
56 | public: |
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 | |
66 | class ILogger |
67 | { |
68 | protected: |
69 | CLogFilter m_Filter; |
70 | |
71 | public: |
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 | */ |
124 | void 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 | */ |
137 | void 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 | */ |
150 | void 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 | */ |
158 | ILogger *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 | */ |
170 | void 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 | */ |
179 | std::unique_ptr<ILogger> log_logger_android(); |
180 | |
181 | /** |
182 | * @ingroup Log |
183 | * |
184 | * Logger combining a vector of other loggers. |
185 | */ |
186 | std::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 | */ |
195 | std::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 | */ |
202 | std::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 | */ |
211 | std::unique_ptr<ILogger> log_logger_windows_debugger(); |
212 | |
213 | /** |
214 | * @ingroup Log |
215 | * |
216 | * Logger which discards all logs. |
217 | */ |
218 | std::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 | */ |
232 | class CFutureLogger : public ILogger |
233 | { |
234 | private: |
235 | std::shared_ptr<ILogger> m_pLogger; |
236 | std::vector<CLogMessage> m_vPending; |
237 | CLock m_PendingLock; |
238 | |
239 | public: |
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 | */ |
257 | class CLogScope |
258 | { |
259 | ILogger *old_scope_logger; |
260 | ILogger *new_scope_logger; |
261 | |
262 | public: |
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 | |