Structured Logging

Formats, levels, centralization and best practices for logging in distributed systems.

Why structured logging?

In a monolithic system, reading plain-text logs may be enough. But in a distributed architecture with dozens of services, unstructured logs become unmanageable. Structured logging — where every entry is an object with well-defined fields — is the foundation for being able to search, filter and correlate events at scale.

Log formats

JSON as the standard

The most widely adopted format for structured logs is JSON. Each log line is an object with consistent fields:

{
  "timestamp": "2024-01-15T10:30:00.123Z",
  "level": "INFO",
  "service": "order-service",
  "traceId": "abc123def456",
  "message": "Orden creada exitosamente",
  "orderId": "ORD-789",
  "userId": "USR-456",
  "duration_ms": 145
}

Essential fields

Every structured log should include at a minimum:

  • timestamp: Exact moment in ISO 8601 format with UTC time zone.
  • level: Event severity (DEBUG, INFO, WARN, ERROR, FATAL).
  • service: Name of the service emitting the log.
  • traceId / correlationId: Identifier for correlating logs across services.
  • message: Human-readable description of the event.

Log levels

Log levels let you filter by severity and control the volume of information:

LevelUseExample
DEBUGInternal detail for development”Query executed in 12ms”
INFONormal business events”Order ORD-789 created”
WARNNon-critical anomalous situations”Retry #2 to the payment service”
ERRORFailures that require attention”Timeout connecting to the DB”
FATALThe service cannot continue”Could not start the connection pool”

Best practices with levels

  • Use INFO for significant business events, not for every line of code.
  • Reserve ERROR for situations that genuinely need human intervention.
  • Configure levels dynamically per service — being able to raise to DEBUG in production without a redeploy is invaluable.
  • Avoid logging sensitive data (passwords, tokens, PII) at any level.

Log centralization

The problem of distributed logs

When you have 20 microservices running across multiple instances, logs are scattered across dozens of containers. Without centralization, diagnosing a problem requires manually connecting to each instance.

Centralization stack

The most common pattern is the ELK stack (Elasticsearch, Logstash, Kibana) or its EFK variant (Elasticsearch, Fluentd, Kibana):

  1. Collection: Each service writes logs to stdout/stderr. An agent (Fluentd, Filebeat, Fluent Bit) collects them.
  2. Processing: Logs are parsed, enriched and transformed before being stored.
  3. Storage: Elasticsearch indexes the logs for fast search.
  4. Visualization: Kibana lets you search, filter and build dashboards over the logs.

Modern alternatives

  • Loki (Grafana): Stores logs by indexing only the labels, not the full content. More cost-effective than Elasticsearch for high volumes.
  • CloudWatch Logs (AWS): Managed solution for AWS environments.
  • Datadog Logs: SaaS platform with automatic correlation between logs, metrics and traces.

Best practices

Context, not noise

Every log line should provide useful context. Ask yourself: “If an alert wakes me up at 3 AM, does this log help me understand what happened?”

Correlation IDs

Propagate a traceId or correlationId in every request that crosses multiple services. This lets you reconstruct the full flow of an operation by filtering on a single ID.

Sampling in production

For high-traffic services, logging every request can be expensive. Implement sampling: log 100% of errors but only a percentage of successful requests.

Retention and rotation

Define clear retention policies:

  • ERROR/FATAL logs: 90 days minimum.
  • INFO logs: 30 days.
  • DEBUG logs: Only in development environments or enabled temporarily.

Don’t log sensitive data

Never include in logs: passwords, access tokens, card numbers, personally identifiable information (PII). Use masking or automatic redaction if needed.

Logging in practice

Pattern: Request logging middleware

A middleware that automatically logs every incoming request and its response:

→ REQUEST  | method=POST path=/api/orders traceId=abc123 userId=USR-456
← RESPONSE | method=POST path=/api/orders traceId=abc123 status=201 duration=145ms

Pattern: Error logging with context

When an error occurs, include all the context needed to diagnose it without having to reproduce it:

ERROR | service=payment-service traceId=abc123 orderId=ORD-789
       message="Pago rechazado por el proveedor"
       provider=stripe errorCode=card_declined
       retryCount=0 userId=USR-456

Summary

Structured logging is the first pillar of observability. Well-formatted, centralized logs with proper context let you diagnose problems quickly, understand system behavior and meet audit requirements. The investment in a solid logging strategy pays off many times over the first time you need to investigate a production incident.