Browse Source

Add a file-, console- and multi-logger

Mikkel Hjortshoej 6 years ago
parent
commit
12c810f85d
3 changed files with 180 additions and 8 deletions
  1. 1 2
      .gitignore
  2. 121 0
      core/log/file_console_logger.odin
  3. 58 6
      core/log/log.odin

+ 1 - 2
.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/
 

+ 121 - 0
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, "] ");
+}

+ 58 - 6
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);
+}