mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-10 18:09:32 +00:00
Integrate docs from PR #5119
This commit is contained in:
68
core/log/doc.odin
Normal file
68
core/log/doc.odin
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Implementation of logging facilities.
|
||||
|
||||
Odin has builtin support for logging using procedure `context`. After a logger is created it can then be assigned to
|
||||
`context.logger` and used implicitly in future log calls.
|
||||
|
||||
While it is ok for simple apps to use the `core:fmt` package, libraries and complex apps should prefer the `core:log`
|
||||
package. By using the implicit logger library and application authors allow the caller to decide how to process log
|
||||
messages.
|
||||
|
||||
When starting out you can easily just init the logger with a single line.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
log.info("Hello World!")
|
||||
}
|
||||
|
||||
However when the application gets more involved you might want to try a more complex setup.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
handle, err := os.open("logs.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0o666)
|
||||
assert(err == nil, "Cannot open log file")
|
||||
|
||||
file_logger := log.create_file_logger(handle)
|
||||
// This closes the file handle
|
||||
defer log.destroy_file_logger(file_logger)
|
||||
|
||||
console_logger := log.create_console_logger()
|
||||
defer log.destroy_console_logger(console_logger)
|
||||
|
||||
multi_logger := log.create_multi_logger(console_logger, file_logger)
|
||||
defer log.destroy_multi_logger(multi_logger)
|
||||
|
||||
context.logger = multi_logger
|
||||
|
||||
log.info("Application started!")
|
||||
}
|
||||
|
||||
It is also possible to create an allocator that logs all allocations.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
|
||||
alloc: log.Log_Allocator
|
||||
log.log_allocator_init(&alloc, .Debug)
|
||||
context.allocator = log.log_allocator(&alloc)
|
||||
|
||||
a := new(i32)
|
||||
free(a)
|
||||
}
|
||||
*/
|
||||
package log
|
||||
@@ -11,6 +11,7 @@ import "core:terminal"
|
||||
import "core:terminal/ansi"
|
||||
import "core:time"
|
||||
|
||||
// Strings to output when `.Level` is included in the logger options.
|
||||
Level_Headers := [?]string{
|
||||
0..<10 = "[DEBUG] --- ",
|
||||
10..<20 = "[INFO ] --- ",
|
||||
@@ -19,22 +20,49 @@ Level_Headers := [?]string{
|
||||
40..<50 = "[FATAL] --- ",
|
||||
}
|
||||
|
||||
/*
|
||||
The default option set for a console logger.
|
||||
|
||||
It is similar to the file logger default option set, but the output includes colors.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[LEVEL] --- [YYYY-MM-DD HH:MM:SS] [file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[INFO ] --- [2025-01-02 12:34:56] [main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Default_Console_Logger_Opts :: Options{
|
||||
.Level,
|
||||
.Terminal_Color,
|
||||
.Short_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
} | Full_Timestamp_Opts
|
||||
} + Full_Timestamp_Opts
|
||||
|
||||
/*
|
||||
The default option set for a file logger.
|
||||
|
||||
It is similar to the console logger default option set, but the output is not colored.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[LEVEL] --- [YYYY-MM-DD HH:MM:SS] [file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[INFO ] --- [2025-01-02 12:34:56] [main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Default_File_Logger_Opts :: Options{
|
||||
.Level,
|
||||
.Short_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
} | Full_Timestamp_Opts
|
||||
} + Full_Timestamp_Opts
|
||||
|
||||
|
||||
//Data backing a file or console logger.
|
||||
File_Console_Logger_Data :: struct {
|
||||
file_handle: ^os.File,
|
||||
ident: string,
|
||||
@@ -67,6 +95,21 @@ init_standard_stream_status :: proc "contextless" () {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Create a logger that outputs to a file.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_file_logger`.
|
||||
|
||||
Inputs:
|
||||
- `h`: A handle to the output file
|
||||
- `lowest`: Log level to use (default is `.Debug`)
|
||||
- `opt`: Specifies additional data present in the log output (default is `log.Default_File_Logger_Opts`)
|
||||
- `ident`: Identifier to include in the output (default is `""`)
|
||||
- `allocator`: Allocator to use for data backing the logger (default is `context.allocator`)
|
||||
*/
|
||||
create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = f
|
||||
@@ -74,6 +117,13 @@ create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_Fi
|
||||
return Logger{file_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_file_logger` and close the file handle.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_file_logger`
|
||||
- `allocator`: Allocator passed to `create_file_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
data := cast(^File_Console_Logger_Data)log.data
|
||||
if data.file_handle != nil {
|
||||
@@ -82,6 +132,19 @@ destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
free(data, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
Create a logger that outputs to the terminal.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_console_logger`.
|
||||
|
||||
Inputs:
|
||||
- `lowest`: Log level to use (default is `.Debug`)
|
||||
- `opt`: Specifies additional data present in the log output (default is `log.Default_Console_Logger_Opts`)
|
||||
- `ident`: Identifier to include in the output (default is `""`)
|
||||
- `allocator`: Allocator to use for data backing the logger (default is `context.allocator`)
|
||||
*/
|
||||
create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = nil
|
||||
@@ -89,6 +152,13 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
|
||||
return Logger{console_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_console_logger`.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_console_logger`
|
||||
- `allocator`: Allocator passed to `create_console_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
free(log.data, allocator)
|
||||
}
|
||||
@@ -117,6 +187,7 @@ _file_console_logger_proc :: proc(h: ^os.File, ident: string, level: Level, text
|
||||
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
|
||||
}
|
||||
|
||||
|
||||
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
|
||||
data := cast(^File_Console_Logger_Data)logger_data
|
||||
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
|
||||
@@ -136,6 +207,7 @@ console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, opt
|
||||
_file_console_logger_proc(h, data.ident, level, text, options, location)
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the log level.
|
||||
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
|
||||
|
||||
RESET :: ansi.CSI + ansi.RESET + ansi.SGR
|
||||
@@ -162,6 +234,7 @@ do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the data and time.
|
||||
do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
|
||||
when time.IS_SUPPORTED {
|
||||
if Full_Timestamp_Opts & opts != nil {
|
||||
@@ -180,6 +253,7 @@ do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the file location.
|
||||
do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) {
|
||||
if Location_Header_Opts & opts == nil {
|
||||
return
|
||||
|
||||
@@ -1,52 +1,102 @@
|
||||
// Implementations of the `context.Logger` interface.
|
||||
package log
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
|
||||
|
||||
// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle.
|
||||
//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,
|
||||
}
|
||||
Logger_Level :: enum {
|
||||
Debug = 0,
|
||||
Info = 10,
|
||||
Warning = 20,
|
||||
Error = 30,
|
||||
Fatal = 40,
|
||||
}
|
||||
*/
|
||||
Level :: runtime.Logger_Level
|
||||
|
||||
/*
|
||||
Option :: enum {
|
||||
Level,
|
||||
Date,
|
||||
Time,
|
||||
Short_File_Path,
|
||||
Long_File_Path,
|
||||
Line,
|
||||
Procedure,
|
||||
Terminal_Color
|
||||
}
|
||||
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
|
||||
|
||||
/*
|
||||
Options :: bit_set[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,
|
||||
@@ -54,67 +104,206 @@ Location_File_Opts :: Options{
|
||||
|
||||
|
||||
/*
|
||||
Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location);
|
||||
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
|
||||
|
||||
/*
|
||||
Logger :: struct {
|
||||
procedure: Logger_Proc,
|
||||
data: rawptr,
|
||||
lowest_level: Level,
|
||||
options: Logger_Options,
|
||||
}
|
||||
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 {
|
||||
@@ -131,6 +320,17 @@ assert :: proc(condition: bool, message := #caller_expression(condition), 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 {
|
||||
@@ -152,6 +352,16 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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)
|
||||
@@ -167,6 +377,17 @@ ensure :: proc(condition: bool, message := #caller_expression(condition), 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)
|
||||
@@ -184,7 +405,15 @@ ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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 {
|
||||
@@ -198,6 +427,15 @@ log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location)
|
||||
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 {
|
||||
|
||||
@@ -6,6 +6,7 @@ import "base:runtime"
|
||||
import "core:sync"
|
||||
|
||||
// Format to use when logging allocations.
|
||||
Log_Allocator_Format :: enum {
|
||||
Bytes, // Actual number of bytes.
|
||||
Human, // Bytes in human units like bytes, kibibytes, etc. as appropriate.
|
||||
}
|
||||
@@ -15,13 +16,23 @@ Log_Allocator_Format :: enum {
|
||||
// The format can be changed by setting the `size_fmt: Log_Allocator_Format` field to either `Bytes` or `Human`.
|
||||
Log_Allocator :: struct {
|
||||
allocator: runtime.Allocator, // Wrapped allocator
|
||||
level: Level,
|
||||
prefix: string,
|
||||
lock: sync.Mutex,
|
||||
level: Level, // Log Level used for allocations
|
||||
prefix: string, // Prefix to use in log messages
|
||||
lock: sync.Mutex,
|
||||
size_fmt: Log_Allocator_Format, // Format to use when logging allocations
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the backing data for the allocator that logs all allocations.
|
||||
|
||||
Inputs:
|
||||
- `la`: Pointer to the data structure to initialize
|
||||
- `level`: Log level to use for allocations
|
||||
- `size_fmt`: Format to use when logging allocations (default is `.Bytes`)
|
||||
- `allocator`: Wrapped allocator (default is `context.allocator`)
|
||||
- `prefix`: Prefix to use in log messages (default is `""`)
|
||||
*/
|
||||
log_allocator_init :: proc(la: ^Log_Allocator, level: Level, size_fmt := Log_Allocator_Format.Bytes,
|
||||
allocator := context.allocator, prefix := "") {
|
||||
la.allocator = allocator
|
||||
la.level = level
|
||||
@@ -31,7 +42,15 @@ log_allocator_init :: proc(la: ^Log_Allocator, level: Level, size_fmt := Log_All
|
||||
}
|
||||
|
||||
/*
|
||||
Create an allocator that logs all allocations.
|
||||
|
||||
Inputs:
|
||||
- `la`: Pointer to the data structure backing the allocator
|
||||
|
||||
Returns:
|
||||
- An allocator that logs all allocations
|
||||
*/
|
||||
log_allocator :: proc(la: ^Log_Allocator) -> runtime.Allocator {
|
||||
return runtime.Allocator{
|
||||
procedure = log_allocator_proc,
|
||||
data = la,
|
||||
@@ -39,6 +58,7 @@ log_allocator :: proc(la: ^Log_Allocator) -> runtime.Allocator {
|
||||
}
|
||||
|
||||
// Backing procedure for allocator that logs all allocations.
|
||||
log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) {
|
||||
la := (^Log_Allocator)(allocator_data)
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
package log
|
||||
|
||||
|
||||
// A container backing for multiple loggers.
|
||||
Multi_Logger_Data :: struct {
|
||||
loggers: []Logger,
|
||||
}
|
||||
|
||||
/*
|
||||
Create a logger that logs to all backing loggers.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_multi_logger`.
|
||||
|
||||
Note: Logs using a multi logger take both the multi logger and the backing loggers' log levels into account.
|
||||
|
||||
Inputs:
|
||||
- `logs` - Backing loggers passed as multiple arguments
|
||||
- `allocator` - An allocator used to allocate data to store backing loggers (default is `context.allocator`)
|
||||
|
||||
Returns:
|
||||
- A multi logger
|
||||
*/
|
||||
create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> Logger {
|
||||
data := new(Multi_Logger_Data, allocator)
|
||||
data.loggers = make([]Logger, len(logs), allocator)
|
||||
@@ -12,12 +29,20 @@ create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> L
|
||||
return Logger{multi_logger_proc, data, Level.Debug, nil}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_multi_logger`.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_multi_logger`
|
||||
- `allocator`: Allocator passed to `create_multi_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_multi_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
data := (^Multi_Logger_Data)(log.data)
|
||||
delete(data.loggers, allocator)
|
||||
free(data, allocator)
|
||||
}
|
||||
|
||||
// Backing procedure for the multi logger.
|
||||
multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string,
|
||||
options: Options, location := #caller_location) {
|
||||
data := cast(^Multi_Logger_Data)logger_data
|
||||
|
||||
Reference in New Issue
Block a user