Logs Required
Log structured events
Write each log entry as one line of structured data, such as JSON, with a time, a level, an event name and its fields.
How to do it
Use a logging library that writes JSON, such as pino, structlog or Monolog. Write the time in UTC as ISO 8601 with milliseconds, and keep server clocks synced with NTP. Name events with fixed words, like order.paid, and put values in fields, not in the message text. Write to standard output and let the platform collect it, as the Twelve-Factor App advises.
Example
import pino from 'pino';
const log = pino({
timestamp: pino.stdTimeFunctions.isoTime,
formatters: { level: label => ({ level: label }) },
messageKey: 'event',
});
log.warn({ request_id: '7f3c2a9e', order: 'A-1042', attempt: 2 }, 'payment.retry');
import structlog
structlog.configure(processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso", utc=True, key="time"),
structlog.processors.JSONRenderer(),
])
structlog.get_logger().warning("payment.retry", request_id="7f3c2a9e", order="A-1042", attempt=2)
{"time":"2026-11-02T09:15:04.120Z","level":"warn","event":"payment.retry","request_id":"7f3c2a9e","order":"A-1042","attempt":2}
journalctl -u shop --since -1h -o cat | jq -s -e 'all(.time and .level and .event)' > /dev/null && echo PASS
How to check
Pipe one hour of logs through a JSON parser, such as jq. Every line parses, and every line has a time, a level and an event.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers operations playbook: https://agentwranglers.se/playbook/operations/logs/structured-logs.