StdLoggingAppender - forward logs to std/logging

Primarily intended for use in libraries or other contexts where you want
to fall back to std/logging if the application is not using or hasn't
configured namespaced_logging.

By default the StdLoggingAppender only logs when no namespaced_logging
appenders are configured, but it can also be configured to always
forward log messages, even when namespaced_logging is configured.
This commit is contained in:
2025-07-07 16:34:04 -05:00
parent c22e7edd5d
commit b2d43d1c6d

View File

@ -1,6 +1,7 @@
import std/[algorithm, atomics, json, locks, options, os, paths, sequtils,
strutils, tables, times]
import timeutils
import std/logging as stdlog
from std/logging import Level
export Level
@ -129,6 +130,19 @@ type
formatter*: LogMessageFormatter
absPath*: Path
StdLoggingAppender* = ref object of LogAppender
## Log appender that forwards log messages to the std/logging
## implementation. This is primarily intended for libraries and other
## situations where you expect that your code will be third-party to others
## and want to respect applications which use std/logging for log handlers
## and configuration.
fallbackOnly*: bool
## when true, only forward to std/logging where there are no appenders
## configured on the related LogService
formatter*: LogMessageFormatter
const UninitializedConfigVersion = low(int)
let JNULL = newJNull()
@ -436,6 +450,19 @@ proc addAppender*(ls: ThreadLocalLogService, appender: LogAppender) {.gcsafe.} =
addAppender(ls[], appender)
proc clearAppenders*(ls: var LogService) {.gcsafe.} =
## Remove all log appenders added to the global log service and refresh the
## local thread state. The updated global state will trigger other threads to
## refresh their state as well.
withLock ls.global.lock:
ls.global.appenders = @[]
ls.global.configVersion.atomicInc
proc clearAppenders*(ls: ThreadLocalLogService) {.gcsafe.} =
clearAppenders(ls[])
func getEffectiveThreshold(logger: Logger): Level {.gcsafe.} =
## Get the effective logging level threshold for a logger. This is the most
## specific level that is set for the logger or any of its parents. The root
@ -525,8 +552,9 @@ template fatal*[L: Logger or Option[Logger], M](l: L, msg: M) =
template fatal*[L: Logger or Option[Logger], M](l: L, error: ref Exception, msg: M) =
log(l, lvlFatal, error, msg)
# -----------------------------------------------------------------------------
# CustomerLogAppender Implementation
# CustomLogAppender Implementation
# -----------------------------------------------------------------------------
func initCustomLogAppender*[T](
@ -536,7 +564,7 @@ func initCustomLogAppender*[T](
threshold = lvlAll): CustomLogAppender[T] {.gcsafe.} =
if doLogMessage.isNil:
debugEcho "initCustomLogAppender: doLogMessage is nil"
raise newException(ValueError, "initCustomLogAppender: doLogMessage is nil")
result = CustomLogAppender[T](
namespace: namespace,
@ -545,8 +573,8 @@ func initCustomLogAppender*[T](
state: state)
method clone*[T](cla: CustomLogAppender[T]): LogAppender {.gcsafe.} =
if cla.doLogMessage.isNil:
debugEcho "CustomLogAppender#clone: source doLogMessage is nil"
assert not cla.doLogMessage.isNil,
"CustomLogAppender#clone: source doLogMessage is nil"
result = CustomLogAppender[T](
namespace: cla.namespace,
@ -561,7 +589,7 @@ method appendLogMessage[T](
msg: LogMessage) {.gcsafe.} =
try:
if cla.doLogMessage.isNil:
debugEcho "doLogMessage is nil"
raise newException(ValueError, "CustomLogAppender.appendLogMessage: doLogMessage is nil")
else: cla.doLogMessage(cla.state, msg)
except Exception:
ls.global.reportLoggingError(
@ -819,6 +847,48 @@ method appendLogMessage(
"unable to append to FileLogAppender")
# -----------------------------------------------------------------------------
# StdLoggingAppender Implementation
# -----------------------------------------------------------------------------
func formatForwardedLog*(lm: LogMessage): string =
## Default formatter for the StdLoggingAppender that prepends the logger
## scope to the message before formatting the message via
## *formatSimpleTextLog*
"[" & lm.scope & "] " & formatSimpleTextLog(lm)
func initStdLoggingAppender*(
fallbackOnly = true,
formatter = formatForwardedLog,
namespace = "",
threshold = lvlAll): StdLoggingAppender =
result = StdLoggingAppender(
namespace: namespace,
threshold: threshold,
fallbackOnly: fallbackOnly,
formatter: formatter)
method clone*(sla: StdLoggingAppender): LogAppender {.gcsafe.} =
result = StdLoggingAppender(
namespace: sla.namespace,
threshold: sla.threshold,
fallbackOnly: sla.fallbackOnly,
formatter: sla.formatter)
method appendLogMessage*(
sla: StdLoggingAppender,
ls: ThreadLocalLogService,
msg: LogMessage) {.gcsafe.} =
if sla.fallbackOnly and ls.appenders.len > 1: return
stdlog.log(msg.level, sla.formatter(msg))
# -----------------------------------------------------------------------------
# Autoconfiguration Implementation
# -----------------------------------------------------------------------------