Your backend crashes at 3 AM. You open the logs and find a three-line string, with American dates, truncated messages, and no trace of the user ID that caused the issue. It takes an hour to figure out what happened. With structured JSON logs, you solve that problem in five minutes.
We, at Meteora Web, have managed dozens of production applications for nearly a decade. We've seen every kind of log: from poorly written text to badly done JSON. In this guide, we show you how to do structured JSON logging the right way, with ready-to-copy examples and best practices that actually work.
Why aren't text logs enough for modern applications?
A text log is like a note on a scrap of paper: useful if you read it immediately, useless if you have to search among thousands. Modern applications generate thousands of logs per minute. Searching for an error in a text file is like looking for a needle in a haystack, but blindfolded.
Structured JSON logs solve the problem at the root: each log is an object with defined fields. The message is separated from the context. The timestamp has a standard format. User ID, IP, response time: everything is a queryable field. You can filter, aggregate, and correlate in milliseconds, not hours.
The shift is not just technical; it's economic. Every minute your team spends deciphering unreadable logs is a minute taken away from developing features that drive revenue. We see it every day in the projects we take on: clients who switch to structured logs reduce debugging time by 60-70%. That's not an opinion; it's data we collect from real projects.
Sponsored Protocol
How to move from text logs to JSON logs without rewriting everything
If you have an existing application, you don't have to throw anything away. Most modern frameworks support structured logging with a few lines of configuration. In Node.js, for example, you can use pino, in Python structlog, in PHP Monolog with the JSON formatter. The key is to start with critical points: errors, transactions, API calls.
// Node.js with pino
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
base: { service: 'api-gateway' },
timestamp: pino.stdTimeFunctions.isoTime
});
logger.info({ userId: 1234, action: 'login', ip: '192.168.1.1' }, 'User logged in');
This is a minimal but working example. Each log is a JSON with ISO timestamp, level, message, and context. You can send it to any aggregation system: ELK, Grafana Loki, Datadog, anything.
What fields should a structured JSON log have to be useful?
A JSON log without standard fields is like a half-filled form. To be useful, it must have a minimum structure that answers three questions: what happened, when, and in what context.
The essential fields are: timestamp in ISO 8601 format, level (debug, info, warn, error), message readable, service (service or microservice name), traceId to correlate distributed requests, and contextual fields like userId, requestId, statusCode, durationMs. Don't overdo it: every field you add is a storage cost, but every missing field is a debugging cost.
Sponsored Protocol
We've seen JSON logs with 50 useless fields and logs with only the message. The truth is in the middle: 10-15 well-chosen fields cover 90% of use cases. The rest should be added only if needed.
How to structure the message and context in JSON logs
The message should be short and descriptive, like a headline. The context goes in separate fields. Correct example: "message": "Payment processed", "amount": 99.90, "currency": "EUR", "paymentMethod": "card". Wrong example: "message": "Payment of 99.90 EUR processed via card" — the message becomes a non-queryable string.
Another common mistake is logging sensitive data. Passwords, tokens, credit card numbers: never in logs. We've seen companies logging plaintext passwords for "debugging." It's a security disaster. If you need to log an identifier, use a hash or an opaque ID.
How to handle errors in JSON logs without losing the stack trace?
Errors are the most important use case for logs. An error without a stack trace is like an accident without a police report: you know it happened, but not why. With JSON, you can include the stack trace as a structured field, without mixing it into the message.
Sponsored Protocol
# Python with structlog
import structlog, logging, traceback
logger = structlog.get_logger()
try:
risky_operation()
except Exception as e:
logger.error("Operation failed", exc_info=True, user_id=user.id)
With exc_info=True, structlog automatically includes the stack trace in the exception field of the JSON. You can search by error type, by module, by line. That's a level of diagnostics text logs will never give you.
We, at Meteora Web, resolved a production incident on a server where the automatic SSL certificate renewal had broken. With structured JSON logs, we found the error in 10 minutes: the error field contained the exact code and service indicated the affected module. Without JSON, it would have been an hour of grep and tail.
What tools should you use to aggregate and visualize JSON logs?
A JSON log without an aggregation system is like a database without queries: you can read it, but you can't interrogate it. The most common tools are ELK Stack (Elasticsearch, Logstash, Kibana), Grafana Loki, and cloud services like Datadog or AWS CloudWatch. The choice depends on budget and infrastructure.
For a small business, Grafana Loki is often the best choice: open source, lightweight, integrates with Prometheus and Grafana. If you already use Grafana for metrics, Loki is the natural complement for logs. Integration is immediate: send JSON logs to Loki and view them in Grafana with queries like {service="api-gateway"} |= "error".
Sponsored Protocol
Storage cost is a factor to consider. JSON logs are heavier than text, but Loki's compression and retention policy let you keep costs under control. We always recommend setting a 30-day retention for debug logs and 1 year for errors, if needed for audit.
How to send JSON logs to an aggregator with a simple script
If you don't want to integrate an SDK, you can send JSON logs via HTTP to an endpoint. Here's an example with curl:
curl -X POST http://loki:3100/loki/api/v1/push \
-H "Content-Type: application/json" \
-d '{"streams": [{"stream": {"service": "api-gateway"}, "values": [["'$(date +%s%N)'", "{\"level\":\"error\",\"message\":\"Connection refused\",\"port\":5432}"]]}]}'
This script sends a JSON log to Loki. You can adapt it for any aggregator. The important thing is that the payload is JSON and fields are consistent.
What common mistakes should you avoid when implementing JSON logs?
The first mistake is logging nothing or logging too much. The second is logging sensitive data. The third is not having a standard format between services. The fourth is not setting log rotation, causing full disks. The fifth is ignoring context: a log without traceId in a microservices system is useless for distributed debugging.
We've seen all these mistakes in projects we take on. A client had an e-commerce with huge text logs, no rotation. The server filled up every week. We implemented JSON logging with pino and Loki, set a 30-day retention, and the problem disappeared. The cost? One day of work. The benefit? Zero downtime and 10x faster debugging.
Sponsored Protocol
Another mistake is not testing logs in staging. JSON logs work differently in production, with real volumes. Simulate a load test and verify logs are complete and queryable. We always do this before going to production.
What to do now
Here are concrete actions to implement structured JSON logging in your application:
- Identify critical points: errors, authentication, payments, API calls. Start there.
- Choose a library: pino for Node.js, structlog for Python, Monolog for PHP. Configure the JSON formatter.
- Define common fields: ISO timestamp, level, service, message, traceId, userId. Use them everywhere.
- Set up an aggregator: Grafana Loki if you want open source, Datadog if you have budget. Send JSON logs to it.
- Test in staging: simulate a load test and verify logs are complete and queryable.
Structured JSON logging isn't a fad: it's the minimum standard for anyone managing production applications. If you haven't implemented it yet, you're paying in debugging hours what could be solved in minutes. We, at Meteora Web, use it in all projects we handle, from domain to revenue. If you want to see how it works in practice, check out our guide on monitoring and observability.