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:
| Level | Use | Example |
|---|---|---|
| DEBUG | Internal detail for development | ”Query executed in 12ms” |
| INFO | Normal business events | ”Order ORD-789 created” |
| WARN | Non-critical anomalous situations | ”Retry #2 to the payment service” |
| ERROR | Failures that require attention | ”Timeout connecting to the DB” |
| FATAL | The 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):
- Collection: Each service writes logs to stdout/stderr. An agent (Fluentd, Filebeat, Fluent Bit) collects them.
- Processing: Logs are parsed, enriched and transformed before being stored.
- Storage: Elasticsearch indexes the logs for fast search.
- 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.