Plain text logs break the moment you need to filter, aggregate, or alert. Structured logging with JSON gives you queryable, machine-readable logs from day one.
You've been there. Production is down, someone says "check the logs," and you're running grep "error" app.log | tail -100 on a server. You find 300 lines that say "error" but can't tell which user was affected, which endpoint failed, or when it started.
This is the plain text logging trap — easy to start, impossible to scale.
Instead of this:
2026-07-14 09:23:15 ERROR Payment failed for user 4821 on order ORD-9912
You emit this:
{
"timestamp": "2026-07-14T09:23:15.442Z",
"level": "error",
"message": "Payment failed",
"service": "billing",
"userId": 4821,
"orderId": "ORD-9912",
"provider": "stripe",
"errorCode": "card_declined",
"latencyMs": 2340
}
Same event, but now every field is queryable. You can filter by level = error AND service = billing, aggregate error rates by provider, or alert when errorCode = card_declined spikes above a threshold.
Plain text works for one developer on one service. It breaks as soon as you have:
Multiple services. Each team formats logs differently. One writes [ERROR], another writes level=error, a third uses severity: 3. Good luck correlating across them.
Alerting needs. You want to fire a Slack alert when error rate exceeds 5% for a service. With plain text you need fragile regex parsing. With structured logs you query level = error GROUP BY service.
Debugging under pressure. At 3am you need to find all requests for a specific user that hit a specific endpoint in the last hour. Structured logs: one query. Plain text: prayer.
Pino is the fastest Node.js logger and outputs JSON by default:
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
})
// Every call produces structured JSON
logger.info({ userId: 4821, action: 'login' }, 'User authenticated')
logger.error({ orderId: 'ORD-9912', err }, 'Payment processing failed')
import structlog
logger = structlog.get_logger()
logger.info("user_authenticated", user_id=4821, action="login")
logger.error("payment_failed", order_id="ORD-9912", error=str(e))
Go 1.21+ includes structured logging in the standard library:
slog.Error("payment failed",
"userId", 4821,
"orderId", "ORD-9912",
"error", err,
)
Regardless of language or framework, include these in every log entry:
| Field | Why |
|---|---|
timestamp |
ISO 8601 with milliseconds. Without this, you can't correlate events across services. |
level |
debug, info, warn, error, fatal. Enables filtering and alerting. |
message |
Human-readable summary of the event. Keep it short and consistent. |
service |
Which service emitted the log. Essential in microservice architectures. |
trace_id |
Correlation ID that follows a request across services. Makes distributed debugging possible. |
Everything else — userId, orderId, latencyMs — goes into contextual fields specific to the event.
Logging entire request/response bodies. This bloats volume, risks leaking PII, and makes search slower. Log the fields you need, not the whole object.
Inconsistent field names. If one service uses user_id and another uses userId, aggregation breaks. Pick a convention and enforce it.
Stringifying everything. { "latency": "234" } is a string. { "latency": 234 } is a number you can aggregate with AVG(). Types matter.
Logging at the wrong level. If you log every successful HTTP request at info level, you bury the signal in noise. Consider using debug for successful requests and info only for meaningful state changes.
Once your logs are structured, a log management tool can:
This is exactly what LogFlow is built for. Ship structured JSON logs via our SDK, REST API, or OpenTelemetry, and you get real-time search, anomaly detection, and alerts without managing any infrastructure.
If you're starting fresh, the fastest path:
The gap between "we have logs" and "our logs are useful" is structured logging. Once you cross it, you won't go back.
Free plan available. No credit card required. Up and running in 2 minutes.
Get started freeYou Don't Need Datadog: Log Management for Startups That Ship Fast
Enterprise observability platforms charge enterprise prices. Here's what startups actually need — and why most are overpaying by 10x.
What is Log Management? A Complete Guide
Log management is the process of collecting, storing, and analyzing log data from your applications and infrastructure. Here's everything you need to know.
Node.js Logging Best Practices in 2026
Structured logs, correct log levels, trace IDs, and shipping logs to a centralized service. Here's how to log properly in Node.js.