Skip to content
Cloud6 min read

Observability: what to instrument first, and what to stop logging

Most systems produce an enormous volume of logs and cannot answer how long a request took at the ninety-fifth percentile. That combination — over-logged and under-instrumented — is the normal state, and it is expensive in both directions: you pay to store output nobody reads while lacking the signals an incident actually needs.

Part ofCloud, Hosting & IT Infrastructure

The three signal types are not interchangeable and are routinely used for each other's jobs. Counting log lines to derive a rate, or logging every request in order to reconstruct a latency distribution, both work badly and cost more than doing it properly.

What each signal is actually for

SignalAnswersCost shapeWrong use
MetricsHow much, how often, how slow — over timeCheap, bounded by cardinalityStoring per-request detail
LogsWhat happened in this specific caseExpensive, scales with volumeDeriving rates by counting lines
TracesWhere the time went across servicesModerate, usually sampledReplacing metrics for alerting

The division is straightforward once stated. Metrics tell you something is wrong and alert on it. Traces tell you which component is responsible. Logs tell you what happened in the specific failing case. An incident normally moves through all three in that order, and a system missing any one of them makes that path longer.

The four signals worth having before anything else

If a service exposes only four measurements, these are the four. They cover the overwhelming majority of what goes wrong, and they are the ones an on-call engineer needs at 3am.

  1. Latency, as a distribution rather than an average. Percentiles at 50, 95 and 99 — and keep failed requests separate, since a fast error flatters the figure.
  2. Traffic, as requests per second by endpoint. Half of all incidents are explained by a change in the input rate.
  3. Errors, as a rate rather than a count, split by class. Server faults and client rejections are different problems.
  4. Saturation, meaning how full the most constrained resource is — connection pool, queue depth, memory, disk. This is the leading indicator the other three lack.

Saturation is the one most often absent, and the only one of the four that warns before users are affected. A connection pool at 95% utilisation has not caused an error yet and will within minutes under any additional load.

Structured logs, or logs you cannot query

A log line assembled by string interpolation can be read by a person and not queried by a machine. Since the entire reason for centralised logging is querying, the format is not a stylistic choice.

ts
// Unqueryable: the fields exist only inside a sentence.
logger.info(`User ${userId} updated order ${orderId} in ${ms}ms`);

// Queryable: filter by tenant, aggregate durations, group by action.
logger.info({
  event: "order.updated",
  tenant_id: tenantId,
  user_id: userId,
  order_id: orderId,
  duration_ms: ms,
  trace_id: traceId,       // the field that ties logs to the trace
});

// Never in a log line, at any level:
//   passwords, tokens, keys, session identifiers, full card numbers,
//   or personal data beyond an identifier you can resolve internally.
// Logs are copied to more places than any other data in the system.
  • One event name per kind of occurrence, stable over time, so a query written today still works next quarter
  • A trace identifier on every line, which is what makes logs and traces one dataset instead of two
  • Consistent field names across services — tenant_id everywhere, not tenantId in one service and tenant in another
  • Log levels that mean something: warn for a situation needing attention eventually, error for one needing it now

Cardinality is why observability bills explode

Metrics are cheap because they aggregate. Each distinct combination of label values creates a separate series, so a label with unbounded values converts one cheap metric into millions of expensive ones.

LabelDistinct valuesSafe as a metric label?
HTTP methodAbout 7Yes
Status codeTensYes
Endpoint, as a route patternTens to hundredsYes
Endpoint, as a raw URL with idsUnboundedNo — this is the classic mistake
Tenant idGrows foreverOnly with a small, fixed tenant count
User id, request id, trace idUnboundedNever — these belong in logs and traces

The raw-URL row is the single most common cause of a surprise observability bill. Labelling by /orders/8fc1a2 rather than /orders/:id creates a new time series per order, permanently, and the cost grows with traffic rather than with the number of things being measured.

What to stop logging

  • Successful requests at debug level in production. The metric already covers the aggregate; the line adds cost and no information
  • Entry and exit of every function. This is what tracing does, properly and with sampling
  • Full request and response bodies. Expensive, and the fastest route to logging personal data and credentials by accident
  • Handled, expected conditions logged as errors. A validation rejection is not an error; logging it as one destroys the error rate as a signal
  • Anything inside a hot loop. One badly placed line in a frequently executed path can outweigh the rest of the estate combined

The fourth item is worth the emphasis. Once the error log contains routine expected events, nobody can look at the error rate and tell whether something is broken — and the alert built on it gets muted, which is how a system with extensive logging ends up with no working alarm.

The order to build it in

  1. The four signals per service, with latency as percentiles and requests kept separate from errors.
  2. Structured logging with a trace identifier on every line and consistent field names.
  3. Distributed tracing, sampled, once more than two services are involved in a request.
  4. Alerts on the four signals, tied to user-visible symptoms rather than to resource utilisation.
  5. A dashboard per service that answers "is this healthy" in one screen, with no interpretation needed.

Step four contains the discipline that makes the rest usable. An alert on high CPU wakes someone for a condition that may harm nobody; an alert on error rate or latency at the ninety-fifth percentile wakes them for something a user is experiencing. Alert on symptoms, investigate causes.

Questions

What should I instrument first?

Four signals per service: latency as a distribution with failed requests kept separate, traffic as requests per second by endpoint, errors as a rate split by class, and saturation of the most constrained resource. Those cover the large majority of incidents. Saturation is the one most often missing and the only one that warns before users are affected.

Why are percentiles better than average latency?

Because an average describes an experience nobody had. If nine requests take 50ms and one takes five seconds, the average is 545ms — every visitor either had a fast experience or a broken one, and the average hides both. Track the 50th, 95th and 99th percentiles, and exclude failed requests, since a fast error makes the number look better.

Why did our observability bill suddenly increase?

Most often metric cardinality. Each distinct combination of label values is a separate time series, so a label with unbounded values — a raw URL containing record ids, a user id, a request id — turns one cheap metric into millions of expensive ones, growing with traffic rather than with what you are measuring. Label by route pattern, not by the actual path. The other common cause is volume-priced log ingestion of debug output left enabled in production.

Can I alert on log patterns instead of metrics?

You can, and it is fragile. A message change silently disables the alert, log pipelines lag behind real time, and log-based evaluation is expensive to run continuously. Alert on metrics, use traces to find which component is responsible, and use logs to see what happened in the specific failing case.

Want this applied to your systems?

Most of what is described here begins as an assessment: a fixed-scope review producing a prioritised written report you can act on with any supplier.

Prefer email? hello@kornspire.com