262 lines
6.4 KiB
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);
|
|
}
|