This commit represents a complete architectural overhaul of the namespaced logging library, transitioning from a shared-memory approach to a robust thread-safe design with proper synchronization primitives. - **NEW**: internal `GlobalLogService` with atomic configuration versioning - **NEW**: Thread-local configuration caching with freshness checking - **NEW**: Separate `LogService` (copyable) and `ThreadLocalLogService` (ref) - **REMOVED**: Manual thread state reloading in favor of automatic freshness - All shared state protected by locks and atomics - Configuration changes use atomic version numbers for efficient sync - Proper cleanup with `=destroy` implementing graceful shutdown - Thread-safe appender cloning via `clone()` method pattern - **NEW**: Dedicated writer threads for console and file output - **NEW**: Channel-based message passing to writer threads - **NEW**: Batched file I/O with optimized write patterns - **NEW**: Graceful thread shutdown on service destruction - **NEW**: Configurable error handling with `ErrorHandlerFunc` - **NEW**: `defaultErrorHandlerFunc` with stderr fallback - **NEW**: Thread-safe error reporting with separate lock - **NEW**: Robust error recovery in all I/O operations - **NEW**: `autoconfigured` module for zero-config usage - **NEW**: `formatSimpleTextLog` as default formatter - **NEW**: Optional logger support for ergonomic usage - **NEW**: Generic `CustomLogAppender[T]` with state parameter - **NEW**: `FileLogAppender` with proper multithreaded file I/O - **BREAKING**: Logger `name` field renamed to `scope` - **BREAKING**: Configuration methods renamed (e.g., `setRootLevel` → `setRootThreshold`) - **NEW**: Comprehensive test suite with 20+ test cases - **NEW**: `testutil` module with thread-safe test infrastructure - **NEW**: Cross-thread synchronization testing - **NEW**: File I/O testing with temporary files - **REMOVED**: Old test suite replaced with more comprehensive version - Atomic version checking prevents unnecessary config copies - Writer threads use efficient polling with 100ms idle sleep - File writer batches messages and optimizes file operations - Thread-local caching reduces lock contention 1. **API Changes**: - `LogService` returned by `iniLogService` is fundamentally different - `threadLocalRef()` required for thread-local operations - `reloadThreadState()` removed (automatic freshness) - Logger field `name` → `scope` 2. **Configuration**: - `setRootLevel()` → `setRootThreshold()` - `setThreshold()` API simplified - `clearAppenders()` removed 3. **Appenders**: - `initThreadCopy()` → `clone()` - `appendLogMessage()` signature changed - Custom appenders now generic with state parameter ```nim let ls = initLogService() ls.addAppender(initConsoleLogAppender()) reloadThreadState(ls) let logger = ls.getLogger("app") logger.info("Hello world") ``` ```nim let ls = initLogService() let tlls = threadLocalRef(ls) tlls.addAppender(initConsoleLogAppender()) let logger = tlls.getLogger("app") logger.info("Hello world") ``` ```nim import namespaced_logging/autoconfigured addLogAppender(initConsoleLogAppender()) info("Hello world") ```
52 lines
1.3 KiB
Nim
52 lines
1.3 KiB
Nim
import std/[locks, sequtils, syncio, os, times]
|
|
from logging import Level
|
|
from ../namespaced_logging import CustomLogAppender, initCustomLogAppender, LogMessage
|
|
|
|
type
|
|
LoggedMessages* = ref object
|
|
messages*: seq[LogMessage]
|
|
lock: Lock
|
|
|
|
proc initLoggedMessages*(): LoggedMessages =
|
|
result = LoggedMessages(messages: @[])
|
|
initLock(result.lock)
|
|
|
|
|
|
proc add*(lm: LoggedMessages, msg: LogMessage) =
|
|
withLock lm.lock: lm.messages.add(msg)
|
|
|
|
|
|
proc clear*(lm: LoggedMessages) =
|
|
withLock lm.lock: lm.messages = @[]
|
|
|
|
|
|
proc get*(lm: LoggedMessages): seq[LogMessage] =
|
|
withLock lm.lock: return lm.messages.mapIt(it)
|
|
|
|
|
|
proc testLogAppenderProc(state: LoggedMessages, msg: LogMessage) {.gcsafe, noconv.} =
|
|
state.add(msg)
|
|
|
|
proc initTestLogAppender*(
|
|
lm: LoggedMessages,
|
|
namespace = "",
|
|
threshold = lvlAll): CustomLogAppender[LoggedMessages] =
|
|
|
|
initCustomLogAppender(
|
|
state = lm,
|
|
doLogMessage = testLogAppenderProc,
|
|
namespace = namespace,
|
|
threshold = threshold)
|
|
|
|
|
|
proc waitForFileContent*(
|
|
path: string,
|
|
expectedLines: int,
|
|
timeoutMs: int = 1000): seq[string] =
|
|
let startTime = getTime()
|
|
while (getTime() - startTime).inMilliseconds < timeoutMs:
|
|
if fileExists(path):
|
|
result = readLines(path)
|
|
if result.len >= expectedLines: break
|
|
sleep(10)
|