Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
logging.h
1#pragma once
2#include "gaia/config/config_core.h"
3
4#include <cstdarg>
5#include <cstdint>
6#include <cstdio>
7
8#include "gaia/cnt/darray_ext.h"
9
10// Controls how logs can grow in bytes before flush is triggered
11#ifndef GAIA_LOG_BUFFER_SIZE
12 #define GAIA_LOG_BUFFER_SIZE 32 * 1024
13#endif
14// Controls how many log entries are possible before flush
15#ifndef GAIA_LOG_BUFFER_ENTRIES
16 #define GAIA_LOG_BUFFER_ENTRIES 2048
17#endif
18
19namespace gaia {
20 namespace util {
22 using LogLevelType = uint8_t;
23
25 enum class LogLevel : LogLevelType {
26 Debug = 0x1,
27 Info = 0x2,
28 Warning = 0x4,
29 Error = 0x8
30 };
32 inline LogLevelType g_logLevelMask = (LogLevelType)LogLevel::Debug | (LogLevelType)LogLevel::Info |
33 (LogLevelType)LogLevel::Warning | (LogLevelType)LogLevel::Error;
34
38 inline void log_enable(LogLevel level, bool value) {
39 if (value)
40 gaia::util::g_logLevelMask |= ((LogLevelType)level);
41 else
42 gaia::util::g_logLevelMask &= ~((LogLevelType)level);
43 }
44
48 inline bool is_logging_enabled(LogLevel level) {
49 return ((LogLevelType)level & g_logLevelMask) != 0;
50 }
51
53 using LogLineFunc = void (*)(LogLevel, const char*);
55 using LogFunc = void (*)(LogLevel, const char*, va_list);
57 using LogFlushFunc = void (*)();
58
60 namespace detail {
61 inline constexpr uint32_t LOG_BUFFER_SIZE = GAIA_LOG_BUFFER_SIZE;
62 inline constexpr uint32_t LOG_RECORD_LIMIT = GAIA_LOG_BUFFER_ENTRIES;
63
64 inline FILE* get_log_out(LogLevel level) {
65 const auto mask = (LogLevelType)level & ((LogLevelType)LogLevel::Error | (LogLevelType)LogLevel::Warning);
66 // If a warning or error level is set we will use stderr for output.
67 return mask != 0 ? stderr : stdout;
68 }
69
70 // LCOV_EXCL_START
71
73 inline void log_line(LogLevel level, const char* msg) {
74 FILE* out = get_log_out(level);
75
76 static constexpr const char* colors[] = {
77 "\033[1;32mD: ", // Debug
78 "\033[0mI: ", // Info
79 "\033[1;33mW: ", // Warning
80 "\033[1;31mE: " // Error
81 };
82 // LogLevel is a bitmask. Calculate what bit is and use it as an index.
83 const auto lvl = (uint32_t)level;
84 const auto idx = GAIA_CLZ(lvl);
85 fprintf(out, "%s%s\033[0m\n", colors[idx], msg);
86 }
87 inline LogLineFunc g_log_line_func = log_line;
88
89 // LCOV_EXCL_STOP
90
91 struct LogBuffer {
92 struct LogRecord {
93 uint32_t offset : 29;
94 uint32_t level : 3; // 3 bits for LogLevel mask
95 };
96
97 char m_buffer[LOG_BUFFER_SIZE];
98 LogRecord m_recs[LOG_RECORD_LIMIT];
99 uint32_t m_buffer_pos = 0;
100 uint32_t m_recs_pos = 0;
101
108 void log(LogLevel level, uint32_t len, const char* msg) {
109 FILE* out = get_log_out(level);
110 const bool is_assert = out == stderr;
111
112 // Big message? Write directly
113 if (is_assert || len >= detail::LOG_BUFFER_SIZE) {
114 // Flush existing buffer first
115 flush();
116
117 // Print message directly (bypass cache)
118 g_log_line_func(level, msg);
119 fflush(out);
120 return;
121 }
122
123 // Normal caching path. If the message doesn't fit, or if there are too many records, flush.
124 if (m_buffer_pos + len > detail::LOG_BUFFER_SIZE || m_recs_pos >= detail::LOG_RECORD_LIMIT)
125 flush();
126
127 // Append message to cache
128 auto& rec = m_recs[m_recs_pos];
129 rec.offset = m_buffer_pos;
130 rec.level = (LogLevelType)level;
131 memcpy(m_buffer + m_buffer_pos, msg, len);
132
133 m_buffer_pos += len;
134 ++m_recs_pos;
135 }
136
137 void flush() {
138 if (m_recs_pos == 0)
139 return;
140
141 for (size_t i = 0; i < m_recs_pos; ++i) {
142 const auto& rec = m_recs[i];
143 g_log_line_func((LogLevel)rec.level, &m_buffer[rec.offset]);
144 }
145
146 m_recs_pos = 0;
147 m_buffer_pos = 0;
148 fflush(stdout);
149 }
150
151 LogBuffer() {
152 // Disable flushing on the new lines. We will control flushing fully.
153 // To avoid issues with Windows’ UCRT we set some reasonable non-zero value.
154 setvbuf(stdout, nullptr, _IOFBF, 4096);
155 setvbuf(stderr, nullptr, _IOFBF, 4096);
156 }
157 ~LogBuffer() {
158 // Flush before the object disappears
159 flush();
160 }
161
162 LogBuffer(const LogBuffer&) = delete;
163 LogBuffer(LogBuffer&&) = delete;
164 LogBuffer& operator=(const LogBuffer&) = delete;
165 LogBuffer& operator=(LogBuffer&&) = delete;
166 };
167
168 inline LogBuffer* g_log() {
169 static LogBuffer* s_log = nullptr;
170 if (s_log == nullptr) {
171 s_log = new LogBuffer();
172 // Register automatic cleanup
173 static struct LogAtExit {
174 LogAtExit() = default;
175 ~LogAtExit() {
176 if (s_log != nullptr) {
177 s_log->flush();
178 delete s_log;
179 s_log = nullptr;
180 }
181 }
182
183 LogAtExit(const LogAtExit&) = delete;
184 LogAtExit(LogAtExit&&) = delete;
185 LogAtExit& operator=(const LogAtExit&) = delete;
186 LogAtExit& operator=(LogAtExit&&) = delete;
187 } s_logDeleter;
188 }
189 return s_log;
190 }
191
193 inline void log_cached(LogLevel level, const char* fmt, va_list args) {
194 va_list args_copy{};
195
196 GAIA_CLANG_WARNING_PUSH()
197 GAIA_GCC_WARNING_PUSH()
198 GAIA_CLANG_WARNING_DISABLE("-Wformat-nonliteral")
199 GAIA_GCC_WARNING_DISABLE("-Wformat-nonliteral")
200 // Early exit if there is nothing to write
201 va_copy(args_copy, args);
202 int l = vsnprintf(nullptr, 0, fmt, args_copy);
203 va_end(args_copy);
204 if (l <= 0)
205 return;
206
207 const auto len = (uint32_t)l;
208 cnt::darray_ext<char, 1024> msg(len + 1);
209
210 va_copy(args_copy, args);
211 vsnprintf(msg.data(), msg.size(), fmt, args_copy);
212 va_end(args_copy);
213 GAIA_GCC_WARNING_POP()
214 GAIA_CLANG_WARNING_POP()
215
216 // Always null-terminate logs
217 msg[len] = 0;
218
219 // Log a message.
220 // We implement a buffering strategy. Warnings and errors flush immediately.
221 // Otherwise, we flush once the buffer is filled or on-demand manually.
222 g_log()->log(level, msg.size(), msg.data());
223 }
224
226 inline void log_flush_cached() {
227 g_log()->flush();
228 }
229
230 // LCOV_EXCL_START
231
233 inline void log_default(LogLevel level, const char* fmt, va_list args) {
234 va_list args_copy{};
235
236 GAIA_CLANG_WARNING_PUSH()
237 GAIA_GCC_WARNING_PUSH()
238 GAIA_CLANG_WARNING_DISABLE("-Wformat-nonliteral")
239 GAIA_GCC_WARNING_DISABLE("-Wformat-nonliteral")
240 // Early exit if there is nothing to write
241 va_copy(args_copy, args);
242 int l = vsnprintf(nullptr, 0, fmt, args_copy);
243 va_end(args_copy);
244 if (l <= 0)
245 return;
246
247 const auto len = (uint32_t)l;
248 cnt::darray_ext<char, 1024> msg(len + 1);
249
250 va_copy(args_copy, args);
251 vsnprintf(msg.data(), msg.size(), fmt, args_copy);
252 va_end(args_copy);
253 GAIA_GCC_WARNING_POP()
254 GAIA_CLANG_WARNING_POP()
255
256 // Always null-terminate logs
257 msg[len] = 0;
258
259 g_log_line_func(level, msg.data());
260 }
261
262 // LCOV_EXCL_STOP
263
265 inline void log_flush_default() {}
266
267 inline LogFunc g_log_func = log_default;
268 inline LogFlushFunc g_log_flush_func = log_flush_default;
269 } // namespace detail
271
275 inline void set_log_func(LogFunc func) {
276 detail::g_log_func = func != nullptr ? func : detail::log_default;
277 }
278
281 inline void set_log_line_func(LogLineFunc func) {
282 detail::g_log_line_func = func != nullptr ? func : detail::log_line;
283 }
284
287 inline void set_log_flush_func(LogFlushFunc func) {
288 detail::g_log_flush_func = func != nullptr ? func : detail::log_flush_default;
289 }
290
294 inline void log(LogLevel level, const char* fmt, ...) {
295 if (!is_logging_enabled(level))
296 return;
297
298 va_list args;
299 va_start(args, fmt);
300 detail::g_log_func(level, fmt, args);
301 va_end(args);
302 }
303
305 inline void log_flush() {
306 detail::g_log_flush_func();
307 }
308 } // namespace util
309} // namespace gaia
310
311// LCOV_EXCL_START
312extern "C" {
313
314typedef void (*gaia_log_line_func_t)(gaia::util::LogLevelType level, const char* msg);
315inline void gaia_set_log_func(gaia_log_line_func_t func) {
316 gaia::util::set_log_line_func((gaia::util::LogLineFunc)func);
317}
318
319inline void gaia_log(uint8_t level, const char* msg) {
320 gaia::util::log((gaia::util::LogLevel)level, "%s", msg);
321}
322
323typedef void (*gaia_log_flush_func_t)();
324inline void gaia_set_flush_func(gaia_log_flush_func_t func) {
325 gaia::util::set_log_flush_func((gaia::util::LogFlushFunc)func);
326}
327
328inline void gaia_flush_logs() {
329 gaia::util::log_flush();
330}
331
332inline void gaia_log_enable(gaia::util::LogLevelType level, bool value) {
333 gaia::util::log_enable((gaia::util::LogLevel)level, value);
334}
335
336inline bool gaia_is_logging_enabled(gaia::util::LogLevelType level) {
337 return gaia::util::is_logging_enabled((gaia::util::LogLevel)level);
338}
339}
340// LCOV_EXCL_STOP
341
342#define GAIA_LOG_D(...) gaia::util::log(gaia::util::LogLevel::Debug, __VA_ARGS__)
343#define GAIA_LOG_N(...) gaia::util::log(gaia::util::LogLevel::Info, __VA_ARGS__)
344#define GAIA_LOG_W(...) gaia::util::log(gaia::util::LogLevel::Warning, __VA_ARGS__)
345#define GAIA_LOG_E(...) gaia::util::log(gaia::util::LogLevel::Error, __VA_ARGS__)