Structured logs
Two million lines a day
Shard Garden's match servers write logs like this:
Player Moss joined garden 7f3a (eu)
Priya, who runs observability, needs every line about garden 7f3a from yesterday. The servers wrote two million lines, in five different sentence styles. A search for "7f3a" also finds level names, chat and random IDs.
Sentences are made for people. At this size, Priya needs logs made for computers.
Structured logs
OpenTelemetry defines a log as a timestamped text record, structured (recommended) or unstructured.
- Unstructured: free text. Easy for people to read, hard to analyse at scale.
- Structured: a stable schema, the same field names, types and meanings every time.
{"timestamp":"2026-10-11T19:04:12.338Z", "level":"INFO", "service":"match-server", "event":"garden.join", "garden_id":"7f3a"}
Now "every garden.join in garden 7f3a" is an exact question.
Quick check
A log line written as valid JSON is always a structured log.
- True
- False
Show the answer
False
OpenTelemetry says structure comes from a stable schema, not from being JSON. If one server writes garden_id and another writes gardenId, a search for one misses the other.
When, where, who and what
OWASP, an open security project, says every log event must record when, where, who and what:
- When: the date and time in an international format, like the one above, in UTC.
- Where: the app's name and version, like
match-server 1.4.2. - Who: the user, if known. For us, an internal player ID, never a name.
- What: the type of event, like
garden.save.failed.
Bug hunt
1log.info('garden.join', {2 garden_id: g.id });3log.info('garden.leave', {4 gardenId: g.id });5log.info('garden.save', {6 garden_id: g.id });
Priya searches for every event in garden 7f3a, and the leaves are missing. Which line is the bug?
- Line 2
- Line 4
- Line 6
Show the answer
Line 4
Line 4 writes gardenId while the others write garden_id. The schema must be the same everywhere, or searches quietly miss events.
Logs that know their trace
An OpenTelemetry log record has fields like Timestamp, SeverityText (the log level), Body, Attributes (extra details) and Resource (which service sent it), plus TraceId and SpanId.
When the OpenTelemetry SDK is running, it links each log to the active trace automatically. So from one "save failed" line, Priya can jump to the whole journey of that request across every service.
Players can write in your logs
Some fields come from players: level names, garden names, chat. A player could name a level Moss, then a line break, then ERROR admin deleted all gardens.
In a plain text log, that line break makes a fake second line. This is log injection. OWASP says to sanitise all event data, including carriage returns, line feeds and delimiter characters, and to encode it correctly for the log format. A structured logger stores the whole name as one value.
Quick check
A player names a level with a line break followed by a fake ERROR message. What stops it faking a log line?
- Logging the name as one encoded value inside a structured log
- Logging it at DEBUG level
- Writing the name in capital letters
Show the answer
Logging the name as one encoded value inside a structured log
Encoded as one field, the line break is part of the value, not the start of a new line. OWASP lists sanitising carriage returns, line feeds and delimiters for exactly this.
Not every line matters equally
Structured logs can be searched. The next questions: which events deserve a log at all, how loudly, and what must never be written down.
Bolt says: Next: log levels, and the things no log may ever hold.
Fact-checked 11 October 2026
- OpenTelemetry: Logs opentelemetry.io
- OWASP: Logging cheat sheet cheatsheetseries.owasp.org