Logging

Structured Logging: Why JSON Logs Make Debugging Faster.

Written by noc724 team3 min readUpdated Oct 18, 2026

A plain-text log line is written for a human reading one line at a time. Production debugging rarely works that way: you search millions of lines for one order ID, count errors per endpoint, or follow one request across five services. Structured logs - usually JSON - make each value a field that tools can query directly.

Key takeaways

  • Structured logs record events as key-value fields instead of free text.
  • Fields make filtering, counting and correlating fast and reliable.
  • Include a timestamp, level, service, message and a request/trace ID in every event.
  • Keep field names consistent across services.
  • Never log secrets or personal data you don't need.
Downtime happens. Get notified!

noc724 checks your websites, servers and APIs around the clock and alerts you by SMS, Telegram or Bale the moment something breaks.

Register for FREE

Plain text vs structured

Plain text:

2026-10-18 09:14:03 ERROR Payment failed for order 58114 after 10002ms (gateway timeout)

Structured:

{"ts":"2026-10-18T09:14:03.211Z","level":"error","service":"payments","msg":"payment failed","order_id":58114,"duration_ms":10002,"reason":"gateway_timeout","request_id":"7f3c2a"}

With the second form, "all failed payments over 5 seconds in the last hour, grouped by reason" is a query, not a regular expression you hope is right.

Fields worth standardising

FieldPurpose
tsISO 8601 timestamp in UTC
leveldebug / info / warn / error / fatal
servicewhich application wrote it
msga short, constant message ("payment failed"), not a sentence with values inside
request_id / trace_idties together everything one request did
duration_mshow long the operation took
domain IDsuser_id, order_id - whatever you search by
errorerror type and message; stack trace as its own field

The key rule: keep the message constant and put variables in fields. "payment failed" with order_id: 58114 groups perfectly; "Payment failed for order 58114" creates a unique message per order.

Examples

Python

import logging, json, sys

class JsonFormatter(logging.Formatter):
    def format(self, record):
        event = {"ts": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"), "level": record.levelname.lower(),
                 "service": "payments", "msg": record.getMessage()}
        event.update(getattr(record, "fields", {}))
        return json.dumps(event)

handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
log = logging.getLogger("payments")
log.addHandler(handler)
log.setLevel(logging.INFO)

log.error("payment failed", extra={"fields": {"order_id": 58114, "reason": "gateway_timeout"}})

Go (log/slog)

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Error("payment failed", "order_id", 58114, "reason", "gateway_timeout", "duration_ms", 10002)

Node.js

console.log(JSON.stringify({ ts: new Date().toISOString(), level: "error", msg: "payment failed", order_id: 58114 }));

In real projects use an established library (structlog, pino, zap and similar) rather than hand-written formatting.

Correlating requests

Generate a request ID at the edge (load balancer or first service), pass it on in a header such as X-Request-ID or the W3C traceparent, and log it in every event. One search then shows the whole story of a failing request across services - the basis of observability.

Pitfalls

  • Secrets and personal data. Mask passwords, tokens, card numbers and full phone numbers. Structured logs make it easy to add a redaction step for known field names.
  • Field explosions. Don't use values as keys ({"user_58114": ...}); log platforms index every key.
  • Inconsistent names. userId, user_id and uid in three services make cross-service search painful. Agree on a short schema.
  • Huge payloads. Logging whole request bodies multiplies storage cost; log sizes and IDs instead.

Frequently asked questions

  • Slightly. Tools such as jq or a log viewer format them nicely, and many libraries print readable output in development and JSON in production.

  • No. Even with files on one server, jq can filter and count JSON logs far more reliably than grep. A central platform adds search across servers and retention.

  • They complement each other. Metrics are cheap and great for alerting and trends; logs explain individual events. Count in metrics, explain with logs.

Share this article:XTelegramLinkedInFacebookReddit
noc724

Written by

The noc724 team

Uptime monitoring engineers

We build and run noc724, an uptime monitoring service that checks websites, servers and APIs from several cities in Iran. These guides come from the problems we and our users run into while keeping services online.

Contact the team

Expert on:Logging

Questions?Contact Support
Feature suggestions?Share

Recent Articles