#include "log.h" #include "scratch.h" #include "string.h" #include "atomic.h" struct log_event_callback { log_event_callback_func *func; i32 level; struct log_event_callback *next; }; /* ========================== * * Global state * ========================== */ GLOBAL struct { struct atomic_i32 initialized; struct sys_mutex mutex; struct arena arena; log_event_callback_func *callbacks_head; 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] = { STR_NOCAST("CRITICAL"), 0xFFFF00FF }, [LOG_LEVEL_ERROR] = { STR_NOCAST("ERROR"), 0xFFFF0000 }, [LOG_LEVEL_WARNING] = { STR_NOCAST("WARNING"), 0xFFFFFF00 }, [LOG_LEVEL_INFO] = { STR_NOCAST("INFO"), 0xFFFFFFFF }, [LOG_LEVEL_DEBUG] = { STR_NOCAST("DEBUG"), 0xFF30D5C8 } }; /* ========================== * * Startup * ========================== */ struct log_startup_receipt log_startup(struct string logfile_path) { G.mutex = sys_mutex_alloc(); G.arena = arena_alloc(GIGABYTE(64)); 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_eval_exchange(&G.initialized, 1); return (struct log_startup_receipt) { 0 }; } /* ========================== * * Callback * ========================== */ void log_register_callback(log_event_callback_func *func) { /* TODO */ (UNUSED)func; #if 0 if (!atomic_i32_eval(&G.initialized)) { return; } struct sys_lock lock = sys_mutex_lock_e(&G.mutex); sys_mutex_unlock(&lock); #endif } /* ========================== * * Log * ========================== */ INTERNAL void append_to_logfile(struct string msg) { __prof; if (!atomic_i32_eval(&G.initialized)) { return; } if (G.file_valid) { struct temp_arena scratch = scratch_begin_no_conflict(); struct string msg_line = string_cat(scratch.arena, msg, STR("\n")); sys_file_write(G.file, BUFFER_FROM_STRING(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_eval(&G.initialized)) { return; } if (G.file_valid) { sys_file_write(G.file, BUFFER_FROM_STRING(STR("******** PANICKING ********\n"))); sys_file_write(G.file, BUFFER_FROM_STRING(msg)); sys_file_write(G.file, BUFFER_FROM_STRING(STR("\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_eval(&G.initialized)) { return; } if (level < 0 || level >= LOG_LEVEL_COUNT) { sys_panic(STR("Invalid log level")); } struct temp_arena scratch = scratch_begin_no_conflict(); struct sys_datetime lt = sys_local_time(); 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, STR("%F:%F:%F |Thread %F| [%F] <%F:%F> %F"), /* Time */ FMT_UINT(lt.hour), FMT_UINT(lt.minute), FMT_UINT(lt.second), /* TID */ FMT_UINT(tid), /* 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, STR("%F:%F:%F |Thread %F| [%F] %F"), /* Time */ FMT_UINT(lt.hour), FMT_UINT(lt.minute), FMT_UINT(lt.second), /* TID */ FMT_UINT(tid), /* Level */ FMT_STR(shorthand), /* Message */ FMT_STR(msg) ); #endif __profmsg((char *)msg.text, msg.len, settings.color); append_to_logfile(msg_formatted); 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_eval(&G.initialized)) { return; } struct temp_arena 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_eval(&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); }