A log line that is just a formatted string is a log line HelixQL has to parse at query time, every time. A few naming conventions fix that permanently.

The difference between a log line that queries in milliseconds and one that requires a regex scan across a week of data usually comes down to whether it was written as structured fields from the start, not whether it was written well.

1. Emit fields, not formatted strings

A message like "order 4821 failed for user 992 after 3 retries" is readable to a person and nearly useless to a query engine. The same information as fields is queryable directly, with no string parsing at all.

logger.error('order failed', {
  order_id: 4821,
  user_id: 992,
  retry_count: 3,
});
from logs
| where message == "order failed"
| where retry_count >= 3
| summarize count() by user_id

2. Keep field names consistent across services

If one service logs user_id and another logs userId or uid, every cross-service query needs to know and handle all three, which nobody remembers to do under incident pressure. Publish a short naming convention document and treat it the same as an API contract.

# field naming convention (excerpt)
user_id       # not userId, uid, user
order_id      # not orderId, order
duration_ms   # not duration, latency, elapsed

3. Put the stable identifier in a field, not just the trace context

Trace ID correlation is powerful, but not every useful query starts from a trace. A support engineer investigating one customer complaint wants to filter logs by order_id directly, without first finding a trace ID to join through.

from logs
| where order_id == 4821
| sort by timestamp asc

4. Avoid high-cardinality fields as indexed dimensions

Structured does not mean every field should be treated as a queryable dimension at the storage layer. Free-text fields like a full stack trace or a raw request body should stay as payload, not become part of the index, or the same cardinality problem that affects metrics shows up in logs too.

  • Emit fields, not formatted message strings, so queries never need to parse text.
  • Standardize field names across services in a short, enforced naming convention.
  • Include stable business identifiers as fields, not only reachable through a trace join.
  • Keep free-text payload out of the indexed dimension set to avoid a cardinality blowup.