Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/common/log.cpp
4214 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "log.h"
5
#include "assert.h"
6
#include "file_system.h"
7
#include "small_string.h"
8
#include "timer.h"
9
10
#include "fmt/format.h"
11
12
#include <array>
13
#include <bitset>
14
#include <cstdio>
15
#include <mutex>
16
#include <vector>
17
18
#if defined(_WIN32)
19
#include "windows_headers.h"
20
#elif defined(__ANDROID__)
21
#include <android/log.h>
22
#else
23
#include <sys/ioctl.h>
24
#include <termios.h>
25
#include <unistd.h>
26
#endif
27
28
using namespace std::string_view_literals;
29
30
namespace Log {
31
namespace {
32
struct RegisteredCallback
33
{
34
Log::CallbackFunctionType Function;
35
void* Parameter;
36
};
37
} // namespace
38
39
using ChannelBitSet = std::bitset<static_cast<size_t>(Channel::MaxCount)>;
40
41
static void RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
42
const std::unique_lock<std::mutex>& lock);
43
static void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
44
const std::unique_lock<std::mutex>& lock);
45
46
static bool FilterTest(Channel channel, Level level);
47
static void ExecuteCallbacks(MessageCategory cat, const char* functionName, std::string_view message);
48
static void FormatLogMessageForDisplay(fmt::memory_buffer& buffer, MessageCategory cat, const char* functionName,
49
std::string_view message, bool timestamp, bool ansi_color_code);
50
static void ConsoleOutputLogCallback(void* pUserParam, MessageCategory cat, const char* functionName,
51
std::string_view message);
52
static void DebugOutputLogCallback(void* pUserParam, MessageCategory cat, const char* functionName,
53
std::string_view message);
54
static void FileOutputLogCallback(void* pUserParam, MessageCategory cat, const char* functionName,
55
std::string_view message);
56
template<typename T>
57
static void FormatLogMessageAndPrint(MessageCategory cat, const char* functionName, std::string_view message,
58
bool timestamp, bool ansi_color_code, const T& callback);
59
#ifdef _WIN32
60
template<typename T>
61
static void FormatLogMessageAndPrintW(MessageCategory cat, const char* functionName, std::string_view message,
62
bool timestamp, bool ansi_color_code, const T& callback);
63
#endif
64
65
static constexpr const std::array<char, static_cast<size_t>(Level::MaxCount)> s_log_level_characters = {
66
{'X', 'E', 'W', 'I', 'V', 'D', 'B', 'T'}};
67
68
static constexpr const std::array<const char*, static_cast<size_t>(Channel::MaxCount)> s_log_channel_names = {{
69
#define LOG_CHANNEL_NAME(X) #X,
70
ENUMERATE_LOG_CHANNELS(LOG_CHANNEL_NAME)
71
#undef LOG_CHANNEL_NAME
72
}};
73
74
namespace {
75
76
struct State
77
{
78
Level log_level = Level::Trace;
79
ChannelBitSet log_channels_enabled = ChannelBitSet().set();
80
81
std::vector<RegisteredCallback> callbacks;
82
std::mutex callbacks_mutex;
83
84
Timer::Value start_timestamp = Timer::GetCurrentValue();
85
86
FileSystem::ManagedCFilePtr file_handle;
87
88
bool console_output_enabled = false;
89
bool console_output_timestamps = false;
90
bool file_output_enabled = false;
91
bool file_output_timestamp = false;
92
bool debug_output_enabled = false;
93
94
#ifdef _WIN32
95
HANDLE hConsoleStdIn = NULL;
96
HANDLE hConsoleStdOut = NULL;
97
HANDLE hConsoleStdErr = NULL;
98
#endif
99
};
100
101
} // namespace
102
103
ALIGN_TO_CACHE_LINE static State s_state;
104
105
} // namespace Log
106
107
void Log::RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam)
108
{
109
std::unique_lock lock(s_state.callbacks_mutex);
110
RegisterCallback(callbackFunction, pUserParam, lock);
111
}
112
113
void Log::RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
114
const std::unique_lock<std::mutex>& lock)
115
{
116
RegisteredCallback Callback;
117
Callback.Function = callbackFunction;
118
Callback.Parameter = pUserParam;
119
120
s_state.callbacks.push_back(std::move(Callback));
121
}
122
123
void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam)
124
{
125
std::unique_lock lock(s_state.callbacks_mutex);
126
UnregisterCallback(callbackFunction, pUserParam, lock);
127
}
128
129
void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
130
const std::unique_lock<std::mutex>& lock)
131
{
132
for (auto iter = s_state.callbacks.begin(); iter != s_state.callbacks.end(); ++iter)
133
{
134
if (iter->Function == callbackFunction && iter->Parameter == pUserParam)
135
{
136
s_state.callbacks.erase(iter);
137
break;
138
}
139
}
140
}
141
142
const std::array<const char*, static_cast<size_t>(Log::Channel::MaxCount)>& Log::GetChannelNames()
143
{
144
return s_log_channel_names;
145
}
146
147
float Log::GetCurrentMessageTime()
148
{
149
return static_cast<float>(Timer::ConvertValueToSeconds(Timer::GetCurrentValue() - s_state.start_timestamp));
150
}
151
152
bool Log::AreConsoleOutputTimestampsEnabled()
153
{
154
return s_state.console_output_timestamps;
155
}
156
157
bool Log::IsConsoleOutputCurrentlyAvailable()
158
{
159
#ifdef _WIN32
160
const HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
161
return (h != NULL && h != INVALID_HANDLE_VALUE);
162
#elif defined(__ANDROID__)
163
return false;
164
#else
165
// standard output isn't really reliable because it could be redirected to a file. check standard input for tty.
166
struct termios attr;
167
return (tcgetattr(STDIN_FILENO, &attr) == 0);
168
#endif
169
}
170
171
bool Log::IsConsoleOutputEnabled()
172
{
173
return s_state.console_output_enabled;
174
}
175
176
bool Log::IsDebugOutputEnabled()
177
{
178
return s_state.debug_output_enabled;
179
}
180
181
Log::Color Log::GetColorForLevel(Level level)
182
{
183
static constexpr const std::array s_level_colours = {
184
Color::Default, // None
185
Color::StrongRed, // Error
186
Color::StrongYellow, // Warning
187
Color::StrongWhite, // Info
188
Color::StrongGreen, // Verbose
189
Color::White, // Dev
190
Color::Green, // Debug
191
Color::Blue, // Trace
192
};
193
194
return s_level_colours[static_cast<size_t>(level)];
195
}
196
197
void Log::ExecuteCallbacks(MessageCategory cat, const char* functionName, std::string_view message)
198
{
199
for (RegisteredCallback& callback : s_state.callbacks)
200
callback.Function(callback.Parameter, cat, functionName, message);
201
}
202
203
ALWAYS_INLINE_RELEASE void Log::FormatLogMessageForDisplay(fmt::memory_buffer& buffer, MessageCategory cat,
204
const char* functionName, std::string_view message,
205
bool timestamp, bool ansi_color_code)
206
{
207
static constexpr const std::array s_ansi_color_codes = {
208
"\033[0m"sv, // default
209
"\033[30m\033[1m"sv, // black
210
"\033[31m"sv, // red
211
"\033[32m"sv, // green
212
"\033[34m"sv, // blue
213
"\033[35m"sv, // magenta
214
"\033[38;5;217m"sv, // orange
215
"\033[36m"sv, // cyan
216
"\033[33m"sv, // yellow
217
"\033[37m"sv, // white
218
"\033[30m\033[1m"sv, // strong black
219
"\033[31m\033[1m"sv, // strong red
220
"\033[32m\033[1m"sv, // strong green
221
"\033[34m\033[1m"sv, // strong blue
222
"\033[35m\033[1m"sv, // strong magenta
223
"\033[38;5;202m"sv, // strong orange
224
"\033[36m\033[1m"sv, // strong cyan
225
"\033[33m\033[1m"sv, // strong yellow
226
"\033[37m\033[1m"sv, // strong white
227
};
228
229
const Level level = UnpackLevel(cat);
230
const Color color = (UnpackColor(cat) == Color::Default) ? GetColorForLevel(level) : UnpackColor(cat);
231
const char* channel_name = GetChannelName(UnpackChannel(cat));
232
233
std::string_view color_start = ansi_color_code ? s_ansi_color_codes[static_cast<size_t>(color)] : ""sv;
234
std::string_view color_end = ansi_color_code ? s_ansi_color_codes[0] : ""sv;
235
236
auto appender = std::back_inserter(buffer);
237
238
if (timestamp)
239
{
240
// find time since start of process
241
const float message_time = Log::GetCurrentMessageTime();
242
243
// have to break it up into lines
244
std::string_view::size_type start = 0;
245
for (;;)
246
{
247
const std::string_view::size_type pos = message.find('\n', start);
248
const std::string_view sub_message =
249
(pos == std::string_view::npos) ? message.substr(start) : message.substr(start, pos - start);
250
const std::string_view end_message = sub_message.ends_with('\n') ? ""sv : "\n"sv;
251
252
if (functionName)
253
{
254
fmt::format_to(appender, "[{:10.4f}] {}{}({}): {}{}{}", message_time, color_start,
255
s_log_level_characters[static_cast<size_t>(level)], functionName, sub_message, color_end,
256
end_message);
257
}
258
else
259
{
260
fmt::format_to(appender, "[{:10.4f}] {}{}/{}: {}{}{}", message_time, color_start,
261
s_log_level_characters[static_cast<size_t>(level)], channel_name, sub_message, color_end,
262
end_message);
263
}
264
265
if (pos != std::string_view::npos)
266
start = pos + 1;
267
else
268
break;
269
}
270
}
271
else
272
{
273
if (functionName)
274
{
275
fmt::format_to(appender, "{}{}({}): {}{}\n", color_start, s_log_level_characters[static_cast<size_t>(level)],
276
functionName, message, color_end);
277
}
278
else
279
{
280
fmt::format_to(appender, "{}{}/{}: {}{}\n", color_start, s_log_level_characters[static_cast<size_t>(level)],
281
channel_name, message, color_end);
282
}
283
}
284
}
285
286
template<typename T>
287
ALWAYS_INLINE_RELEASE void Log::FormatLogMessageAndPrint(MessageCategory cat, const char* functionName,
288
std::string_view message, bool timestamp, bool ansi_color_code,
289
const T& callback)
290
{
291
fmt::memory_buffer buffer;
292
FormatLogMessageForDisplay(buffer, cat, functionName, message, timestamp, ansi_color_code);
293
callback(std::string_view(buffer.data(), buffer.size()));
294
}
295
296
#ifdef _WIN32
297
298
template<typename T>
299
ALWAYS_INLINE_RELEASE void Log::FormatLogMessageAndPrintW(MessageCategory cat, const char* functionName,
300
std::string_view message, bool timestamp,
301
bool ansi_color_code, const T& callback)
302
{
303
fmt::memory_buffer buffer;
304
FormatLogMessageForDisplay(buffer, cat, functionName, message, timestamp, ansi_color_code);
305
306
// Convert to UTF-16 first so unicode characters display correctly. NT is going to do it
307
// anyway...
308
wchar_t wbuf[512];
309
wchar_t* wmessage_buf = wbuf;
310
int wmessage_buflen = static_cast<int>(std::size(wbuf) - 1);
311
if (buffer.size() >= std::size(wbuf))
312
{
313
wmessage_buflen = static_cast<int>(buffer.size());
314
wmessage_buf = static_cast<wchar_t*>(std::malloc((buffer.size() + 1) * sizeof(wchar_t)));
315
}
316
317
wmessage_buflen =
318
MultiByteToWideChar(CP_UTF8, 0, buffer.data(), static_cast<int>(buffer.size()), wmessage_buf, wmessage_buflen);
319
if (wmessage_buflen > 0) [[likely]]
320
{
321
wmessage_buf[wmessage_buflen] = '\0';
322
callback(std::wstring_view(wmessage_buf, wmessage_buflen));
323
}
324
325
if (wmessage_buf != wbuf)
326
std::free(wmessage_buf);
327
}
328
329
static bool EnableVirtualTerminalProcessing(HANDLE hConsole)
330
{
331
DWORD old_mode;
332
if (!GetConsoleMode(hConsole, &old_mode))
333
return false;
334
335
// already enabled?
336
if (old_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
337
return true;
338
339
return SetConsoleMode(hConsole, old_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
340
}
341
342
#endif
343
344
void Log::ConsoleOutputLogCallback(void* pUserParam, MessageCategory cat, const char* functionName,
345
std::string_view message)
346
{
347
if (!s_state.console_output_enabled)
348
return;
349
350
#if defined(_WIN32)
351
FormatLogMessageAndPrintW(
352
cat, functionName, message, s_state.console_output_timestamps, true, [cat](const std::wstring_view& message) {
353
HANDLE hOutput = (UnpackLevel(cat) <= Level::Warning) ? s_state.hConsoleStdErr : s_state.hConsoleStdOut;
354
DWORD chars_written;
355
WriteConsoleW(hOutput, message.data(), static_cast<DWORD>(message.length()), &chars_written, nullptr);
356
});
357
#elif !defined(__ANDROID__)
358
FormatLogMessageAndPrint(
359
cat, functionName, message, s_state.console_output_timestamps, true, [cat](std::string_view message) {
360
const int outputFd = (UnpackLevel(cat) <= Log::Level::Warning) ? STDERR_FILENO : STDOUT_FILENO;
361
write(outputFd, message.data(), message.length());
362
});
363
#endif
364
}
365
366
void Log::DebugOutputLogCallback(void* pUserParam, MessageCategory cat, const char* functionName,
367
std::string_view message)
368
{
369
if (!s_state.debug_output_enabled)
370
return;
371
372
#if defined(_WIN32)
373
FormatLogMessageAndPrintW(cat, functionName, message, false, false,
374
[](const std::wstring_view& message) { OutputDebugStringW(message.data()); });
375
#elif defined(__ANDROID__)
376
if (message.empty())
377
return;
378
379
static constexpr int logPriority[static_cast<size_t>(Level::MaxCount)] = {
380
ANDROID_LOG_INFO, // None
381
ANDROID_LOG_ERROR, // Error
382
ANDROID_LOG_WARN, // Warning
383
ANDROID_LOG_INFO, // Info
384
ANDROID_LOG_INFO, // Verbose
385
ANDROID_LOG_DEBUG, // Dev
386
ANDROID_LOG_DEBUG, // Debug
387
ANDROID_LOG_DEBUG, // Trace
388
};
389
390
__android_log_print(logPriority[static_cast<size_t>(UnpackLevel(cat))], GetChannelName(UnpackChannel(cat)), "%.*s",
391
static_cast<int>(message.length()), message.data());
392
#endif
393
}
394
395
void Log::SetConsoleOutputParams(bool enabled, bool timestamps)
396
{
397
std::unique_lock lock(s_state.callbacks_mutex);
398
399
s_state.console_output_timestamps = timestamps;
400
if (s_state.console_output_enabled == enabled)
401
return;
402
403
s_state.console_output_enabled = enabled;
404
405
#if defined(_WIN32)
406
// On windows, no console is allocated by default on a windows based application
407
static bool console_was_allocated = false;
408
static HANDLE old_stdin = NULL;
409
static HANDLE old_stdout = NULL;
410
static HANDLE old_stderr = NULL;
411
412
if (enabled)
413
{
414
old_stdin = GetStdHandle(STD_INPUT_HANDLE);
415
old_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
416
old_stderr = GetStdHandle(STD_ERROR_HANDLE);
417
418
if (!old_stdout)
419
{
420
// Attach to the parent console if we're running from a command window
421
if (!AttachConsole(ATTACH_PARENT_PROCESS) && !AllocConsole())
422
return;
423
424
s_state.hConsoleStdIn = GetStdHandle(STD_INPUT_HANDLE);
425
s_state.hConsoleStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
426
s_state.hConsoleStdErr = GetStdHandle(STD_ERROR_HANDLE);
427
428
EnableVirtualTerminalProcessing(s_state.hConsoleStdOut);
429
EnableVirtualTerminalProcessing(s_state.hConsoleStdErr);
430
431
std::FILE* fp;
432
freopen_s(&fp, "CONIN$", "r", stdin);
433
freopen_s(&fp, "CONOUT$", "w", stdout);
434
freopen_s(&fp, "CONOUT$", "w", stderr);
435
436
console_was_allocated = true;
437
}
438
else
439
{
440
s_state.hConsoleStdIn = old_stdin;
441
s_state.hConsoleStdOut = old_stdout;
442
s_state.hConsoleStdErr = old_stderr;
443
}
444
}
445
else
446
{
447
if (console_was_allocated)
448
{
449
console_was_allocated = false;
450
451
std::FILE* fp;
452
freopen_s(&fp, "NUL:", "w", stderr);
453
freopen_s(&fp, "NUL:", "w", stdout);
454
freopen_s(&fp, "NUL:", "w", stdin);
455
456
SetStdHandle(STD_ERROR_HANDLE, old_stderr);
457
SetStdHandle(STD_OUTPUT_HANDLE, old_stdout);
458
SetStdHandle(STD_INPUT_HANDLE, old_stdin);
459
460
s_state.hConsoleStdIn = NULL;
461
s_state.hConsoleStdOut = NULL;
462
s_state.hConsoleStdErr = NULL;
463
464
FreeConsole();
465
}
466
}
467
#endif
468
469
if (enabled)
470
RegisterCallback(ConsoleOutputLogCallback, nullptr, lock);
471
else
472
UnregisterCallback(ConsoleOutputLogCallback, nullptr, lock);
473
}
474
475
void Log::SetDebugOutputParams(bool enabled)
476
{
477
std::unique_lock lock(s_state.callbacks_mutex);
478
if (s_state.debug_output_enabled == enabled)
479
return;
480
481
s_state.debug_output_enabled = enabled;
482
if (enabled)
483
RegisterCallback(DebugOutputLogCallback, nullptr, lock);
484
else
485
UnregisterCallback(DebugOutputLogCallback, nullptr, lock);
486
}
487
488
void Log::FileOutputLogCallback(void* pUserParam, MessageCategory cat, const char* functionName,
489
std::string_view message)
490
{
491
if (!s_state.file_output_enabled)
492
return;
493
494
FormatLogMessageAndPrint(cat, functionName, message, s_state.file_output_timestamp, false,
495
[](std::string_view message) {
496
std::fwrite(message.data(), 1, message.size(), s_state.file_handle.get());
497
std::fflush(s_state.file_handle.get());
498
});
499
}
500
501
void Log::SetFileOutputParams(bool enabled, const char* filename, bool timestamps /* = true */)
502
{
503
std::unique_lock lock(s_state.callbacks_mutex);
504
505
s_state.file_output_timestamp = timestamps;
506
507
if (s_state.file_output_enabled == enabled)
508
return;
509
510
if (enabled)
511
{
512
s_state.file_handle = FileSystem::OpenManagedSharedCFile(filename, "wb", FileSystem::FileShareMode::DenyWrite);
513
if (!s_state.file_handle) [[unlikely]]
514
{
515
ExecuteCallbacks(PackCategory(Channel::Log, Level::Error, Color::Default), nullptr,
516
TinyString::from_format("Failed to open log file '{}'", filename));
517
return;
518
}
519
520
RegisterCallback(FileOutputLogCallback, nullptr, lock);
521
}
522
else
523
{
524
UnregisterCallback(FileOutputLogCallback, nullptr, lock);
525
s_state.file_handle.reset();
526
}
527
528
s_state.file_output_enabled = enabled;
529
}
530
531
Log::Level Log::GetLogLevel()
532
{
533
return s_state.log_level;
534
}
535
536
bool Log::IsLogVisible(Level level, Channel channel)
537
{
538
return FilterTest(channel, level);
539
}
540
541
void Log::SetLogLevel(Level level)
542
{
543
std::unique_lock lock(s_state.callbacks_mutex);
544
DebugAssert(level < Level::MaxCount);
545
s_state.log_level = level;
546
}
547
548
void Log::SetLogChannelEnabled(Channel channel, bool enabled)
549
{
550
std::unique_lock lock(s_state.callbacks_mutex);
551
DebugAssert(channel < Channel::MaxCount);
552
s_state.log_channels_enabled[static_cast<size_t>(channel)] = enabled;
553
}
554
555
const char* Log::GetChannelName(Channel channel)
556
{
557
return s_log_channel_names[static_cast<size_t>(channel)];
558
}
559
560
ALWAYS_INLINE_RELEASE bool Log::FilterTest(Channel channel, Level level)
561
{
562
return (level <= s_state.log_level && s_state.log_channels_enabled[static_cast<size_t>(channel)]);
563
}
564
565
void Log::Write(MessageCategory cat, std::string_view message)
566
{
567
if (!FilterTest(UnpackChannel(cat), UnpackLevel(cat)))
568
return;
569
570
std::unique_lock lock(s_state.callbacks_mutex);
571
ExecuteCallbacks(cat, nullptr, message);
572
}
573
574
void Log::Write(MessageCategory cat, const char* functionName, std::string_view message)
575
{
576
if (!FilterTest(UnpackChannel(cat), UnpackLevel(cat)))
577
return;
578
579
std::unique_lock lock(s_state.callbacks_mutex);
580
ExecuteCallbacks(cat, functionName, message);
581
}
582
583
void Log::WriteFmtArgs(MessageCategory cat, fmt::string_view fmt, fmt::format_args args)
584
{
585
if (!FilterTest(UnpackChannel(cat), UnpackLevel(cat)))
586
return;
587
588
fmt::memory_buffer buffer;
589
fmt::vformat_to(std::back_inserter(buffer), fmt, args);
590
591
std::unique_lock lock(s_state.callbacks_mutex);
592
ExecuteCallbacks(cat, nullptr, std::string_view(buffer.data(), buffer.size()));
593
}
594
595
void Log::WriteFmtArgs(MessageCategory cat, const char* functionName, fmt::string_view fmt, fmt::format_args args)
596
{
597
if (!FilterTest(UnpackChannel(cat), UnpackLevel(cat)))
598
return;
599
600
fmt::memory_buffer buffer;
601
fmt::vformat_to(std::back_inserter(buffer), fmt, args);
602
603
std::unique_lock lock(s_state.callbacks_mutex);
604
ExecuteCallbacks(cat, functionName, std::string_view(buffer.data(), buffer.size()));
605
}
606
607