libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
Logger.cpp
1/****************************************************************************
2 * Platform Abstraction Layer
3 * Daryl Borth 2026
4 * Logger.cpp
5 *
6 * Generic - every platform-specific byte in or out lives behind an
7 * LoggingDriver implementation in drivers/ogc or drivers/wut. This file
8 * never includes a platform SDK header.
9 ***************************************************************************/
10#include "Logger.h"
11#include "Platform.h"
12#include "Time.h"
13
14#include <cstdio>
15#include <cstring>
16
17static const Ticks processStartTicks = SystemTime::now();
18
19Logger::Logger()
20{
21}
22
23Logger::~Logger()
24{
25 shutdown();
26
27 for (int i = 0; i < slotCount; i++)
28 delete slots[i].backend;
29}
30
32{
33 // Must be exactly one bit - a caller passing an OR of flags here
34 // almost certainly meant multiBackendMask, not this.
35 if (backend == nullptr || id == LOGGER_NONE || (id & (id - 1)) != 0)
36 {
37 delete backend;
38 return false;
39 }
40
41 for (int i = 0; i < slotCount; i++)
42 {
43 if (slots[i].id == id)
44 {
45 delete backend;
46 return false;
47 }
48 }
49
50 if (slotCount >= MAX_BACKENDS)
51 {
52 delete backend;
53 return false;
54 }
55
56 slots[slotCount].id = id;
57 slots[slotCount].backend = backend;
58 slots[slotCount].active = false;
59 slotCount++;
60 return true;
61}
62
63uint32_t Logger::resolveActiveMask() const
64{
65 uint32_t mask;
66
67 switch (logConfig.mode)
68 {
69 case LogMode::OSReport: mask = LOGGER_OSREPORT; break;
70 case LogMode::UDP: mask = LOGGER_UDP; break;
71 case LogMode::SerialGecko: mask = LOGGER_SERIAL; break;
72 case LogMode::File: mask = LOGGER_FILE; break;
73 case LogMode::Multi: mask = logConfig.multiBackendMask; break;
74 default: mask = LOGGER_NONE; break;
75 }
76
77 if (logConfig.mirrorToOSReport)
78 mask |= LOGGER_OSREPORT;
79
80 return mask;
81}
82
83void Logger::init(const LogConfig & newConfig)
84{
85 MutexLock guard(lock);
86
87 logConfig = newConfig;
88 uint32_t activeMask = resolveActiveMask();
89
90 // Tracked per-slot rather than reported inline: we want every
91 // failure surfaced only once OSReport/SYS_Report (if any) is known
92 // to be active, and registration order isn't guaranteed to put that
93 // backend first.
94 bool activationFailed[MAX_BACKENDS] = {};
95
96 for (int i = 0; i < slotCount; i++)
97 {
98 bool wantActive = (slots[i].id & activeMask) != 0;
99
100 if (wantActive && !slots[i].active)
101 {
102 slots[i].active = slots[i].backend->init(logConfig);
103 activationFailed[i] = !slots[i].active;
104 }
105 else if (!wantActive && slots[i].active)
106 {
107 slots[i].backend->shutdown();
108 slots[i].active = false;
109 }
110 else if (wantActive && slots[i].active)
111 {
112 // Already active from a previous init() - reopen against
113 // the new logConfig (target IP/path/etc may have changed).
114 slots[i].backend->shutdown();
115 slots[i].active = slots[i].backend->init(logConfig);
116 activationFailed[i] = !slots[i].active;
117 }
118 }
119
120 // Surface activation failures directly through the OSReport/
121 // SYS_Report backend, if one is active
122 int osReportSlot = -1;
123 for (int i = 0; i < slotCount; i++)
124 if (slots[i].id == LOGGER_OSREPORT && slots[i].active)
125 osReportSlot = i;
126
127 if (osReportSlot >= 0)
128 {
129 for (int i = 0; i < slotCount; i++)
130 {
131 if (!activationFailed[i])
132 continue;
133
134 char msg[128];
135 int n = snprintf(msg, sizeof(msg), "[Logger] backend '%s' failed to activate (init() returned false)\n", slots[i].backend->name());
136 if (n > 0)
137 {
138 size_t len = (size_t)n < sizeof(msg) ? (size_t)n : sizeof(msg) - 1;
139 slots[osReportSlot].backend->write(LogLevel::Warning, msg, len);
140 }
141 }
142 }
143
144 initialized = true;
145}
146
148{
149 MutexLock guard(lock);
150
151 if (!initialized)
152 return; // logging isn't running - never activate behind its back
153
154 uint32_t activeMask = resolveActiveMask();
155 if ((id & activeMask) == 0)
156 return; // not wanted right now - mode off, or on but not this backend
157
158 for (int i = 0; i < slotCount; i++)
159 {
160 if (slots[i].id != id)
161 continue;
162
163 if (!slots[i].active)
164 slots[i].active = true; // backend already finished its own setup - just make it visible to log()
165
166 break;
167 }
168}
169
171{
172 MutexLock guard(lock);
173
174 // Close the file-backed log first
175 for (int i = 0; i < slotCount; i++)
176 {
177 if (slots[i].id == LOGGER_FILE && slots[i].active)
178 {
179 slots[i].backend->shutdown();
180 slots[i].active = false;
181 break;
182 }
183 }
184
185 for (int i = 0; i < slotCount; i++)
186 {
187 if (slots[i].active)
188 {
189 slots[i].backend->shutdown();
190 slots[i].active = false;
191 }
192 }
193
194 initialized = false;
195}
196
198{
199 MutexLock guard(lock);
200 logConfig.level = level;
201}
202
203static const char * LevelTag(LogLevel level)
204{
205 switch (level)
206 {
207 case LogLevel::Debug: return "[DEBUG] ";
208 case LogLevel::Info: return "[INFO] ";
209 case LogLevel::Warning: return "[WARN] ";
210 case LogLevel::Error: return "[ERROR] ";
211 default: return "";
212 }
213}
214
215void Logger::log(LogLevel level, const char * fmt, va_list args)
216{
217 if (!initialized || level == LogLevel::None)
218 return;
219
220 // Cheap early-out before taking the lock or touching the stack
221 // buffer at all - the overwhelmingly common case once a build has
222 // settled on a level (eg. Info in release, Debug only when actively
223 // chasing a bug).
224 if (level < logConfig.level)
225 return;
226
227 MutexLock guard(lock);
228
229 // Re-check under the lock: another thread may have raised the level
230 // (or shut Logger down) between the check above and taking it.
231 if (!initialized || level < logConfig.level)
232 return;
233
234 // Fixed stack buffer only - no malloc/new anywhere in this path.
235 char line[512];
236 size_t offset = 0;
237
238 if (logConfig.includeSequenceNumber)
239 {
240 int n = snprintf(line + offset, sizeof(line) - offset, "%06u ", (unsigned)(++sequence));
241 if (n > 0)
242 offset += (size_t)n < (sizeof(line) - offset) ? (size_t)n : (sizeof(line) - offset - 1);
243 }
244
245 if (logConfig.includeTimestamp)
246 {
247 uint32_t ms = SystemTime::diffMillisecs(processStartTicks, SystemTime::now());
248 int n = snprintf(line + offset, sizeof(line) - offset, "[%5u.%03u] ", (unsigned)(ms / 1000), (unsigned)(ms % 1000));
249 if (n > 0)
250 offset += (size_t)n < (sizeof(line) - offset) ? (size_t)n : (sizeof(line) - offset - 1);
251 }
252
253 if (logConfig.includeLevelTag)
254 {
255 size_t remaining = sizeof(line) - offset;
256 size_t tagLen = strlen(LevelTag(level));
257 if (tagLen < remaining)
258 {
259 memcpy(line + offset, LevelTag(level), tagLen);
260 offset += tagLen;
261 }
262 }
263
264 if (offset < sizeof(line) - 1)
265 {
266 int n = vsnprintf(line + offset, sizeof(line) - offset, fmt, args);
267 if (n > 0)
268 offset += (size_t)n < (sizeof(line) - offset) ? (size_t)n : (sizeof(line) - offset - 1);
269 }
270
271 // Ensure a trailing newline so line-oriented backends (UDP/file/
272 // serial) don't run consecutive log lines together - but don't
273 // overflow the buffer doing it.
274 if (offset == 0 || line[offset - 1] != '\n')
275 {
276 if (offset < sizeof(line) - 1)
277 line[offset++] = '\n';
278 else
279 line[offset - 1] = '\n';
280 }
281 line[offset] = '\0';
282
283 uint32_t activeMask = resolveActiveMask();
284
285 for (int i = 0; i < slotCount; i++)
286 {
287 if (slots[i].active && (slots[i].id & activeMask) != 0)
288 slots[i].backend->write(level, line, offset);
289 }
290}
291
292void LogPrintf(LogLevel level, const char * fmt, ...)
293{
294 if (!platform || !platform->getLogger())
295 return;
296
297 va_list args;
298 va_start(args, fmt);
299 platform->getLogger()->log(level, fmt, args);
300 va_end(args);
301}
302
303extern "C" void Log_Printf(int level, const char * fmt, ...)
304{
305 if (!platform || !platform->getLogger())
306 return;
307
308 va_list args;
309 va_start(args, fmt);
310 platform->getLogger()->log(static_cast<LogLevel>(level), fmt, args);
311 va_end(args);
312}
void log(LogLevel level, const char *fmt, va_list args)
Formats fmt/args into a fixed stack buffer and fans it out to every backend selected by the current c...
Definition Logger.cpp:215
void activateDeferred(LogBackendId id)
Called by a backend that deferred its own activation on some background precondition (eg.
Definition Logger.cpp:147
void shutdown()
Shuts down every registered backend and forgets the config.
Definition Logger.cpp:170
void setLevel(LogLevel level)
Runtime-adjusts the minimum severity without touching any other config field or re-touching backend i...
Definition Logger.cpp:197
bool registerBackend(LogBackendId id, LoggingDriver *backend)
Registers a backend under the given single-bit LogBackendId, for later selection via LogConfig::mode ...
Definition Logger.cpp:31
void init(const LogConfig &config)
Applies config, calling init()/shutdown() on registered backends as needed so only the ones actually ...
Definition Logger.cpp:83
static const int MAX_BACKENDS
Maximum number of backends that can be registered at once.
Definition Logger.h:241
Abstract backend a Logger fans a formatted line out to.
Definition Logger.h:153
RAII lock guard - locks on construction, unlocks on destruction.
Definition Mutex.h:37
static Ticks now()
Definition Time.h:28
static uint32_t diffMillisecs(Ticks start, Ticks end)
Definition Time.h:52
LogLevel
Severity of a single log call.
Definition Logger.h:45
LogBackendId
Bitmask identifying an individual backend.
Definition Logger.h:77
@ OSReport
OSReport (Wii U) / SYS_Report (GC+Wii) - always safe, no hardware required.
Definition Logger.h:64
@ Multi
fan out to every backend set in LogConfig::multiBackendMask
Definition Logger.h:68
Full runtime configuration for the Logger.
Definition Logger.h:101
uint32_t multiBackendMask
Only consulted when mode == LogMode::Multi.
Definition Logger.h:107
LogMode mode
Which backend(s) receive log lines; see LogMode.
Definition Logger.h:102