Files
SectrPrototype/code2/grime/logger.odin
2025-10-13 02:13:58 -04:00

134 lines
3.7 KiB
Odin

package grime
import core_log "core:log"
Max_Logger_Message_Width :: 160
LoggerEntry :: struct {
text : string,
timestamp : string,
level : string,
location : string,
}
Logger :: struct {
file_path : string,
file : OS_Handle,
id : string,
// varena : VArena,
// entries : Array(LoggerEntry),
}
to_odin_logger :: proc( logger : ^ Logger ) -> Odin_Logger {
return { logger_interface, logger, LoggerLevel.Debug, Default_File_Logger_Opts }
}
logger_init :: proc( logger : ^ Logger, id : string, file_path : string, file := OS_INVALID_HANDLE )
{
if file == OS_INVALID_HANDLE
{
logger_file, result_code := file_open( file_path, FS_Open_Readonly | FS_Open_Create )
assert( result_code == OS_ERROR_NONE, "Log failures are fatal and must never occur at runtime (there is no logging)" )
logger.file = logger_file
file_truncate( file_path, 0 )
}
else {
logger.file = file
}
logger.file_path = file_path
logger.id = id
LOGGER_VARENA_BASE_ADDRESS : uintptr = 2 * Tera
@static vmem_init_counter : uintptr = 0
context.logger = { logger_interface, logger, LoggerLevel.Debug, Default_File_Logger_Opts }
log_print("Initialized Logger")
when false {
log_print("This sentence is over 80 characters long on purpose to test the ability of this logger to properfly wrap long as logs with a new line and then at the end of that pad it with the appropraite signature.")
}
}
logger_interface :: proc(
logger_data : rawptr,
level : LoggerLevel,
text : string,
options : LoggerOptions,
location := #caller_location )
{
logger := cast(^ Logger) logger_data
@static builder_backing : [16 * Kilo] byte; {
mem_zero( cursor(builder_backing[:]), len(builder_backing) )
}
builder := strbuilder_from_bytes( builder_backing[:] )
first_line_length := len(text) > Max_Logger_Message_Width ? Max_Logger_Message_Width : len(text)
first_line := transmute(string) text[ 0 : first_line_length ]
str_pfmt_builder( & builder, "%s ", first_line )
// str_pfmt_builder( & builder, "%-s ", first_line )
// Signature
{
when TIME_IS_SUPPORTED
{
if Logger_Full_Timestamp_Opts & options != nil {
str_pfmt_builder( & builder, "[")
t := time_now()
year, month, day := time_date(t)
hour, minute, second := time_clock(t)
if .Date in options {
str_pfmt_builder( & builder, "%d-%02d-%02d ", year, month, day )
}
if .Time in options {
str_pfmt_builder( & builder, "%02d:%02d:%02d", hour, minute, second)
}
str_pfmt_builder( & builder, "] ")
}
}
core_log.do_level_header( options, & builder, level )
if logger.id != "" {
str_pfmt_builder( & builder, "[%s] ", logger.id )
}
core_log.do_location_header( options, & builder, location )
}
// Oversized message handling
if len(text) > Max_Logger_Message_Width
{
offset := Max_Logger_Message_Width
bytes := transmute( []u8 ) text
for left := len(bytes) - Max_Logger_Message_Width; left > 0; left -= Max_Logger_Message_Width
{
str_pfmt_builder( & builder, "\n" )
subset_length := len(text) - offset
if subset_length > Max_Logger_Message_Width {
subset_length = Max_Logger_Message_Width
}
subset := slice( cursor(bytes)[offset:], subset_length )
str_pfmt_builder( & builder, "%s", transmute(string) subset )
offset += Max_Logger_Message_Width
}
}
str_pfmt_file_ln( logger.file, to_string(builder) )
}
// Below are made on demand per-package.
// They should strict only use a scratch allocator...
log_print :: proc( msg : string, level := LoggerLevel.Info, loc := #caller_location ) {
core_log.log( level, msg, location = loc )
}
log_print_fmt :: proc( fmt : string, args : ..any, level := LoggerLevel.Info, loc := #caller_location ) {
core_log.logf( level, fmt, ..args, location = loc )
}