Document version 3.0.0 changes and migration

This commit is contained in:
jdb committed 2026-09-29 09:15:59 -05:00
1 parent 5a2418f438
commit 9fa158f68d
2 files changed
+96 -4

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
# 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.