file_console_logger.odin 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package log
  2. import "core:fmt";
  3. import "core:strings";
  4. import "core:os";
  5. import "core:time";
  6. Level_Headers := []string{
  7. "[DEBUG] --- ",
  8. "[INFO ] --- ",
  9. "[WARN ] --- ",
  10. "[ERROR] --- ",
  11. "[FATAL] --- ",
  12. };
  13. Default_Console_Logger_Opts :: Options{
  14. Option.Level,
  15. Option.Terminal_Color,
  16. Option.Short_File_Path,
  17. Option.Line,
  18. Option.Procedure,
  19. } | Full_Timestamp_Opts;
  20. Default_File_Logger_Opts :: Options{
  21. Option.Level,
  22. Option.Short_File_Path,
  23. Option.Line,
  24. Option.Procedure,
  25. } | Full_Timestamp_Opts;
  26. File_Console_Logger_Data :: struct {
  27. lowest_level: Level,
  28. file_handle: os.Handle,
  29. ident : string,
  30. }
  31. create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> Logger {
  32. data := new(File_Console_Logger_Data);
  33. data.lowest_level = lowest;
  34. data.file_handle = h;
  35. data.ident = ident;
  36. return Logger{file_console_logger_proc, data, opt};
  37. }
  38. destroy_file_logger ::proc(log : ^Logger) {
  39. data := cast(^File_Console_Logger_Data)log.data;
  40. if data.file_handle != os.INVALID_HANDLE do os.close(data.file_handle);
  41. free(data);
  42. log^ = nil_logger();
  43. }
  44. create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger {
  45. data := new(File_Console_Logger_Data);
  46. data.lowest_level = lowest;
  47. data.file_handle = os.INVALID_HANDLE;
  48. data.ident = ident;
  49. return Logger{file_console_logger_proc, data, opt};
  50. }
  51. destroy_console_logger ::proc(log : ^Logger) {
  52. free(log.data);
  53. log^ = nil_logger();
  54. }
  55. file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
  56. data := cast(^File_Console_Logger_Data)logger_data;
  57. if level < data.lowest_level do return;
  58. h : os.Handle;
  59. if(data.file_handle != os.INVALID_HANDLE) do h = data.file_handle;
  60. else do h = level <= Level.Error ? context.stdout : context.stderr;
  61. backing: [1024]byte; //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
  62. buf := strings.builder_from_slice(backing[:]);
  63. do_level_header(options, level, &buf);
  64. when time.IS_SUPPORTED {
  65. if Full_Timestamp_Opts & options != nil {
  66. fmt.sbprint(&buf, "[");
  67. t := time.now();
  68. y, m, d := time.date(t);
  69. h, min, s := time.clock(t);
  70. if Option.Date in options do fmt.sbprintf(&buf, "%d-%02d-%02d ", y, m, d);
  71. if Option.Time in options do fmt.sbprintf(&buf, "%02d:%02d:%02d", h, min, s);
  72. fmt.sbprint(&buf, "] ");
  73. }
  74. }
  75. do_location_header(options, &buf, location);
  76. if data.ident != "" do fmt.sbprintf(&buf, "[%s] ", data.ident);
  77. //TODO(Hoej): When we have better atomics and such, make this thread-safe
  78. fmt.fprintf(h, "%s %s\n", strings.to_string(buf), text);
  79. }
  80. do_level_header :: proc(opts : Options, level : Level, str : ^strings.Builder) {
  81. RESET :: "\x1b[0m";
  82. RED :: "\x1b[31m";
  83. YELLOW :: "\x1b[33m";
  84. DARK_GREY :: "\x1b[90m";
  85. col := RESET;
  86. switch level {
  87. case Level.Debug : col = DARK_GREY;
  88. case Level.Info : col = RESET;
  89. case Level.Warning : col = YELLOW;
  90. case Level.Error, Level.Fatal : col = RED;
  91. }
  92. if Option.Level in opts {
  93. if Option.Terminal_Color in opts do fmt.sbprint(str, col);
  94. fmt.sbprint(str, Level_Headers[level]);
  95. if Option.Terminal_Color in opts do fmt.sbprint(str, RESET);
  96. }
  97. }
  98. do_location_header :: proc(opts : Options, buf : ^strings.Builder, location := #caller_location) {
  99. if Location_Header_Opts & opts != nil do fmt.sbprint(buf, "["); else do return;
  100. file := location.file_path;
  101. if Option.Short_File_Path in opts {
  102. when os.OS == "windows" do delimiter := '\\'; else do delimiter := '/';
  103. last := 0;
  104. for r, i in location.file_path do if r == delimiter do last = i+1;
  105. file = location.file_path[last:];
  106. }
  107. if Location_File_Opts & opts != nil do fmt.sbprint(buf, file);
  108. if Option.Procedure in opts {
  109. if Location_File_Opts & opts != nil do fmt.sbprint(buf, ".");
  110. fmt.sbprintf(buf, "%s()", location.procedure);
  111. }
  112. if Option.Line in opts {
  113. if Location_File_Opts & opts != nil || Option.Procedure in opts do fmt.sbprint(buf, ":");
  114. fmt.sbprint(buf, location.line);
  115. }
  116. fmt.sbprint(buf, "] ");
  117. }