Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core:log improvements #4463

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 143 additions & 21 deletions core/log/file_console_logger.odin
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import "core:encoding/ansi"
import "core:fmt"
import "core:strings"
import "core:os"
import "core:os/os2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My issue with importing os2 here is that it doesn't support everything yet. You might have to split file_console_logger.odin into two files. One for the platforms that support os2, and one for everything else.

import "core:time"

import "base:runtime"

Level_Headers := [?]string{
0..<10 = "[DEBUG] --- ",
10..<20 = "[INFO ] --- ",
Expand All @@ -31,44 +34,158 @@ Default_File_Logger_Opts :: Options{
.Procedure,
} | Full_Timestamp_Opts

@private
File_Type :: enum {
console,
os,
os2,
}

/*
Implements a logger that writes to a file stream, such as stdout, or a file on the system.
Lperlind marked this conversation as resolved.
Show resolved Hide resolved

This logger is compatible both with `os` and `os2`.
- To make a logger that writes to an `os.Handle` refer to `make_file_logger_os`
- To make a logger that writes to an `^os2.File` refer to `make_file_logger_os2`
- To make a logger that writes to `stdout` and `stderr` refer to `make_console_logger`
*/
File_Console_Logger_Data :: struct {
file_handle: os.Handle,
type: File_Type,
file_handle_os: os.Handle,
file_handle_os2: ^os2.File,
ident: string,
close_file_on_delete: bool,
allocator: runtime.Allocator,
}

create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> Logger {
data := new(File_Console_Logger_Data)
data.file_handle = h
/*
Makes a new logger that will write to the provided `os.Handle`
Lperlind marked this conversation as resolved.
Show resolved Hide resolved

*Allocates Using Provided Allocator*

Inputs:
- h: An os handle that the logger will write to
- lowest: The lowest level logging to accept
- opt: The wanted logging options
- ident: An identifier that will be written alongside the logged message
- close_file_on_delete: Sets the logger to close the handle when delete_file_logger is called if `true`
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: `#caller_location`)

Returns:
- res: The new file logger
- res: An allocator error if one occured, `nil` otherwise
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
*/
make_file_logger_os :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", close_file_on_delete := false, allocator := context.allocator, loc := #caller_location) -> (res: Logger, err: runtime.Allocator_Error) {
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
data := new(File_Console_Logger_Data, allocator, loc) or_return
data.type = .os
data.file_handle_os = h
data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt}
data.allocator = allocator
data.close_file_on_delete = close_file_on_delete
return Logger{file_console_logger_proc, data, lowest, opt}, nil
}

destroy_file_logger :: proc(log: Logger) {
data := cast(^File_Console_Logger_Data)log.data
if data.file_handle != os.INVALID_HANDLE {
os.close(data.file_handle)
}
free(data)
/*
Makes a new logger that will write to the provided `^os2.File`

*Allocates Using Provided Allocator*

Inputs:
- f: An os2 file that the logger will write to
- lowest: The lowest level logging to accept
- opt: The wanted logging options
- ident: An identifier that will be written alongside the logged message
- close_file_on_delete: Sets the logger to close the file when delete_file_logger is called if `true`
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: `#caller_location`)

Returns:
- res: The new file logger
- res: An allocator error if one occured, `nil` otherwise
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
*/
make_file_logger_os2 :: proc(f: ^os2.File, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", close_file_on_delete := false, allocator := context.allocator, loc := #caller_location) -> (res: Logger, err: runtime.Allocator_Error) {
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
data := new(File_Console_Logger_Data, allocator, loc) or_return
data.type = .os2
data.file_handle_os2 = f
data.ident = ident
data.allocator = allocator
data.close_file_on_delete = close_file_on_delete
return Logger{file_console_logger_proc, data, lowest, opt}, nil
}
make_file_logger :: proc {
make_file_logger_os,
make_file_logger_os2,
}

create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger {
data := new(File_Console_Logger_Data)
data.file_handle = os.INVALID_HANDLE
/*
Makes a new logger that will write to `stdout` and `stderr`. `Stdout` will be written to
if the log level is below `Level.Error`, otherwise `stderr` will be written to

*Allocates Using Provided Allocator*

Inputs:
- lowest: The lowest level logging to accept
- opt: The wanted logging options
- ident: An identifier that will be written alongside the logged message
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: `#caller_location`)

Returns:
- res: The new file logger
- res: An allocator error if one occured, `nil` otherwise
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
*/
make_console_logger :: proc(lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator, loc := #caller_location) -> (res: Logger, err: runtime.Allocator_Error) {
data := new(File_Console_Logger_Data, allocator, loc) or_return
data.type = .console
data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt}
data.allocator = allocator
return Logger{file_console_logger_proc, data, lowest, opt}, nil
}

@(deprecated = "Use make_file_logger instead")
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> (Logger) {
logger, _ := make_file_logger(h, lowest, opt, ident, true, context.allocator)
return logger
}

@(deprecated = "Use make_console_logger instead")
create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger {
logger, _ := make_file_logger(os.INVALID_HANDLE, lowest, opt, ident, true, context.allocator)
return logger
}

/*
Deletes a logger made with `make_console_logger` and `make_file_logger`.
If a logger was created with `make_file_logger` and `close_file_on_delete` was
set to true then the logger will try to close the file handle it was provided.

Inputs:
- log: The logger to delete
*/
delete_console_logger :: proc(log: Logger) {
data := cast(^File_Console_Logger_Data)log.data
if data.close_file_on_delete {
switch data.type {
case .console:
case .os: os.close(data.file_handle_os)
case .os2: os2.close(data.file_handle_os2)
}
}
free(data, data.allocator)
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
}
@(deprecated = "Use delete_console_logger instead")
destroy_console_logger :: proc(log: Logger) {
free(log.data)
delete_console_logger(log)
}
@(deprecated = "Use delete_file_logger instead")
destroy_file_logger :: proc(log: Logger) {
delete_file_logger(log)
}
delete_file_logger :: delete_console_logger

file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
data := cast(^File_Console_Logger_Data)logger_data
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
if data.file_handle != os.INVALID_HANDLE {
h = data.file_handle
}
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:])

