I want to setup a logger which can be safely accessed by other threads. Help is appreciated.
One thing that you can do is wrap an existing logger inside another logger (a sync logger) that would synchronize its logger and reroute all the log procedure calls into the child logger. This should help synchronize multiple buffer flushes that are done under the hood in packages os
and fmt
.
sync_logger :: proc(l: log.Logger) -> log.Logger {
Sync_Logger_Data :: struct {
wrapped_logger: log.Logger,
mutex: sync.Mutex,
}
sync_logger_proc :: proc(data: rawptr, level: log.Level, text: string, options: log.Options, location := #caller_location) {
data := cast(^Sync_Logger_Data) data
sync.mutex_lock(&data.mutex)
data.wrapped_logger.procedure(data.wrapped_logger.data, level, text, options, location)
sync.mutex_unlock(&data.mutex)
}
logger_data := new(Sync_Logger_Data, context.allocator)
logger_data.wrapped_logger = context.logger
logger_data.mutex = {}
return log.Logger {
data = logger_data,
lowest_level = logger_data.wrapped_logger.lowest_level,
options = logger_data.wrapped_logger.options,
procedure = sync_logger_proc,
}
}
However the downside of that approach is that you have to make sure you don’t call logger multiple times to get multiple log lines on one thread. Or at least, when you do, you do not expect these lines to be in-order. If this is a concern, I suggest using a recursive mutex instead of a plain mutex and add special procedures when a log needs to be synchronized across multiple messages.
log_sync_begin :: proc() {
// You can make that mutex global or
// get it from context.logger.data
sync.mutex_lock(g_log_mutex)
}
log_sync_end :: proc() {
// Same as above
sync.mutex_unlock(g_log_mutex)
}
1 Like