r/nim 19h ago

lumber: A JSON logger with CLI

Post image
14 Upvotes

Hi all! I've just released lumber, a JSON logger for Nim inspired by pino, and I'd love for you to try it out. I hope to incorporate some feedback before freezing the API for v1.0.0.

Highlights

  • Compile-time log elimination: build with -d:lumberLevel=INFO and calls below the threshold vanish from the binary entirely; runtime-filtered calls never evaluate their arguments either
  • Structured logging: named fields (logger.info("login", user="alice")), per-logger context, child loggers, and thread-local scoped context
  • Exception logging: pass an exception as an argument and its message, type, and stack trace become structured fields
  • Thread-safe: share one module-level logger across threads
  • Flexible outputs: size/time-based file rotation, buffering with level-triggered flush, and a background-thread async writer
  • Middleware: rate limiting, sampling, and redaction (including nested fields) built in, or write your own
  • CLI prettifier: pipe JSON logs through the lumber binary for colored human-readable output with level/field filtering, regex highlighting, and timezone conversion
  • Minimal dependencies: stdlib plus one pure-Nim package, no C libraries

Example

import std/strformat
import lumber

type
  User = object
    name: string

var logger = newLogger(name = "demo")
var user = User(name: "Alice")

logger.trace("This is a trace")
logger.debug(user)
logger.info(&"{user.name} logged in")
logger.warn("Disk usage high", partition="/dev/sda1", usage=92)

proc loadConfig() =
  raise newException(IOError, "file not found: config.toml")

proc initApp() =
  loadConfig()

try:
  initApp()
except IOError as e:
  logger.error("Failed to load config", e)

logger.fatal("Game over")

Output

Raw output

{"timestamp":"2026-07-22T05:03:27.833Z","level":"TRACE","name":"demo","filename":"minimal.nim","line":18,"message":"This is a trace"}
{"timestamp":"2026-07-22T05:03:27.833Z","level":"DEBUG","name":"demo","filename":"minimal.nim","line":19,"message":"User(name: \"Alice\")"}
{"timestamp":"2026-07-22T05:03:27.833Z","level":"INFO","name":"demo","filename":"minimal.nim","line":20,"message":"Alice logged in"}
{"timestamp":"2026-07-22T05:03:27.833Z","level":"WARN","name":"demo","filename":"minimal.nim","line":21,"message":"Disk usage high","extra":{"partition":"/dev/sda1","usage":92}}
{"timestamp":"2026-07-22T05:03:27.833Z","level":"ERROR","name":"demo","filename":"minimal.nim","line":32,"message":"Failed to load config","extra":{"error":"file not found: config.toml","errorType":"IOError","stackTrace":"/Users/cryo/projects/nim/nim-lumber/examples/minimal.nim(30) minimal\n/Users/cryo/projects/nim/nim-lumber/examples/minimal.nim(27) initApp\n/Users/cryo/projects/nim/nim-lumber/examples/minimal.nim(24) loadConfig\n"}}
{"timestamp":"2026-07-22T05:03:27.833Z","level":"FATAL","name":"demo","filename":"minimal.nim","line":34,"message":"Game over"}

Piped through the CLI

2026-07-21T22:04:45.613-07:00 PDT [TRACE] (minimal.nim:18) demo: This is a trace
2026-07-21T22:04:45.613-07:00 PDT [DEBUG] (minimal.nim:19) demo: User(name: "Alice")
2026-07-21T22:04:45.613-07:00 PDT [INFO ] (minimal.nim:20) demo: Alice logged in
2026-07-21T22:04:45.613-07:00 PDT [WARN ] (minimal.nim:21) demo: Disk usage high
  partition: "/dev/sda1"
  usage: 92
2026-07-21T22:04:45.613-07:00 PDT [ERROR] (minimal.nim:32) demo: Failed to load config
  error: "file not found: config.toml"
  errorType: "IOError"
  stackTrace:
    /Users/cryo/projects/nim/nim-lumber/examples/minimal.nim(30) minimal
    /Users/cryo/projects/nim/nim-lumber/examples/minimal.nim(27) initApp
    /Users/cryo/projects/nim/nim-lumber/examples/minimal.nim(24) loadConfig
2026-07-21T22:04:45.613-07:00 PDT [FATAL] (minimal.nim:34) demo: Game over

---

More docs and examples are available in the README.

All feedback is very welcome: ergonomics, missing features, performance, or anything that feels un-Nim-like.