#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 atomic32 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) { __prof; 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 = 1; } } atomic32_fetch_set(&G.initialized, 1); } /* ========================== * * Callback * ========================== */ void log_register_callback(log_event_callback_func *func, i32 level) { if (!atomic32_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 (!atomic32_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 (!atomic32_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 (!atomic32_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_current_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 (!atomic32_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 (!atomic32_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); }