Systeric / Docs
Open App →

How to Log

A log line is a message to your future self, written for the night something is broken in production and you can’t reproduce it. That framing decides everything: what to log, at what level, in what shape. Good logging is not “print things as you go.” It’s leaving exactly the trail you’ll need, and nothing that becomes a liability.

Logs are one of the three signals, alongside metrics and traces. A metric tells you how much (error rate is up). A trace tells you where (this procedure, this query). A log tells you what happened, the specific narrative fact: “refund rejected for order 8842 because the amount exceeded the original charge.” Reach for a log when you’ll want that sentence later.


What Deserves a Log#

What’s expected: You log the decisions and events you’d want to reconstruct after the fact, not a running commentary of every line.

Most console.logs are noise: they helped the person who wrote them, once, and now they bury the ten lines that matter. Log the things that carry meaning after the moment has passed:

  • State changes: an order moved to paid, a job started, a user was deactivated. The facts that explain “how did we get into this state?”
  • Decisions: which branch the code took and why, especially the non-obvious ones. “Skipped discount: code expired.”
  • Boundaries: calls to anything outside your control, an external API, a payment provider, with the outcome. That’s where failures hide.
  • Errors, with context: never a bare Error with no surrounding facts. The order id, the input, what you were trying to do.

If you would never grep for it during an incident, it probably shouldn’t be a log. And if it’s a number you’ll want to chart or alert on, a count, a duration, a rate, it’s a metric or a span, not a log. Logs are for discrete facts, not for measurement.


Use Levels Honestly#

What’s expected: The level of a log matches how much a human should care, and error always means someone should look.

LevelMeansExample
errorSomething failed; a human should lookPayment webhook rejected, order left pending
warnOff the happy path, but handledRetried a flaky call and succeeded
infoA meaningful business eventOrder placed, user invited
debugDetail useful only when diggingFull request body, off in production

The failure mode is crying wolf: logging routine things as error so the error stream fills with noise, and the one real error scrolls past unnoticed. If everything is an error, nothing is. error is a promise that something is actually wrong.


Log Fields, Not Sentences#

What’s expected: Your logs carry structured fields you can filter and group by, not prose you can only eyeball.

At 2am you will not read logs; you’ll query them: “show me every failed checkout for this user in the last hour.” That only works if the useful bits are fields, not buried in an English sentence. A message you have to regex is a message you can’t use under pressure.

Bad: console.log("Discount failed for order " + id + " code " + code)

Good: console.log(JSON.stringify({ event: "discount_rejected", orderId, code, reason: "expired" }))

The second one you can filter by orderId, group by reason, and count. The first you can only read. We lean on plain console.* today, which makes this discipline yours to hold: put the identifiers in as fields, name the event consistently, and your logs become queryable instead of just readable.


Correlate With the Trace#

What’s expected: A log can be tied back to the exact request and trace it came from.

A log line alone tells you what; the trace tells you where in the whole request. They’re only powerful together, and they connect through the trace id that OpenTelemetry already runs through every request. Include that id in your logs; don’t invent a parallel request id that lives only in logs and connects to nothing. With the trace id, one error log jumps you straight to the full trace: the query that was slow, the call that failed, the path that got there.


Never Log These#

What’s expected: No secret or personal data ever reaches a log.

Logs get shipped, stored, and read by many people; a log is one of the easiest ways to leak. This is a hard line, not a guideline (see Security & Data Handling):

  • Never: passwords, tokens, API keys, session cookies, full card numbers, raw personal data.
  • Redact: log that a card ending 4242 was charged, not the number. Log a user id, not their email and address.
  • When in doubt, leave it out. A missing field costs you one debugging session. A leaked secret costs a breach.

Related: Metrics vs Traces vs Logs, Observability, Working with Events, Security & Data Handling