Proposal: standard logging (Log.*) — levels, structured fields, console/file/overlay sinks #15

Closed
opened 2026-08-29 20:21:58 +02:00 by orkun · 1 comment
Owner

Summary

A standard logging library with levels, structured fields, and pluggable
outputs (console, file, in-game overlay) — the default way to answer "what is my
game doing?" and "why did that break?"

Why it matters for game devs

  • Debugging without a debugger: most non-expert devs debug by printing.
    Give them something better than scattered print calls.
  • In-game dev console / overlay: show recent warnings on screen during
    playtests.
  • Bug reports: write a rotating log file players can attach.

Proposed API (illustrative)

# doc-check: skip — illustrative API sketch
Log.info("level loaded", { name: level, entities: count })
Log.warn("missing texture, using placeholder", { path: p })
Log.error("save failed", { err: e })

Log.set_level(LogLevel.Warn)          # quiet in release builds
Log.add_sink(Log.file("game.log"))    # also write to a rotating file
Log.add_sink(Log.overlay())           # draw recent lines in-game
  • Levels: trace/debug/info/warn/error.
  • Structured key/values (cheap to format, easy to grep).
  • Multiple sinks: stdout, file (with rotation), and an in-engine overlay.
  • Compile-time level gate so disabled logs cost ~nothing in release.

Considerations

  • Native/C-free; file sink builds on the Filesystem library.
  • Determinism: logging must have no effect on simulation; timestamps come
    from the Time library and are excluded from replays.
  • Cheap formatting; avoid per-frame allocations for hot debug logs.
  • Thread-safe if the Jobs library lands (jobs may log).
  • Small, readable default console format; structured/JSON sink optional.

Scope / acceptance

  • Log namespace with levels + structured fields.
  • Console + rotating-file + overlay sinks; add_sink/set_level.
  • Release-build level gating.
  • Docs page + a dev-console example.
  • Tests (level filtering, sink dispatch).

Related: Time, Filesystem & IO, Jobs & async, error handling.

## Summary A **standard logging library** with levels, structured fields, and pluggable outputs (console, file, in-game overlay) — the default way to answer "what is my game doing?" and "why did that break?" ## Why it matters for game devs - **Debugging without a debugger**: most non-expert devs debug by printing. Give them something better than scattered `print` calls. - **In-game dev console / overlay**: show recent warnings on screen during playtests. - **Bug reports**: write a rotating log file players can attach. ## Proposed API (illustrative) ```ludic # doc-check: skip — illustrative API sketch Log.info("level loaded", { name: level, entities: count }) Log.warn("missing texture, using placeholder", { path: p }) Log.error("save failed", { err: e }) Log.set_level(LogLevel.Warn) # quiet in release builds Log.add_sink(Log.file("game.log")) # also write to a rotating file Log.add_sink(Log.overlay()) # draw recent lines in-game ``` - Levels: `trace/debug/info/warn/error`. - Structured key/values (cheap to format, easy to grep). - Multiple **sinks**: stdout, file (with rotation), and an in-engine overlay. - Compile-time level gate so disabled logs cost ~nothing in release. ## Considerations - Native/C-free; file sink builds on the Filesystem library. - Determinism: logging must have **no effect** on simulation; timestamps come from the Time library and are excluded from replays. - Cheap formatting; avoid per-frame allocations for hot `debug` logs. - Thread-safe if the Jobs library lands (jobs may log). - Small, readable default console format; structured/JSON sink optional. ## Scope / acceptance - [ ] `Log` namespace with levels + structured fields. - [ ] Console + rotating-file + overlay sinks; `add_sink`/`set_level`. - [ ] Release-build level gating. - [ ] Docs page + a dev-console example. - [ ] Tests (level filtering, sink dispatch). Related: Time, Filesystem & IO, Jobs & async, error handling.
orkun added the
proposal
priority:medium
area:stdlib
labels 2026-08-29 20:21:58 +02:00
Author
Owner

Done in commit 031b138.

Added the Log.* namespace — levelled, structured logging to stderr:

  • Five levels: Log.trace/debug/info/warn/error(msg, [key, value]...) — printed as [LEVEL] msg key=value ....
  • Structured fields as trailing key/value pairs; values may be strings, ints, or longs (numbers are formatted automatically).
  • Log.set_level(n) — runtime threshold (0=all default … 4=error, 5 silences all); Log.level() reads it back.
  • The level tag is chosen at compile time from the method name, so a filtered-out level costs only a threshold comparison — the release-build gating this issue asks for.

Writes to stderr and returns, never touching the simulation, so logging has no effect on determinism/replays.

Acceptance:

  • Log namespace with levels + structured fields
  • Console (stderr) sink; add_sink/rotating-file/overlay — see below
  • Release-build level gating (set_level, compile-time tag)
  • Docs page + example
  • Tests (level threshold round-trip, structured fields, per-level dispatch)

Tests: examples/library/logging.ludic (wired into x test); stderr gating verified by hand (warn/error emit, lower levels suppressed). Docs: new docs/language/log/ section.

Scoped for v1: the pluggable rotating-file sink and in-engine overlay sink (add_sink) are deliberately deferred — the file sink wants the Filesystem library (#10) and the overlay wants a live screen handle. Filing those as follow-ups; the console sink + levels + structured fields cover the common case now.

Done in commit `031b138`. Added the `Log.*` namespace — levelled, structured logging to stderr: - Five levels: `Log.trace`/`debug`/`info`/`warn`/`error(msg, [key, value]...)` — printed as `[LEVEL] msg key=value ...`. - **Structured fields** as trailing key/value pairs; values may be strings, ints, or longs (numbers are formatted automatically). - `Log.set_level(n)` — runtime threshold (0=all default … 4=error, 5 silences all); `Log.level()` reads it back. - The level tag is chosen **at compile time** from the method name, so a filtered-out level costs only a threshold comparison — the release-build gating this issue asks for. Writes to stderr and returns, never touching the simulation, so logging has no effect on determinism/replays. **Acceptance:** - [x] `Log` namespace with levels + structured fields - [x] Console (stderr) sink; `add_sink`/rotating-file/overlay — see below - [x] Release-build level gating (`set_level`, compile-time tag) - [x] Docs page + example - [x] Tests (level threshold round-trip, structured fields, per-level dispatch) Tests: `examples/library/logging.ludic` (wired into `x test`); stderr gating verified by hand (warn/error emit, lower levels suppressed). Docs: new `docs/language/log/` section. Scoped for v1: the pluggable **rotating-file sink** and **in-engine overlay sink** (`add_sink`) are deliberately deferred — the file sink wants the Filesystem library (#10) and the overlay wants a live screen handle. Filing those as follow-ups; the console sink + levels + structured fields cover the common case now.
orkun closed this issue 2026-08-30 20:59:17 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: workshopsoft/ludic#15
No description provided.