diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | core/log/file_console_logger.odin | 121 | ||||
| -rw-r--r-- | core/log/log.odin | 64 |
3 files changed, 180 insertions, 8 deletions
diff --git a/.gitignore b/.gitignore index 0c3e8b65a..a56b4f1e2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ - +![Cc]ore/[Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot @@ -264,7 +264,6 @@ bin/ odin odin.dSYM - # shared collection shared/ diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin new file mode 100644 index 000000000..af748e2a6 --- /dev/null +++ b/core/log/file_console_logger.odin @@ -0,0 +1,121 @@ +package log + +import "core:fmt"; +import "core:os"; + +Level_Headers := []string{ + "[DEBUG] --- ", + "[INFO ] --- ", + "[WARN ] --- ", + "[ERROR] --- ", + "[FATAL] --- ", +}; + +Default_Console_Logger_Opts :: Options{ + Option.Level, + Option.Terminal_Color, + Option.Short_File_Path, + Option.Line, + Option.Procedure, +} | Full_Timestamp_Opts; + +Default_File_Logger_Opts :: Options{ + Option.Level, + Option.Short_File_Path, + Option.Line, + Option.Procedure, +} | Full_Timestamp_Opts; + + +File_Console_Logger_Data :: struct { + lowest_level: Level, + file_handle: os.Handle, +} + +file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> Logger { + data := new(File_Console_Logger_Data); + data.lowest_level = lowest; + data.file_handle = h; + return Logger{file_console_logger_proc, data, opt, ident}; +} + +console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger { + data := new(File_Console_Logger_Data); + data.lowest_level = lowest; + data.file_handle = os.INVALID_HANDLE; + return Logger{file_console_logger_proc, data, opt, ident}; +} + +file_console_logger_proc :: proc(logger_data: rawptr, level: Level, ident: string, text: string, options: Options, location := #caller_location) { + data := cast(^File_Console_Logger_Data)logger_data; + if level < data.lowest_level do return; + + h : os.Handle; + if(data.file_handle != os.INVALID_HANDLE) do h = data.file_handle; + else do h = level <= Level.Error ? os.stdout : os.stderr; + backing: [1024]byte; //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. + buf := fmt.string_buffer_from_slice(backing[:]); + + do_level_header(options, level, &buf); + + + /*if Full_Timestamp_Opts & options != nil { + time := os.get_current_system_time(); + if Option.Date in options do fmt.sbprintf(&buf, "%d-%d-%d ", time.year, time.month, time.day); + if Option.Time in options do fmt.sbprintf(&buf, "%d:%d:%d ", time.hour, time.minute, time.second); + } +*/ + do_location_header(options, &buf, location); + + if ident != "" do fmt.sbprintf(&buf, "[%s] ", ident); + //TODO(Hoej): When we have better atomics and such, make this thread-safe + fmt.fprintf(h, "%s %s\n", fmt.to_string(buf), text); +} + +do_level_header :: proc(opts : Options, level : Level, buf : ^fmt.String_Buffer) { + + RESET :: "\x1b[0m"; + RED :: "\x1b[31m"; + YELLOW :: "\x1b[33m"; + DARK_GREY :: "\x1b[90m"; + + col := RESET; + switch level { + case Level.Debug : col = DARK_GREY; + case Level.Info : col = RESET; + case Level.Warning : col = YELLOW; + case Level.Error, Level.Fatal : col = RED; + } + + if Option.Level in opts { + if Option.Terminal_Color in opts do fmt.sbprint(buf, col); + fmt.sbprint(buf, Level_Headers[level]); + if Option.Terminal_Color in opts do fmt.sbprint(buf, RESET); + } +} + +do_location_header :: proc(opts : Options, buf : ^fmt.String_Buffer, location := #caller_location) { + if Location_Header_Opts & opts != nil do fmt.sbprint(buf, "["); else do return; + + file := location.file_path; + if Option.Short_File_Path in opts { + when os.OS == "windows" do delimiter := '\\'; else do delimiter := '/'; + last := 0; + for r, i in location.file_path do if r == delimiter do last = i+1; + file = location.file_path[last:]; + } + + if Location_File_Opts & opts != nil do fmt.sbprint(buf, file); + + if Option.Procedure in opts { + if Location_File_Opts & opts != nil do fmt.sbprint(buf, "."); + fmt.sbprintf(buf, "%s()", location.procedure); + } + + if Option.Line in opts { + if Location_File_Opts & opts != nil || Option.Procedure in opts do fmt.sbprint(buf, ":"); + fmt.sbprint(buf, location.line); + } + + fmt.sbprint(buf, "] "); +}
\ No newline at end of file diff --git a/core/log/log.odin b/core/log/log.odin index fac2fbe5b..b72977789 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -1,5 +1,8 @@ package log +import "core:fmt"; +import "core:runtime"; + Level :: enum { Debug, Info, @@ -9,26 +12,75 @@ Level :: enum { } Option :: enum { - Level, - Time, - File, - Line, - Procedure, + Level, + Date, + Time, + Short_File_Path, + Long_File_Path, + Line, + Procedure, + Terminal_Color } + Options :: bit_set[Option]; +Full_Timestamp_Opts :: Options{ + Option.Date, + Option.Time +}; +Location_Header_Opts :: Options{ + Option.Short_File_Path, + Option.Long_File_Path, + Option.Line, + Option.Procedure, +}; +Location_File_Opts :: Options{ + Option.Short_File_Path, + Option.Long_File_Path +}; Logger_Proc :: #type proc(data: rawptr, level: Level, ident, text: string, options: Options, location := #caller_location); Logger :: struct { procedure: Logger_Proc, data: rawptr, + options: Options, + ident: string } +Multi_Logger_Data :: struct { + loggers : []Logger, +} + +multi_logger :: proc(logs: ..Logger) -> Logger { + data := new(Multi_Logger_Data); + data.loggers = make([]Logger, len(logs)); + for log, i in logs do data.loggers[i] = log; + return Logger{multi_logger_proc, data, nil, ""}; +} + +multi_logger_proc :: proc(logger_data: rawptr, level: Level, ident: string, text: string, + options: Options, location := #caller_location) { + data := cast(^Multi_Logger_Data)logger_data; + if data.loggers == nil || len(data.loggers) == 0 do return; + for log in data.loggers do log.procedure(log.data, level, log.ident, text, log.options, location); +} nil_logger_proc :: proc(data: rawptr, level: Level, ident, text: string, options: Options, location := #caller_location) { // Do nothing } nil_logger :: proc() -> Logger { - return Logger{nil_logger_proc, nil}; + return Logger{nil_logger_proc, nil, nil, ""}; } + +debug :: proc(fmt_str : string, args : ..any, location := #caller_location) do log(level=Level.Debug, fmt_str=fmt_str, args=args, location=location); +info :: proc(fmt_str : string, args : ..any, location := #caller_location) do log(level=Level.Info, fmt_str=fmt_str, args=args, location=location); +warn :: proc(fmt_str : string, args : ..any, location := #caller_location) do log(level=Level.Warning, fmt_str=fmt_str, args=args, location=location); +error :: proc(fmt_str : string, args : ..any, location := #caller_location) do log(level=Level.Error, fmt_str=fmt_str, args=args, location=location); +fatal :: proc(fmt_str : string, args : ..any, location := #caller_location) do log(level=Level.Fatal, fmt_str=fmt_str, args=args, location=location); + +log :: proc(level : Level, fmt_str : string, args : ..any, location := #caller_location) { + logger := context.logger; + str := fmt.tprintf(fmt_str, ..args); //NOTE(Hoej): While tprint isn't thread-safe, no logging is. + logger.procedure(logger.data, level, logger.ident, str, logger.options, location); +}
\ No newline at end of file |