# Changelog ## 3.0.0 This release reworks logging ownership for Nim's ORC memory manager, fixes background writer shutdown, and switches generated timestamps to UTC. It introduces breaking API changes from 2.1.2. Nim 2.2.0 or later remains required. ### Breaking changes and migration - **Service handles and local caches are separate types.** `LogService` is now a copyable handle that keeps shared configuration alive. Pass a copy to each worker, then call `threadLocalRef()` inside that worker. Keep `ThreadLocalLogService` and its loggers in the thread that creates them. Use `local.service` instead of `local[]` to obtain a shared handle. - **`ensureFreshness(LogService)` has been removed.** Refresh the relevant `ThreadLocalLogService` instead. Configuration methods remain available on both types; `LogService.appenders()` returns clones of the current shared configuration. - **Custom appender callbacks now receive `state: var T`.** `initCustomLogAppender` copies supported value state into each clone and rejects ordinary references, closures, and embedded synchronization primitives, including nested ones. Use `SharedLogState[T]` for shared mutable value data, or `initFactoryCustomLogAppender` for independently created state containing references or closures. - **Appenders now accept `LogFormatter` objects.** The former `LogMessageFormatter` callback type is replaced by `StatefulLogFormatterFunc` and `StatelessLogFormatterFunc`. Noncapturing formatter functions still convert automatically. Replace directly supplied captured closures with `initLogFormatter(seed, factory)`, and use `formatter.format(message)` to invoke a formatter directly. Custom appender and formatter clones must own independent state or use explicit shared-state handles. - **Generated timestamps now use UTC.** `LogMessage.ts` remains a `DateTime`; structured JSON output uses a `Z` suffix. Explicit timestamps supplied in caller-constructed messages are preserved. - **The test collector returns value snapshots.** `LoggedMessages.get()` now returns `seq[RecordedLogMessage]` instead of `seq[LogMessage]`. Timestamps are `Time` values, additional JSON data is serialized text, and exceptions retain their name, message, and stacktrace as text. Use the collector's methods instead of accessing its former `messages` field. ### Added - `SharedLogState[T]` and `initSharedLogState` provide copyable ownership of shared value data. Use `withState` for synchronized updates and `snapshot` for independent copies. Raw pointers and custom copy/destruction hooks remain the caller's responsibility. - `FactoryCustomLogAppender` and `initFactoryCustomLogAppender` create custom state lazily within the thread using each clone. Formatter factories provide the same ownership pattern for captured formatter state. Configuration refresh can replace clones and reset their local state; use shared-state handles for data that must persist across refreshes. ### Fixed and clarified - Service copies retain shared configuration without sharing ordinary ORC reference ownership between threads. Workers can outlive the original handle and the thread that created it. - Local caches are built independently. Failed configuration refreshes preserve the previous cache and retry on a later refresh without requiring another configuration change. - Background console and file writers drain queued records at normal process exit, including empty messages and the final file batch. Shutdown excludes further submissions and waits for writers to finish. Applications must join their logging workers before exiting. - Both message constructors avoid local-time conversion races by creating UTC timestamps directly. - `isEnabled` intentionally checks cached namespace thresholds without refreshing configuration. Logging calls, `getLogger`, or an explicit local refresh pick up changes. Applications that guard all logging should refresh at suitable boundaries, such as the start of a request. The shared root threshold is read only when no cached namespace threshold applies. ### Tests and known limitations - `nimble test` runs all enabled unit, regression, and ThreadSanitizer suites under ORC with threads enabled. Individual suites remain available through `testUnit`, `testRegression`, and `testThreadSafety`. - Validation passes 32 unit tests, 29 regression tests, and 11 sanitizer scenarios. Two scenarios, `factory-state` and `allocator`, are temporarily skipped because they expose a known race in Nim 2.2.10's default allocator. Their code and assertions remain available; enable them with `NSL_TEST_RUNTIME_ALLOCATOR=1 nimble testThreadSafety`. - The allocator limitation remains unresolved and also reproduces without importing this library. Passing the enabled tests does not establish full validation of factory state under the default allocator. See [the test documentation](tests/README.md) for details and diagnostic results. See the [README](README.md) for usage and migration examples.