From e0dba8125c9a5aabfd47461235f69f24c6937242 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Mon, 19 May 2025 18:05:06 -0500 Subject: [PATCH] Add CustomLogAppender to allow extensible log appenders. --- namespaced_logging.nimble | 2 +- src/namespaced_logging.nim | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/namespaced_logging.nimble b/namespaced_logging.nimble index f6ca8a6..539c72e 100644 --- a/namespaced_logging.nimble +++ b/namespaced_logging.nimble @@ -1,6 +1,6 @@ # Package -version = "1.0.1" +version = "1.1.0" author = "Jonathan Bernard" description = "Wrapper around std/logging to provide namespaced logging." license = "MIT" diff --git a/src/namespaced_logging.nim b/src/namespaced_logging.nim index c429ac1..a04e57d 100644 --- a/src/namespaced_logging.nim +++ b/src/namespaced_logging.nim @@ -52,6 +52,9 @@ type ## console. useStderr*: bool + CustomLogAppender* = ref object of LogAppender + doLogMessage*: proc (msg: LogMessage) {.gcsafe.} + #[ # TODO: need to think throudh thread-safe IO for file logging FileLogAppender* = ref object of LogAppender @@ -75,6 +78,12 @@ method initThreadCopy*(cla: ConsoleLogAppender): LogAppender {.gcsafe.} = 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.} = result = FileLogAppender( @@ -165,6 +174,16 @@ func initConsoleLogAppender*( 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.} = raise newException(CatchableError, "missing concrete implementation") @@ -181,6 +200,12 @@ method appendLogMessage*(cla: ConsoleLogAppender, msg: LogMessage): void {.gcsaf stdout.flushFile() +method appendLogMessage*(cla: CustomLogAppender, msg: LogMessage): void {.gcsafe.} = + if msg.level < cla.threshold: return + + cla.doLogMessage(msg) + + proc getLogger*( ls: LogService, name: string, @@ -201,16 +226,57 @@ proc getLogger*( 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.} = + ## 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) ls.cfg.appenders.add(appender) 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 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 var namespaces = toSeq(values(ts.loggers))