Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6dc6d6a
Add experimental lib_logging XDK module (SLF4J-shaped, injection-first)
lagergren Apr 29, 2026
c40e6c0
Add runtime-implementation plan for lib_logging round trip
lagergren Apr 29, 2026
9d3b0be
Trim OPEN_QUESTIONS.md and link addressed items to the runtime plan
lagergren Apr 29, 2026
a346493
Mature lib_logging end-to-end: const sweep, multi-marker, interning
lagergren May 1, 2026
7a9904f
Add lib_slogging parallel module + design comparison
lagergren May 1, 2026
a1666f4
doc/logging: restructure as a navigable doc tree
lagergren May 1, 2026
dfbdce0
doc/logging: kebab-case doc names + remove archive
lagergren May 1, 2026
139b37d
doc/logging: group docs into design/, usage/, future/ subdirectories
lagergren May 1, 2026
f3fb552
Bring slogging POC to logging parity
lagergren May 4, 2026
7adadf2
Tighten logging documentation structure
lagergren May 4, 2026
cbc29db
Add canonical logging backend primitives
lagergren May 5, 2026
200428a
Mark logging injection as POC wiring
lagergren May 5, 2026
a1862b1
Clarify logging POC requirements and configuration
lagergren May 5, 2026
1d2702c
Expand logging configuration guide
lagergren May 5, 2026
e0abf08
Clarify logging documentation entry points
lagergren May 5, 2026
6f1bcfd
Add logging branch review prompt
lagergren May 5, 2026
254073b
Fix logging review prompt whitespace
lagergren May 5, 2026
5dfb49a
Fix logging POC review issues
lagergren May 5, 2026
2d528dd
Clean up logging POC docs and wiring
lagergren May 5, 2026
675985b
Add lazy logging to logging POCs
lagergren May 5, 2026
a426d8a
Split logging manual scenarios by API style
lagergren May 5, 2026
9ee4257
Address logging POC review: doc restructure + 8 code fixes
lagergren May 5, 2026
8cc7fb5
Explain MDC before the example that uses it
lagergren May 5, 2026
16ac679
Removed `Attrs.x` as this is just a String key and any value pair. Us…
thegridman May 7, 2026
9944c4d
Fix `TestSLogging.x` in manualTests
thegridman May 7, 2026
f66fed5
Align log levels with Open Telemetry severity values
thegridman May 8, 2026
a6281e4
Align log `Record` closer to Open Telemetry, which allows `AnyValue` …
thegridman May 8, 2026
e75741a
Update `TestSLogging.x`
thegridman May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions doc/logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Ecstasy logging β€” start here

`lib_logging` is the XDK's injectable logging library. Application code asks the
runtime for a logger and writes events; the host container chooses where the
output goes.

```ecstasy
@Inject Logger logger;
logger.info("processed {} records in {}ms", [count, elapsed]);
```

That is the whole hello-world. There is no factory call, no static initializer,
no configuration file. The default `ConsoleLogSink` writes the line; the host
can swap that for JSON, async, fanout, hierarchical levels, or a test capture
sink without changing caller code.

> **New here?** Read [`usage/injected-logger-example.md`](usage/injected-logger-example.md) next β€” it walks
> from the smallest possible app up to a realistic per-class logger setup. Come
> back to this README for the design context.

## Reading paths

| If you want to… | Read in this order |
|---|---|
| **Write code against `lib_logging`** | [`usage/injected-logger-example.md`](usage/injected-logger-example.md) β†’ [`usage/structured-logging.md`](usage/structured-logging.md) β†’ [`usage/lazy-logging.md`](usage/lazy-logging.md) |
| **Decide whether to merge this PR** | [`roadmap.md`](roadmap.md) β†’ [`lib-logging-vs-lib-slogging.md`](lib-logging-vs-lib-slogging.md) β†’ [`open-questions.md`](open-questions.md) |
| **Understand the API choice** | [`lib-logging-vs-lib-slogging.md`](lib-logging-vs-lib-slogging.md) β†’ [`design/why-slf4j-and-injection.md`](design/why-slf4j-and-injection.md) β†’ [`cloud-integration.md`](cloud-integration.md) |
| **Migrate from SLF4J** | [`usage/slf4j-parity.md`](usage/slf4j-parity.md) β†’ [`usage/ecstasy-vs-java-examples.md`](usage/ecstasy-vs-java-examples.md) |
| **Migrate from Go `slog`** | [`usage/slog-parity.md`](usage/slog-parity.md) β†’ [`api-cross-reference.md`](api-cross-reference.md) |
| **Write a custom backend** | [`usage/custom-sinks.md`](usage/custom-sinks.md) β†’ [`design/design.md`](design/design.md) |
| **Read the architecture** | [`design/design.md`](design/design.md) β†’ [`design/xdk-alignment.md`](design/xdk-alignment.md) |

