Gaia-ECS v0.9.3
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 {
21 using LogLevelType = uint8_t;
22
23 enum class LogLevel : LogLevelType { Debug = 0x1, Info = 0x2, Warning = 0x4, Error = 0x8 };
24 inline LogLevelType g_logLevelMask = (LogLevelType)LogLevel::Debug | (LogLevelType)LogLevel::Info |
25 (LogLevelType)LogLevel::Warning | (LogLevelType)LogLevel::Error;
26
30 inline void log_enable(LogLevel level, bool value) {
31 if (value)
32 gaia::util::g_logLevelMask |= ((LogLevelType)level);
33 else
34 gaia::util::g_logLevelMask &= ~((LogLevelType)level);
35 }
36
38 inline bool is_logging_enabled(LogLevel level) {
39 return ((LogLevelType)level & g_logLevelMask) != 0;
40 }
41
42 using LogLineFunc = void (*)(LogLevel, const char*);
43 using LogFunc = void (*)(LogLevel, const char*, va_list);
44 using LogFlushFunc = void (*)();
45
46 namespace detail {
47 inline constexpr uint32_t LOG_BUFFER_SIZE = GAIA_LOG_BUFFER_SIZE;
48 inline constexpr uint32_t LOG_RECORD_LIMIT = GAIA_LOG_BUFFER_ENTRIES;
49
50 inline FILE* get_log_out(LogLevel level) {
51 const auto mask = (LogLevelType)level & ((LogLevelType)LogLevel::Error | (LogLevelType)LogLevel::Warning);
52 // If a warning or error level is set we will use stderr for output.
53 return mask != 0 ? stderr : stdout;
54 }
55
56 // LCOV_EXCL_START
57
59 inline void log_line(LogLevel level, const char* msg) {
60 FILE* out = get_log_out(level);
61
62 static constexpr const char* colors[] = {
63 "\033[1;32mD: ", // Debug
64 "\033[0mI: ", // Info
65 "\033[1;33mW: ", // Warning
66 "\033[1;31mE: " // Error
67 };
68 // LogLevel is a bitmask. Calculate what bit is and use it as an index.
69 const auto lvl = (uint32_t)level;
70 const auto idx = GAIA_CLZ(lvl);
71 fprintf(out, "%s%s\033[0m\n", colors[idx], msg);
72 }
73 inline LogLineFunc g_log_line_func = log_line;
74
75 // LCOV_EXCL_STOP
76
77 struct LogBuffer {
78 struct LogRecord {
79 uint32_t offset : 29;
80 uint32_t level : 3; // 3 bits for LogLevel mask
81 };
82
83 char m_buffer[LOG_BUFFER_SIZE];
84 LogRecord m_recs[LOG_RECORD_LIMIT];
85 uint32_t m_buffer_pos = 0;
86 uint32_t m_recs_pos = 0;
87
94 void log(LogLevel level, uint32_t len, const char* msg) {
95 FILE* out = get_log_out(level);
96 const bool is_assert = out == stderr;
97
98 // Big message? Write directly
99 if (is_assert || len >= detail::LOG_BUFFER_SIZE) {
100 // Flush existing buffer first
101 flush();
102
103 // Print message directly (bypass cache)
104 g_log_line_func(level, msg);
105 fflush(out);
106 return;
107 }
108
109 // Normal caching path. If the message doesn't fit, or if there are too many records, flush.
110 if (m_buffer_pos + len > detail::LOG_BUFFER_SIZE || m_recs_pos >= detail::LOG_RECORD_LIMIT)
111 flush();
112
113 // Append message to cache
114 auto& rec = m_recs[m_recs_pos];
115 rec.offset = m_buffer_pos;
116 rec.level = (LogLevelType)level;
117 memcpy(m_buffer + m_buffer_pos, msg, len);
118
119 m_buffer_pos += len;
120 ++m_recs_pos;
121 }
122
123 void flush() {
124 if (m_recs_pos == 0)
125 return;
126
127 for (size_t i = 0; i < m_recs_pos; ++i) {
128 const auto& rec = m_recs[i];
129 g_log_line_func((LogLevel)rec.level, &m_buffer[rec.offset]);
130 }
131
132 m_recs_pos = 0;
133 m_buffer_pos = 0;
134 fflush(stdout);
135 }
136
137 LogBuffer() {
138 // Disable flushing on the new lines. We will control flushing fully.
139 // To avoid issues with Windows’ UCRT we set some reasonable non-zero value.
140 setvbuf(stdout, nullptr, _IOFBF, 4096);
141 setvbuf(stderr, nullptr, _IOFBF, 4096);
142 }
143 ~LogBuffer() {
144 // Flush before the object disappears
145 flush();
146 }
147
148 LogBuffer(const LogBuffer&) = delete;
149 LogBuffer(LogBuffer&&) = delete;
150 LogBuffer& operator=(const LogBuffer&) = delete;
151 LogBuffer& operator=(LogBuffer&&) = delete;
152 };
153
154 inline LogBuffer* g_log() {
155 static LogBuffer* s_log = nullptr;
156 if (s_log == nullptr) {
157 s_log = new LogBuffer();
158 // Register automatic cleanup
159 static struct LogAtExit {
160 LogAtExit() = default;
161 ~LogAtExit() {
162 if (s_log != nullptr) {
163 s_log->flush();
164 delete s_log;
165 s_log = nullptr;
166 }
167 }
168
169 LogAtExit(const LogAtExit&) = delete;
170 LogAtExit(LogAtExit&&) = delete;
171 LogAtExit& operator=(const LogAtExit&) = delete;
172 LogAtExit& operator=(LogAtExit&&) = delete;
173 } s_logDeleter;
174 }
175 return s_log;
176 }
177
179 inline void log_cached(LogLevel level, const char* fmt, va_list args) {
180 va_list args_copy{};
181
182 GAIA_CLANG_WARNING_PUSH()
183 GAIA_GCC_WARNING_PUSH()
184 GAIA_CLANG_WARNING_DISABLE("-Wformat-nonliteral")
185 GAIA_GCC_WARNING_DISABLE("-Wformat-nonliteral")
186 // Early exit if there is nothing to write
187 va_copy(args_copy, args);
188 int l = vsnprintf(nullptr, 0, fmt, args_copy);
189 va_end(args_copy);
190 if (l <= 0)
191 return;
192
193 const auto len = (uint32_t)l;
194 cnt::darray_ext<char, 1024> msg(len + 1);
195
196 va_copy(args_copy, args);
197 vsnprintf(msg.data(), msg.size(), fmt, args_copy);
198 va_end(args_copy);
199 GAIA_GCC_WARNING_POP()
200 GAIA_CLANG_WARNING_POP()
201
202 // Always null-terminate logs
203 msg[len] = 0;
204
205 // Log a message.
206 // We implement a buffering strategy. Warnings and errors flush immediately.
207 // Otherwise, we flush once the buffer is filled or on-demand manually.
208 g_log()->log(level, msg.size(), msg.data());
209 }
210
212 inline void log_flush_cached() {
213 g_log()->flush();
214 }
215
216 // LCOV_EXCL_START
217
219 inline void log_default(LogLevel level, const char* fmt, va_list args) {
220 va_list args_copy{};
221
222 GAIA_CLANG_WARNING_PUSH()
223 GAIA_GCC_WARNING_PUSH()
224 GAIA_CLANG_WARNING_DISABLE("-Wformat-nonliteral")
225 GAIA_GCC_WARNING_DISABLE("-Wformat-nonliteral")
226 // Early exit if there is nothing to write
227 va_copy(args_copy, args);
228 int l = vsnprintf(nullptr, 0, fmt, args_copy);
229 va_end(args_copy);
230 if (l <= 0)
231 return;
232
233 const auto len = (uint32_t)l;
234 cnt::darray_ext<char, 1024> msg(len + 1);
235
236 va_copy(args_copy, args);
237 vsnprintf(msg.data(), msg.size(), fmt, args_copy);
238 va_end(args_copy);
239 GAIA_GCC_WARNING_POP()
240 GAIA_CLANG_WARNING_POP()
241
242 // Always null-terminate logs
243 msg[len] = 0;
244
245 g_log_line_func(level, msg.data());
246 }
247
248 // LCOV_EXCL_STOP
249
251 inline void log_flush_default() {}
252
253 inline LogFunc g_log_func = log_default;
254 inline LogFlushFunc g_log_flush_func = log_flush_default;
255 } // namespace detail
256
260 inline void set_log_func(LogFunc func) {
261 detail::g_log_func = func != nullptr ? func : detail::log_default;
262 }
263
266 inline void set_log_line_func(LogLineFunc func) {
267 detail::g_log_line_func = func != nullptr ? func : detail::log_line;
268 }
269
272 inline void set_log_flush_func(LogFlushFunc func) {
273 detail::g_log_flush_func = func != nullptr ? func : detail::log_flush_default;
274 }
275
279 inline void log(LogLevel level, const char* fmt, ...) {
280 if (!is_logging_enabled(level))
281 return;
282
283 va_list args;
284 va_start(args, fmt);
285 detail::g_log_func(level, fmt, args);
286 va_end(args);
287 }
288
289 inline void log_flush() {
290 detail::g_log_flush_func();
291 }
292 } // namespace util
293} // namespace gaia
294
295// LCOV_EXCL_START
296extern "C" {
297
298typedef void (*gaia_log_line_func_t)(gaia::util::LogLevelType level, const char* msg);
299inline void gaia_set_log_func(gaia_log_line_func_t func) {
300 gaia::util::set_log_line_func((gaia::util::LogLineFunc)func);
301}
302
303inline void gaia_log(uint8_t level, const char* msg) {
304 gaia::util::log((gaia::util::LogLevel)level, "%s", msg);
305}
306
307typedef void (*gaia_log_flush_func_t)();
308inline void gaia_set_flush_func(gaia_log_flush_func_t func) {
309 gaia::util::set_log_flush_func((gaia::util::LogFlushFunc)func);
310}
311
312inline void gaia_flush_logs() {
313 gaia::util::log_flush();
314}
315
316inline void gaia_log_enable(gaia::util::LogLevelType level, bool value) {
317 gaia::util::log_enable((gaia::util::LogLevel)level, value);
318}
319
320inline bool gaia_is_logging_enabled(gaia::util::LogLevelType level) {
321 return gaia::util::is_logging_enabled((gaia::util::LogLevel)level);
322}
323}
324// LCOV_EXCL_STOP
325
326#define GAIA_LOG_D(...) gaia::util::log(gaia::util::LogLevel::Debug, __VA_ARGS__)
327#define GAIA_LOG_N(...) gaia::util::log(gaia::util::LogLevel::Info, __VA_ARGS__)
328#define GAIA_LOG_W(...) gaia::util::log(gaia::util::LogLevel::Warning, __VA_ARGS__)
329#define GAIA_LOG_E(...) gaia::util::log(gaia::util::LogLevel::Error, __VA_ARGS__)
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition logging.h:77
void log(LogLevel level, uint32_t len, const char *msg)
Logs a message. We implement a buffering strategy. Warnings and errors flush immediately....
Definition logging.h:94