Files
nim-namespaced-logging/CHANGELOG.md
T

5.0 KiB

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 for details and diagnostic results.

See the README for usage and migration examples.