Each doc has a "Next β†’" footer that follows its column above. You can also use
the [Full doc index](#full-doc-index) at the bottom.

## Goals

Why this library exists. These are the directional aims, not contractual specs:

- **Familiar.** A JVM or Go engineer should recognize the call shape on first
read, with no Ecstasy-specific learning curve for the common case.
- **Container-aware.** The host application β€” not the calling library β€” decides
where logs go. Per-container override is a one-line change, not a classpath
ceremony.
- **Cloud-ready.** Structured fields (level, message, request context, exception)
flow into JSON output and from there into Cloud Logging / CloudWatch / App
Insights through small, well-known adapters. See [`cloud-integration.md`](cloud-integration.md).
- **Pay-for-what-you-use.** Disabled log calls cost a level check. Expensive
message and value construction can be deferred behind that check.
- **Testable.** Capturing log output in a test should be one line, with no
global state to reset.

## Requirements

The narrower, verifiable list β€” what the accepted XDK logging API **must** do.
Status reflects what the POC in this branch ships:

| # | Requirement | Status | Evidence |
|---|---|---|---|
| R1 | One canonical injectable logger acquired by `@Inject Logger logger;`. | βœ“ done | [`Logger.x`](../../lib_logging/src/main/x/logging/Logger.x), [`NativeContainer.java`](../../javatools/src/main/java/org/xvm/runtime/NativeContainer.java) `registerLoggingResources` |
| R2 | Per-name child loggers via API, not via wildcard injection. | βœ“ done | `Logger.named(String)` in [`Logger.x`](../../lib_logging/src/main/x/logging/Logger.x); [`NamedLoggerTest`](../../lib_logging/src/test/x/LoggingTest/NamedLoggerTest.x) |
| R3 | Events carry first-class structured key/value fields, exception, MDC, markers. | βœ“ done | [`LogEvent.x`](../../lib_logging/src/main/x/logging/LogEvent.x); [`StructuredLoggingTest`](../../lib_logging/src/test/x/LoggingTest/StructuredLoggingTest.x) |
| R4 | Disabled log calls are cheap; expensive args/values can be supplied lazily. | βœ“ done | `MessageSupplier`, `addLazyArgument` in [`LoggingEventBuilder.x`](../../lib_logging/src/main/x/logging/LoggingEventBuilder.x); [`LazyLoggingTest`](../../lib_logging/src/test/x/LoggingTest/LazyLoggingTest.x) |
| R5 | Backends are swappable, composable, async-wrappable, and test-capturable. | βœ“ done | [`CompositeLogSink.x`](../../lib_logging/src/main/x/logging/CompositeLogSink.x), [`AsyncLogSink.x`](../../lib_logging/src/main/x/logging/AsyncLogSink.x), [`MemoryLogSink.x`](../../lib_logging/src/main/x/logging/MemoryLogSink.x) |
| R6 | Per-logger-name level routing (Logback longest-prefix). | βœ“ done | [`HierarchicalLogSink.x`](../../lib_logging/src/main/x/logging/HierarchicalLogSink.x); [`HierarchicalLogSinkTest`](../../lib_logging/src/test/x/LoggingTest/HierarchicalLogSinkTest.x) |
| R7 | JSON output with redaction. | βœ“ done | [`JsonLogSink.x`](../../lib_logging/src/main/x/logging/JsonLogSink.x), [`JsonLogSinkOptions.x`](../../lib_logging/src/main/x/logging/JsonLogSinkOptions.x); [`JsonLogSinkTest`](../../lib_logging/src/test/x/LoggingTest/JsonLogSinkTest.x) |
| R8 | Backend failures must not break callers. | βœ“ contract | Documented in [`LogSink.x`](../../lib_logging/src/main/x/logging/LogSink.x); enforcement still up to each sink. |
| R9 | In-memory capture sink for tests. | βœ“ done | [`MemoryLogSink.x`](../../lib_logging/src/main/x/logging/MemoryLogSink.x); used throughout `lib_logging/src/test/` |
| R10 | Default logger name and source-location capture have a stable lowering target the compiler can fill in later. | ~ partial | `Logger.logAt(...)` populates source explicitly; runtime fallback derives logger name from caller namespace. Compiler-synthesized source/name still TBD β€” see [`roadmap.md`](roadmap.md). |
| R11 | Configuration-file driven backend (Logback-equivalent ops). | β†’ v1+ | Sketched in [`future/logback-integration.md`](future/logback-integration.md); not in this branch. |

## Non-goals (this branch)

These are explicitly out of scope for the POC. Listing them prevents reviewer
churn over things that were never the target:

- **A configuration file format.** `JsonLogSinkOptions` covers the JSON sink's
knobs; XML/JSON/YAML config and hot reload belong to a future configured
backend.
- **Distributed tracing context propagation.** MDC carries strings; tracing
IDs can ride on it. Trace/span propagation itself is a separate library.
- **Automatic compiler call-site capture.** `Logger.logAt(sourceFile, sourceLine)`
is the lowering target; the compiler does not yet synthesize those args for
ordinary `logger.info(...)` calls.
- **Rolling/network/cloud destinations.** They are pure-XTC sink modules built
on the SPI; out of scope for the base library.

## Two POCs in this branch

This branch carries two parallel libraries so reviewers can compare API shapes
in real Ecstasy code:

| Library | Prior art | Tests |
|---|---|---|
| [`lib_logging`](../../lib_logging/) | SLF4J 2.x + Logback | 70 XTC test methods, [`TestLogging.x`](../../manualTests/src/main/x/TestLogging.x) demo |
| [`lib_slogging`](../../lib_slogging/) | Go `log/slog` | 41 XTC test methods, [`TestSLogging.x`](../../manualTests/src/main/x/TestSLogging.x) demo |

The branch **recommends `lib_logging`** as the canonical XDK shape on
familiarity and ecosystem grounds β€” the [API-choice doc](lib-logging-vs-lib-slogging.md)
explains why. Whichever shape wins should ship as `lib_logging`; `lib_slogging`
is a POC name, not a proposed permanent sibling. Both modules are deliberately
optional in the runtime: a build that only ships one of them must still start.

## Status & roadmap

The POC implements every requirement above except R11 (configuration-file
backend) and the compiler-side half of R10. The merge-blocking decisions, the
v1 scope, and the explicit-future items are tracked in
[`roadmap.md`](roadmap.md). Outstanding language/runtime questions for the XTC
designers are in [`open-questions.md`](open-questions.md) (Q-D1…Q-D7).

## Full doc index

| Doc | One-line description |
|---|---|
| **Top-level** | |
| [`README.md`](README.md) | This file. The "start here" entry point. |
| [`roadmap.md`](roadmap.md) | What blocks merge, what lands in v1, what is explicit future. |
| [`lib-logging-vs-lib-slogging.md`](lib-logging-vs-lib-slogging.md) | API-choice summary first, then depth-first rationale on levels, context, markers, formatting, and backend shape. |
| [`api-cross-reference.md`](api-cross-reference.md) | Official SLF4J / Go slog API links mapped to local Ecstasy types and difference notes. |
| [`cloud-integration.md`](cloud-integration.md) | Why the API choice is the entry point to cloud observability ecosystems. |
| [`open-questions.md`](open-questions.md) | Decision tracker: resolved calls, designer questions (Q-D1..Q-D7), W-item parity list, and remaining follow-up. |
| **Concepts** | |
| [`concepts/markers.md`](concepts/markers.md) | What a log marker is, who uses them, when they earn their API surface. |
| **Design (`lib_logging` side)** | |
| [`design/design.md`](design/design.md) | `lib_logging` architecture: types, API↔impl boundary, sink-type rule, MDC mechanism, per-container override. |
| [`design/why-slf4j-and-injection.md`](design/why-slf4j-and-injection.md) | The original rationale for the SLF4J shape and injection-first acquisition. |
| [`design/xdk-alignment.md`](design/xdk-alignment.md) | Alignment with the conventions of other XDK libraries (`lib_ecstasy`, `lib_cli`, etc.). |
| **Usage / examples** | |
| [`usage/injected-logger-example.md`](usage/injected-logger-example.md) | End-to-end working example of `@Inject Logger`. **Suggested first stop for new users.** |
| [`usage/ecstasy-vs-java-examples.md`](usage/ecstasy-vs-java-examples.md) | Per-feature SLF4J Java vs `lib_logging` Ecstasy. |
| [`usage/slf4j-parity.md`](usage/slf4j-parity.md) | Exhaustive type/method mapping reference. |
| [`usage/slog-parity.md`](usage/slog-parity.md) | Go `log/slog` vs `lib_slogging` mapping reference. |
| [`usage/platform-and-examples-adaptation.md`](usage/platform-and-examples-adaptation.md) | Migration survey for existing Ecstasy code bases. |
| **Extension** | |
| [`usage/custom-sinks.md`](usage/custom-sinks.md) | How to write a custom `LogSink`, with worked examples. |
| [`usage/custom-handlers.md`](usage/custom-handlers.md) | How to write a custom slog `Handler`, with worked examples. |
| [`usage/structured-logging.md`](usage/structured-logging.md) | Structured logging dive: key/value pairs, JSON output, fluent builder. |
| [`usage/lazy-logging.md`](usage/lazy-logging.md) | Lazy message/value construction: Kotlin blocks, Java suppliers, and the Ecstasy API. |
| [`usage/configuration.md`](usage/configuration.md) | Logback XML vs proposed Ecstasy JSON configuration, dynamic sink/handler reload, and equivalence mapping. |
| **Follow-up backend/compiler work** | |
| [`future/logback-integration.md`](future/logback-integration.md) | Configuration-driven Logback-style backend on top of the shipped primitives. |

## Where the actual code lives

```
lib_logging/ SLF4J-shaped library
β”œβ”€β”€ build.gradle.kts
β”œβ”€β”€ README.md module-level intro
└── src/
β”œβ”€β”€ main/x/
β”‚ β”œβ”€β”€ logging.x module declaration
β”‚ └── logging/
β”‚ β”œβ”€β”€ Level.x severity enum
β”‚ β”œβ”€β”€ Marker.x marker interface (org.slf4j.Marker)
β”‚ β”œβ”€β”€ BasicMarker.x default marker impl
β”‚ β”œβ”€β”€ MarkerFactory.x stateful marker factory
β”‚ β”œβ”€β”€ MDC.x per-fiber mapped diagnostic context (const)
β”‚ β”œβ”€β”€ LogEvent.x immutable event const (Marker[] markers)
β”‚ β”œβ”€β”€ LogSink.x SPI β€” the API/impl boundary
β”‚ β”œβ”€β”€ Logger.x user-facing facade interface
β”‚ β”œβ”€β”€ LoggingEventBuilder.x SLF4J 2.x fluent builder
β”‚ β”œβ”€β”€ LoggerFactory.x non-injection acquisition path
β”‚ β”œβ”€β”€ LoggerRegistry.x identity-stable interning of named children
β”‚ β”œβ”€β”€ MessageFormatter.x {}-substitution + throwable promotion
β”‚ β”œβ”€β”€ BasicLogger.x canonical Logger impl (const)
β”‚ β”œβ”€β”€ BasicEventBuilder.x canonical builder impl
β”‚ β”œβ”€β”€ ConsoleLogSink.x default sink (const), forwards to @Inject Console
β”‚ β”œβ”€β”€ JsonLogSink.x JSON-Lines sink (const), rendered by lib_json
β”‚ β”œβ”€β”€ JsonLogSinkOptions.x root level, redaction, inclusion, field names
β”‚ β”œβ”€β”€ CompositeLogSink.x multi-destination fanout (const)
β”‚ β”œβ”€β”€ HierarchicalLogSink.x longest-prefix per-logger thresholds (service)
β”‚ β”œβ”€β”€ AsyncLogSink.x bounded async wrapper (service)
β”‚ β”œβ”€β”€ NoopLogSink.x drops every event (const)
β”‚ └── MemoryLogSink.x test-helper, captures events (service)
└── test/x/LoggingTest/ 70 focused XTC test methods

lib_slogging/ slog-shaped sibling library
β”œβ”€β”€ build.gradle.kts
└── src/
β”œβ”€β”€ main/x/
β”‚ β”œβ”€β”€ slogging.x module declaration
β”‚ └── slogging/
β”‚ β”œβ”€β”€ Level.x open Int severity (Level(severity, label))
β”‚ β”œβ”€β”€ Attr.x the single structured-data carrier
β”‚ β”œβ”€β”€ Record.x immutable event const (the LogEvent equivalent)
β”‚ β”œβ”€β”€ Handler.x SPI (the LogSink equivalent)
β”‚ β”œβ”€β”€ BoundHandler.x default with/withGroup derivation wrapper
β”‚ β”œβ”€β”€ Logger.x concrete user-facing const
β”‚ β”œβ”€β”€ LoggerContext.x optional SharedContext<Logger> helper
β”‚ β”œβ”€β”€ TextHandler.x default human-readable handler (const)
β”‚ β”œβ”€β”€ JSONHandler.x JSON-Lines handler (const, lib_json renderer)
β”‚ β”œβ”€β”€ HandlerOptions.x threshold, redaction, source, field-name knobs
β”‚ β”œβ”€β”€ AsyncHandler.x bounded async wrapper (service)
β”‚ β”œβ”€β”€ NopHandler.x drops every record (const)
β”‚ └── MemoryHandler.x test-helper (service)
└── test/x/SLoggingTest/ 41 focused XTC test methods
```

---

Next: [`usage/injected-logger-example.md`](usage/injected-logger-example.md) β†’
Loading
Loading