diff --git a/docs/api/build-library-logger.md b/docs/api/build-library-logger.md index 10df799..9d8d3f5 100644 --- a/docs/api/build-library-logger.md +++ b/docs/api/build-library-logger.md @@ -62,10 +62,10 @@ When config-built runtime queue or file controls are still needed internally: ```moonbit let logger = build_library_logger(config) let full = logger.to_logger() -ignore(full.pending_count()) +ignore(full.sink.pending_count()) ``` -In this example, the library facade is unwrapped before using configured runtime helper methods. +In this example, the library facade is unwrapped before using runtime-specific helpers through the preserved `RuntimeSink` value. And the unwrapped value still carries the same `RuntimeSink` pipeline built from the original config. @@ -82,4 +82,4 @@ e.g.: 2. Use `parse_and_build_library_logger(...)` when starting from JSON text. -3. Use `to_logger()` when internal code later needs configured-runtime helpers or broader logger composition without changing the public facade type. +3. Use `to_logger()` when internal code later needs broader logger composition or direct access to the preserved `RuntimeSink` value without changing the public facade type. diff --git a/docs/api/library-logger-to-logger.md b/docs/api/library-logger-to-logger.md index e726c74..b34cbb3 100644 --- a/docs/api/library-logger-to-logger.md +++ b/docs/api/library-logger-to-logger.md @@ -36,7 +36,7 @@ Detailed rules explaining key parameters and behaviors - This conversion unwraps the existing logger instead of rebuilding it. - Sink wiring, target, min level, timestamp behavior, and attached wrappers remain the same. - Use this when code needs full-surface APIs such as `with_timestamp(...)`, `with_filter(...)`, or `with_patch(...)`. -- This is also the step required for configured-runtime helpers such as `flush()`, `drain()`, `pending_count()`, or file controls when the wrapped sink is `RuntimeSink`. +- When the wrapped sink is `RuntimeSink`, unwrapping preserves that runtime sink value, but the result type is still `Logger[RuntimeSink]` rather than the `ConfiguredLogger` alias. Runtime-specific operations remain available through `full.sink` or by keeping a `ConfiguredLogger` value directly. ### How to Use @@ -57,10 +57,10 @@ In this example, the facade is unwrapped so the caller can access full logger co When a library-facing runtime logger should later expose configured runtime controls internally: ```moonbit let full = logger.to_logger() -ignore(full.pending_count()) +ignore(full.sink.pending_count()) ``` -In this example, unwrapping exposes the broader configured-runtime helper surface on the same underlying logger state. +In this example, unwrapping preserves the same `RuntimeSink` pipeline, and callers reach runtime-specific helpers through that preserved sink value. ### Error Case diff --git a/docs/api/parse-and-build-library-logger.md b/docs/api/parse-and-build-library-logger.md index b6bcd5a..4934c77 100644 --- a/docs/api/parse-and-build-library-logger.md +++ b/docs/api/parse-and-build-library-logger.md @@ -66,10 +66,10 @@ let logger = parse_and_build_library_logger(raw) catch { err => return } let full = logger.to_logger() -ignore(full.pending_count()) +ignore(full.sink.pending_count()) ``` -In this example, the caller unwraps the library facade before using configured runtime helper methods. +In this example, the caller unwraps the library facade before using runtime-specific helpers through the preserved `RuntimeSink` value. And the unwrapped value still reflects the same `RuntimeSink` pipeline built from the parsed config text. @@ -88,4 +88,4 @@ e.g.: 2. Use `build_library_logger(...)` when the config is already typed. -3. Use `to_logger()` when internal code later needs configured-runtime helpers or broader logger composition without changing the public facade type. +3. Use `to_logger()` when internal code later needs broader logger composition or direct access to the preserved `RuntimeSink` value without changing the public facade type. diff --git a/src/BitLogger_test.mbt b/src/BitLogger_test.mbt index 041f93d..256620a 100644 --- a/src/BitLogger_test.mbt +++ b/src/BitLogger_test.mbt @@ -1199,6 +1199,68 @@ test "configured logger can project to library logger" { inspect(logger.to_logger().target, content="lib.projected") } +test "logger projection preserves sync composition state through library facade" { + let captured_target : Ref[String] = Ref("") + let captured_timestamp : Ref[UInt64] = Ref(0UL) + let captured_fields : Ref[Array[Field]] = Ref([]) + let base = Logger::new( + callback_sink(fn(rec) { + captured_target.val = rec.target + captured_timestamp.val = rec.timestamp_ms + captured_fields.val = rec.fields + }), + min_level=Level::Warn, + target="lib.projected.state", + ) + .with_timestamp() + .with_context_fields([field("service", "bitlogger")]) + let projected = base.to_library_logger() + let full = projected.to_logger() + + inspect(projected.is_enabled(Level::Error), content="true") + inspect(projected.is_enabled(Level::Info), content="false") + inspect(full.target, content="lib.projected.state") + inspect(full.timestamp, content="true") + full.error("boom", fields=[field("mode", "test")]) + inspect(captured_target.val, content="lib.projected.state") + inspect(captured_timestamp.val > 0UL, content="true") + inspect(captured_fields.val.length(), content="2") + inspect(captured_fields.val[0].key, content="service") + inspect(captured_fields.val[0].value, content="bitlogger") + inspect(captured_fields.val[1].key, content="mode") +} + +test "configured logger projection preserves runtime helpers through unwrap" { + let projected = build_logger( + LoggerConfig::new( + min_level=Level::Warn, + target="lib.projected.runtime", + sink=SinkConfig::new(kind=SinkKind::Console), + queue=Some(QueueConfig::new(3, overflow=QueueOverflowPolicy::DropNewest)), + ), + ).to_library_logger() + let full = projected.to_logger() + + inspect(projected.is_enabled(Level::Error), content="true") + inspect(projected.is_enabled(Level::Info), content="false") + inspect(full.target, content="lib.projected.runtime") + full.error("one") + full.error("two") + inspect(match full.sink { + RuntimeSink::QueuedConsole(_) => "QueuedConsole" + RuntimeSink::QueuedJsonConsole(_) => "QueuedJsonConsole" + RuntimeSink::QueuedTextConsole(_) => "QueuedTextConsole" + RuntimeSink::QueuedFile(_) => "QueuedFile" + _ => "Other" + }, content="QueuedConsole") + inspect(full.sink.pending_count(), content="2") + inspect(full.sink.dropped_count(), content="0") + inspect(full.sink.drain(max_items=1), content="1") + inspect(full.sink.pending_count(), content="1") + inspect(full.sink.flush(), content="1") + inspect(full.sink.pending_count(), content="0") +} + test "default library logger mirrors shared sync defaults" { set_default_min_level(Level::Warn) set_default_target("lib.default")