package log import "base:runtime" import "core:fmt" //These are defined in package `base:runtime` as they are used in the `context`. This is to prevent an import definition cycle. /* Logger_Level :: enum { Debug = 0, Info = 10, Warning = 20, Error = 30, Fatal = 40, } */ Level :: runtime.Logger_Level /* Specifies additional data present in the log output. Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle. Option :: enum { // The log level, e.g. "[DEBUG] ---" Level, // The date, e.g. [2025-01-02] Date, // The time, e.g. [12:34:56] Time, // Just the filename, e.g. [main.odin] Short_File_Path, // Full file path, e.g. [/tmp/project/main.odin] Long_File_Path, // File line of the log statement, e.g. [8] Line, // Calling procedure, e.g. [main()] Procedure, // Enables colored output Terminal_Color } */ Option :: runtime.Logger_Option /* Specifies additional data present in the log output. Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle. Options :: bit_set[Option]; */ Options :: runtime.Logger_Options /* A preset option set for a logger. When you use this set of options you can expect the following output: [YYYY-MM-DD HH:MM:SS] Message For example: [2025-01-02 12:34:56] Hello World! */ Full_Timestamp_Opts :: Options{ .Date, .Time, } /* A preset option set for a logger. When you use this set of options you can expect the following output: [file.odin:L:proc()] Message For example: [main.odin:8:main()] Hello World! */ Location_Header_Opts :: Options{ .Short_File_Path, .Long_File_Path, .Line, .Procedure, } /* A preset option set for a logger. When you use this set of options you can expect the following output: [file.odin] Message For example: [main.odin] Hello World! */ Location_File_Opts :: Options{ .Short_File_Path, .Long_File_Path, } /* Implementation of the logger. Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle. Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location); */ Logger_Proc :: runtime.Logger_Proc /* Data backing the logger. Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle. Logger :: struct { // Implementation procedure: Logger_Proc, // Configuration data passed to the implementation data: rawptr, // Minimum level for messages passed to the implementation lowest_level: Level, // Additional data present in the log output options: Logger_Options, } */ Logger :: runtime.Logger /* Do nothing. Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle. */ nil_logger_proc :: runtime.default_logger_proc /* Create a logger that does nothing. Returns: - A logger that does nothing */ nil_logger :: proc() -> Logger { return Logger{nil_logger_proc, nil, Level.Debug, nil} } /* Log a formatted message at the `Debug` level. Inputs: - `fmt_str`: A format string, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `location`: Location of the caller (default is #caller_location) */ debugf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Debug, fmt_str, ..args, location=location) } /* Log a formatted message at the `Info` level. Inputs: - `fmt_str`: A format string, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `location`: Location of the caller (default is #caller_location) */ infof :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Info, fmt_str, ..args, location=location) } /* Log a formatted message at the `Warn` level. Inputs: - `fmt_str`: A format string, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `location`: Location of the caller (default is #caller_location) */ warnf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Warning, fmt_str, ..args, location=location) } /* Log a formatted message at the `Error` level. Inputs: - `fmt_str`: A format string, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `location`: Location of the caller (default is #caller_location) */ errorf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Error, fmt_str, ..args, location=location) } /* Log a formatted message at the `Fatal` level. Inputs: - `fmt_str`: A format string, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `location`: Location of the caller (default is #caller_location) */ fatalf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Fatal, fmt_str, ..args, location=location) } /* Log a message at the `Debug` level. Inputs: - `args`: values to be concatenated into the output - `sep`: separator to use when concatenating (default is `" "`) - `location`: Location of the caller (default is #caller_location) */ debug :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Debug, ..args, sep=sep, location=location) } /* Log a message at the `Info` level. Inputs: - `args`: values to be concatenated into the output - `sep`: separator to use when concatenating (default is `" "`) - `location`: Location of the caller (default is #caller_location) */ info :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Info, ..args, sep=sep, location=location) } /* Log a message at the `Warn` level. Inputs: - `args`: values to be concatenated into the output - `sep`: separator to use when concatenating (default is `" "`) - `location`: Location of the caller (default is #caller_location) */ warn :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Warning, ..args, sep=sep, location=location) } /* Log a message at the `Error` level. Inputs: - `args`: values to be concatenated into the output - `sep`: separator to use when concatenating (default is `" "`) - `location`: Location of the caller (default is #caller_location) */ error :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Error, ..args, sep=sep, location=location) } /* Log a message at the `Fatal` level. Inputs: - `args`: values to be concatenated into the output - `sep`: separator to use when concatenating (default is `" "`) - `location`: Location of the caller (default is #caller_location) */ fatal :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Fatal, ..args, sep=sep, location=location) } /* Log a message at the `Fatal` level and abort the program. Inputs: - `args`: values to be concatenated into the output - `location`: Location of the caller (default is #caller_location) */ panic :: proc(args: ..any, location := #caller_location) -> ! { log(.Fatal, ..args, location=location) runtime.panic("log.panic", location) } /* Log a formatted message at the `Fatal` level and abort the program. Inputs: - `fmt_str`: A format string, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `location`: Location of the caller (default is #caller_location) */ panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! { logf(.Fatal, fmt_str, ..args, location=location) runtime.panic("log.panicf", location) } /* When condition is `false` log a message at the `Fatal` level and abort the program. Can be disabled using `ODIN_DISABLE_ASSERT`. Inputs: - `condition`: A boolean to check - `message`: Message to log when condition is false (a default is provided) - `loc`: Location of the caller (default is #caller_location) */ @(disabled=ODIN_DISABLE_ASSERT) assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { @(cold) internal :: proc(message: string, loc: runtime.Source_Code_Location) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc } log(.Fatal, message, location=loc) p("runtime assertion", message, loc) } internal(message, loc) } } /* When condition is `false` log a formatted message at the `Fatal` level and abort the program. Can be disabled using `ODIN_DISABLE_ASSERT`. Inputs: - `condition`: A boolean to check - `fmt_str`: A format string to use when condition is false, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `loc`: Location of the caller (default is #caller_location) */ @(disabled=ODIN_DISABLE_ASSERT) assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) { if !condition { // NOTE(dragos): We are using the same trick as in builtin.assert // to improve performance to make the CPU not // execute speculatively, making it about an order of // magnitude faster @(cold) internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc } message := fmt.tprintf(fmt_str, ..args) log(.Fatal, message, location=loc) p("runtime assertion", message, loc) } internal(loc, fmt_str, ..args) } } /* When condition is `false` log a message at the `Fatal` level and abort the program. Unlike `assert` this procedure cannot be disabled with `ODIN_DISABLE_ASSERT` and will always execute. Inputs: - `condition`: A boolean to check - `message`: Message to log when condition is false (a default is provided) - `loc`: Location of the caller (default is #caller_location) */ ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { @(cold) internal :: proc(message: string, loc: runtime.Source_Code_Location) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc } log(.Fatal, message, location=loc) p("unsatisfied ensure", message, loc) } internal(message, loc) } } /* When condition is `false` log a formatted message at the `Fatal` level and abort the program. Unlike `assertf` this procedure cannot be disabled with `ODIN_DISABLE_ASSERT` and will always execute. Inputs: - `condition`: A boolean to check - `fmt_str`: A format string to use when condition is false, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `loc`: Location of the caller (default is #caller_location) */ ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) { if !condition { @(cold) internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc } message := fmt.tprintf(fmt_str, ..args) log(.Fatal, message, location=loc) p("unsatisfied ensure", message, loc) } internal(loc, fmt_str, ..args) } } /* Log a message at the desired level. Inputs: - `level`: The level of the message - `args`: values to be concatenated into the output - `sep`: separator to use when concatenating (default is `" "`) - `location`: Location of the caller (default is #caller_location) */ log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) { logger := context.logger if logger.procedure == nil || logger.procedure == nil_logger_proc { return } if level < logger.lowest_level { return } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() str := fmt.tprint(..args, sep=sep) logger.procedure(logger.data, level, str, logger.options, location) } /* Log a formatted message at the desired level. Inputs: - `level`: The level of the message - `fmt_str`: A format string, e.g. `"a: %v, b: %v" - `args`: Arguments for the format string - `location`: Location of the caller (default is #caller_location) */ logf :: proc(level: Level, fmt_str: string, args: ..any, location := #caller_location) { logger := context.logger if logger.procedure == nil || logger.procedure == nil_logger_proc { return } if level < logger.lowest_level { return } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() str := fmt.tprintf(fmt_str, ..args) logger.procedure(logger.data, level, str, logger.options, location) }