Expand All @@ -89,8 +206,13 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
if data.ident != "" {
fmt.sbprintf(&buf, "[%s] ", data.ident)
}

//TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
switch data.type {
case .console: fmt.fprintf(level < Level.Error ? os.stdout : os.stderr, "%s%s\n", strings.to_string(buf), text)
case .os: fmt.fprintf(data.file_handle_os, "%s%s\n", strings.to_string(buf), text)
case .os2: fmt.wprintf(data.file_handle_os2.stream, "%s%s\n", strings.to_string(buf), text)
}
}

do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
Expand Down
43 changes: 37 additions & 6 deletions core/log/multi_logger.odin
Original file line number Diff line number Diff line change
@@ -1,22 +1,53 @@
package log

import "base:runtime"
import "core:slice"
import "core:mem"

Multi_Logger_Data :: struct {
loggers: []Logger,
allocator: runtime.Allocator,
}

@(deprecated = "Use make_multi_logger instead")
create_multi_logger :: proc(logs: ..Logger) -> Logger {
data := new(Multi_Logger_Data)
data.loggers = make([]Logger, len(logs))
logger, _ := make_multi_logger(..logs, allocator = context.allocator)
return logger
}

/*
Makes a new logger that will write to several other loggers.

*Allocates Using Provided Allocator*

Inputs:
- logs: The loggers this logger will write to
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: `#caller_location`)

Returns:
- res: The new multi logger
- res: An allocator error if one occured, `nil` otherwise
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
*/
make_multi_logger :: proc(logs: ..Logger, allocator := context.allocator, loc := #caller_location) -> (res: Logger, err: runtime.Allocator_Error) {
logger_size := mem.align_forward_int(size_of(Multi_Logger_Data), align_of(Logger))
content_size := len(mem.slice_to_bytes(logs))

data_bytes := make([]byte, logger_size + content_size, allocator, loc) or_return
data := cast(^Multi_Logger_Data)raw_data(data_bytes)
data.loggers = slice.reinterpret([]Logger, data_bytes[logger_size:])
assert(len(data.loggers) == len(logs))
copy(data.loggers, logs)
return Logger{multi_logger_proc, data, Level.Debug, nil}
data.allocator = allocator

return Logger{multi_logger_proc, data, Level.Debug, nil}, nil
}

destroy_multi_logger :: proc(log: Logger) {
delete_multi_logger :: proc(log: Logger) {
data := (^Multi_Logger_Data)(log.data)
delete(data.loggers)
free(data)
free(data, data.allocator)
Lperlind marked this conversation as resolved.
Show resolved Hide resolved
}
destroy_multi_logger :: delete_multi_logger

multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string,
options: Options, location := #caller_location) {
Expand Down
Loading