#include "log.h" #include "scratch.h" #include "string.h" #include "app.h" struct log_event_callback { log_event_callback_func *func; i32 level; struct log_event_callback *next; }; /* ========================== * * Global state * ========================== */ GLOBAL struct { struct sys_mutex mutex; struct arena arena; log_event_callback_func *callbacks_head; struct sys_file file; b32 file_valid; } L = { 0 }, DEBUG_LVAR(L_log); GLOBAL READONLY const 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 * ========================== */ void log_startup(struct string logfile_path) { L.mutex = sys_mutex_alloc(); L.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)) { L.file = sys_file_open_append(logfile_path); L.file_valid = true; } } } /* ========================== * * Callback * ========================== */ void log_register_callback(log_event_callback_func *func) { sys_mutex_lock(&L.mutex); (UNUSED)func; sys_mutex_unlock(&L.mutex); } /* ========================== * * Log * ========================== */ INTERNAL void append_to_logfile(struct string msg) { __prof; if (L.file_valid) { struct temp_arena scratch = scratch_begin_no_conflict(); struct string msg_line = string_cat(scratch.arena, msg, STR("\n")); sys_file_write(L.file, BUFFER_FROM_STRING(msg_line)); scratch_end(scratch); } } #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 (level < 0 || level >= LOG_LEVEL_COUNT) { sys_panic(STR("Invalid log level")); } struct temp_arena scratch = scratch_begin_no_conflict(); struct sys_local_time_info 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 { 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 { 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); }