Add CustomLogAppender to allow extensible log appenders.

This commit is contained in:
Jonathan Bernard 2025-05-19 18:05:06 -05:00
parent 0eb0d33573
commit e0dba8125c
2 changed files with 67 additions and 1 deletions

View File

@ -1,6 +1,6 @@
# Package # Package
version = "1.0.1" version = "1.1.0"
author = "Jonathan Bernard" author = "Jonathan Bernard"
description = "Wrapper around std/logging to provide namespaced logging." description = "Wrapper around std/logging to provide namespaced logging."
license = "MIT" license = "MIT"

View File

@ -52,6 +52,9 @@ type
## console. ## console.
useStderr*: bool useStderr*: bool
CustomLogAppender* = ref object of LogAppender
doLogMessage*: proc (msg: LogMessage) {.gcsafe.}
#[ #[
# TODO: need to think throudh thread-safe IO for file logging # TODO: need to think throudh thread-safe IO for file logging
FileLogAppender* = ref object of LogAppender FileLogAppender* = ref object of LogAppender
@ -75,6 +78,12 @@ method initThreadCopy*(cla: ConsoleLogAppender): LogAppender {.gcsafe.} =
useStderr: cla.useStdErr) useStderr: cla.useStdErr)
method initThreadCopy*(cla: CustomLogAppender): LogAppender {.gcsafe.} =
result = CustomLogAppender(
namespace: cla.namespace,
threshold: cla.threshold,
doLogMessage: cla.doLogMessage)
#[ #[
method initThreadCopy*(fla: FileLogAppender): LogAppender {.gcsafe.} = method initThreadCopy*(fla: FileLogAppender): LogAppender {.gcsafe.} =
result = FileLogAppender( result = FileLogAppender(
@ -165,6 +174,16 @@ func initConsoleLogAppender*(
useStderr: useStdErr) useStderr: useStdErr)
func initCustomLogAppender*(
namespace = "",
threshold = lvlAll,
doLogMessage: proc (msg: LogMessage) {.gcsafe.}): CustomLogAppender {.gcsafe.} =
result = CustomLogAppender(
namespace: namespace,
threshold: threshold,
doLogMessage: doLogMessage)
method appendLogMessage*(appender: LogAppender, msg: LogMessage): void {.base, gcsafe.} = method appendLogMessage*(appender: LogAppender, msg: LogMessage): void {.base, gcsafe.} =
raise newException(CatchableError, "missing concrete implementation") raise newException(CatchableError, "missing concrete implementation")
@ -181,6 +200,12 @@ method appendLogMessage*(cla: ConsoleLogAppender, msg: LogMessage): void {.gcsaf
stdout.flushFile() stdout.flushFile()
method appendLogMessage*(cla: CustomLogAppender, msg: LogMessage): void {.gcsafe.} =
if msg.level < cla.threshold: return
cla.doLogMessage(msg)
proc getLogger*( proc getLogger*(
ls: LogService, ls: LogService,
name: string, name: string,
@ -201,16 +226,57 @@ proc getLogger*(
else: return some(getLogger(ls.get, name, threshold)) else: return some(getLogger(ls.get, name, threshold))
proc setThreshold*(ls: LogService, name: string, threshold: Level) {.gcsafe.} =
## Set the logging threshold for a logger and reload the thread state. This
## will affect the logger's thread-local copy, so you don't need to call
## `reloadThreadState` to make the change effective for the current thread,
## but in a multi-threaded context other pre-existing threads will not see
## the change until they reload their state.
acquire(ls.lock)
var idx = -1
for i in 0 ..< ls.cfg.loggers.len:
if ls.cfg.loggers[i].name == name:
idx = i
break
if idx == -1:
ls.cfg.loggers.add(LoggerConfig(name: name, threshold: some(threshold)))
else:
ls.cfg.loggers[idx].threshold = some(threshold)
release(ls.lock)
reloadThreadState(ls)
proc addAppender*(ls: LogService, appender: LogAppender) {.gcsafe.} = proc addAppender*(ls: LogService, appender: LogAppender) {.gcsafe.} =
## Add a log appender to the log service. This will affect the logger's
## thread-local copy, so you don't need to call `reloadThreadState` to make
## the change effective for the current thread, but in a multi-threaded
## context other pre-existing threads will not see the change until they
## reload their state.
acquire(ls.lock) acquire(ls.lock)
ls.cfg.appenders.add(appender) ls.cfg.appenders.add(appender)
release(ls.lock) release(ls.lock)
reloadThreadState(ls)
proc clearAppenders*(ls: LogService) {.gcsafe.} =
## Clear all log appenders from the log service. This will affect the
## logger's thread-local copy, so you don't need to call `reloadThreadState`
## to make the change effective for the current thread, but in a multi-threaded
## context other pre-existing threads will not see the change until they
## reload their state.
acquire(ls.lock)
ls.cfg.appenders = @[]
release(ls.lock)
reloadThreadState(ls)
func `<`(a, b: LoggerConfig): bool = a.name < b.name func `<`(a, b: LoggerConfig): bool = a.name < b.name
func getEffectiveLevel(ts: ThreadState, name: string): Level {.gcsafe.} = func getEffectiveLevel(ts: ThreadState, name: string): Level {.gcsafe.} =
## Get the effective logging level for a logger. This is the most specific
## level that is set for the logger or any of its parents. The root logger
## is used as the default if no other level is set.
result = ts.cfg.rootLevel result = ts.cfg.rootLevel
var namespaces = toSeq(values(ts.loggers)) var namespaces = toSeq(values(ts.loggers))