Free preview

Requirements checklist

Ask, one at a time, absorbing each answer:

□ levels + WHERE filtering lives      two gates (logger + destination)?
□ destinations, several at once?      forces a real dispatch layer
□ formats differ per destination?     forces formatter/appender split
□ structured fields — bindable        request-scoped context objects
  to a logger, or per-message only?
□ cost of a SUPPRESSED message?       dictates order of operations in log()
□ threads + ordering promises?        per-thread order at one destination
□ config: startup or runtime?         startup-only buys immutable wiring

The core model

Logger        name, minLevel (gate #1), boundFields (immutable),
              shared appender list
LogEvent      timestamp, level, loggerName, message, merged fields,
              threadId — immutable, built ONCE, after gate #1
Appender      minLevel (gate #2) + append(event); owns its Formatter
Formatter     event -> text; any format pairs with any destination
log() flow    gate FIRST -> merge fields (per-call wins, one line)
              -> build event -> for each appender: filter, format, write
with(fields)  returns a NEW child logger — context never mutates a
              shared logger
lazy form     debug(supplier) — supplier never runs if suppressed

Invariants: event immutable after construction · suppressed call allocates nothing · the field-merge collision rule exists in exactly one line · appenders serialize their own writes.

Principles demonstrated (name them at the decision)

  • Single responsibility — formatting and writing are different jobs; the split is why destination #4 costs one class.
  • Open/closed — new destination = new Appender; nothing else touched.
  • Immutability as thread-safety — events shared freely across appenders; child loggers make context aliasing unrepresentable.

Complexity facts

suppressed message   O(1) compare, ZERO allocation — because the gate
                     precedes construction (placement, not luck)
emitted message      O(fields) merge once + O(appenders) dispatch
                     + per-appender formatting (only past its gate)
per-destination      synchronized write = ordering promise; contention
                     only when many threads hit one appender

What earns points, per report dimension

  • Requirements & interface — the two-gates question asked; destinations, fields, hot-path cost, and ordering promises pinned before design.
  • Core design & invariants — three layers each standing on a requirement; event as immutable value built once; gate-before-construction stated as a rule, not an accident; child-logger context with a defined merge order.
  • Extension probe — the round will move the requirements; points come from locating the change in one layer and saying why the other layers were protected by the seams.
  • Complexity honesty — the suppressed-message cost claimed with its placement justification; emitted-path costs itemized per layer.
  • Communication — layering justified from requirements as you draw it, rejections narrated, probes answered directly.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue