power_play/src/log.c

262 lines
6.4 KiB
C

#include "arena.h"
#include "log.h"
#include "string.h"
#include "atomic.h"
#include "snc.h"
struct log_event_callback {
log_event_callback_func *func;
struct log_event_callback *next;
i32 level;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct atomic_i32 initialized;
struct snc_mutex callbacks_mutex;
struct arena *callbacks_arena;
struct log_event_callback *first_callback;
struct log_event_callback *last_callback;
struct sys_file file;
b32 file_valid;
} G = ZI, DEBUG_ALIAS(G, G_log);
GLOBAL READONLY struct log_level_settings g_log_level_settings[LOG_LEVEL_COUNT] = {
[LOG_LEVEL_CRITICAL] = {
LIT_NOCAST("CRITICAL"),
COLOR_PURPLE
},
[LOG_LEVEL_ERROR] = {
LIT_NOCAST("ERROR"),
COLOR_RED
},
[LOG_LEVEL_WARNING] = {
LIT_NOCAST("WARNING"),
COLOR_YELLOW
},
[LOG_LEVEL_SUCCESS] = {
LIT_NOCAST("SUCCESS"),
COLOR_GREEN
},
[LOG_LEVEL_INFO] = {
LIT_NOCAST("INFO"),
COLOR_WHITE
},
[LOG_LEVEL_DEBUG] = {
LIT_NOCAST("DEBUG"),
COLOR_BLUE
}
};
/* ========================== *
* Startup
* ========================== */
void log_startup(struct string logfile_path)
{
G.callbacks_arena = arena_alloc(MEBI(8));
if (logfile_path.len > 0) {
/* Create / wipe log file */
sys_file_close(sys_file_open_write(logfile_path));
/* Keep log file open for appending */
if (sys_is_file(logfile_path)) {
G.file = sys_file_open_append(logfile_path);
G.file_valid = true;
}
}
atomic_i32_fetch_set(&G.initialized, 1);
}
/* ========================== *
* Callback
* ========================== */
void log_register_callback(log_event_callback_func *func, i32 level)
{
if (!atomic_i32_fetch(&G.initialized)) { return; }
struct snc_lock lock = snc_lock_e(&G.callbacks_mutex);
{
struct log_event_callback *callback = arena_push(G.callbacks_arena, struct log_event_callback);
callback->func = func;
callback->level = level;
if (G.last_callback) {
G.last_callback->next = callback;
} else {
G.first_callback = callback;
}
G.last_callback = callback;
}
snc_unlock(&lock);
}
/* ========================== *
* Log
* ========================== */
INTERNAL void append_to_logfile(struct string msg)
{
__prof;
if (!atomic_i32_fetch(&G.initialized)) { return; }
if (G.file_valid) {
struct arena_temp scratch = scratch_begin_no_conflict();
struct string msg_line = string_cat(scratch.arena, msg, LIT("\n"));
sys_file_write(G.file, msg_line);
scratch_end(scratch);
}
}
/* Panic log function is separate to enforce zero side effects other than
* writing to log file. */
void _log_panic(struct string msg)
{
if (!atomic_i32_fetch(&G.initialized)) { return; }
if (G.file_valid) {
sys_file_write(G.file, LIT("******** PANICKING ********\n"));
sys_file_write(G.file, msg);
sys_file_write(G.file, LIT("\n***************************\n"));
}
}
#if LOG_INCLUDE_SOURCE_LOCATION
void _log(i32 level, struct string file, u32 line, struct string msg)
#else
void _log(i32 level, struct string msg)
#endif
{
__prof;
if (!atomic_i32_fetch(&G.initialized)) { return; }
if (level < 0 || level >= LOG_LEVEL_COUNT) {
sys_panic(LIT("Invalid log level"));
}
struct arena_temp scratch = scratch_begin_no_conflict();
struct sys_datetime datetime = sys_local_time();
i64 time_ns = sys_time_ns();
u32 tid = sys_thread_id();
struct log_level_settings settings = g_log_level_settings[level];
struct string shorthand = settings.shorthand;
#if LOG_INCLUDE_SOURCE_LOCATION
struct string msg_formatted = string_format(
scratch.arena,
LIT("[%F:%F:%F.%F] |%F| [%F] <%F:%F> %F"),
/* Time */
FMT_UINT_Z(datetime.hour, 2),
FMT_UINT_Z(datetime.minute, 2),
FMT_UINT_Z(datetime.second, 2),
FMT_UINT_Z(datetime.milliseconds, 3),
/* TID */
FMT_UINT_Z(tid, 5),
/* Level */
FMT_STR(shorthand),
/* Source location */
FMT_STR(file),
FMT_SINT(line),
/* Message */
FMT_STR(msg)
);
#else
struct string msg_formatted = string_format(
scratch.arena,
LIT("[%F:%F:%F.%F] |%F| [%F] %F"),
/* Time */
FMT_UINT_Z(datetime.hour, 2),
FMT_UINT_Z(datetime.minute, 2),
FMT_UINT_Z(datetime.second, 2),
FMT_UINT_Z(datetime.milliseconds, 3),
/* TID */
FMT_UINT_Z(tid, 5),
/* Level */
FMT_STR(shorthand),
/* Message */
FMT_STR(msg)
);
#endif
__profmsg((char *)msg.text, msg.len, settings.color);
append_to_logfile(msg_formatted);
/* Run callbacks */
struct log_event event = ZI;
event.level = level;
event.msg = msg;
event.datetime = datetime;
event.time_ns = time_ns;
#if LOG_INCLUDE_SOURCE_LOCATION
event.file = file;
event.line = line;
#endif
{
struct snc_lock lock = snc_lock_s(&G.callbacks_mutex);
for (struct log_event_callback *callback = G.first_callback; callback; callback = callback->next) {
if (level <= callback->level) {
__profn("Run log callback");
callback->func(event);
}
}
snc_unlock(&lock);
}
scratch_end(scratch);
}
#if LOG_INCLUDE_SOURCE_LOCATION
void _logfv(i32 level, struct string file, u32 line, struct string fmt, va_list args)
#else
void _logfv(i32 level, struct string fmt, va_list args)
#endif
{
if (!atomic_i32_fetch(&G.initialized)) { return; }
struct arena_temp scratch = scratch_begin_no_conflict();
struct string msg = string_formatv(scratch.arena, fmt, args);
#if LOG_INCLUDE_SOURCE_LOCATION
_log(level, file, line, msg);
#else
_log(level, msg);
#endif
scratch_end(scratch);
}
#if LOG_INCLUDE_SOURCE_LOCATION
void _logf(i32 level, struct string file, u32 line, struct string fmt, ...)
#else
void _logf(i32 level, struct string fmt, ...)
#endif
{
if (!atomic_i32_fetch(&G.initialized)) { return; }
va_list args;
va_start(args, fmt);
#if LOG_INCLUDE_SOURCE_LOCATION
_logfv(level, file, line, fmt, args);
#else
_logfv(level, fmt, args);
#endif
va_end(args);
}