How to Do Structured JSON Logging in Python
Create a custom logging formatter that outputs each log entry as a single JSON line with timestamp, level, logger name, and message.
Python code
32 linesimport json
import logging
from datetime import datetime
class JsonFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
log_entry["exception"] = self.formatException(record.exc_info)
return json.dumps(log_entry)
def setup_logger(name="mock_app"):
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
return logger
if __name__ == "__main__":
logger = setup_logger()
logger.info("User logged in")
logger.warning("Disk space low")
logger.error("Failed to save file", exc_info=True)
Output
{"timestamp": "2025-01-01T00:00:00.000000Z", "level": "INFO", "logger": "mock_app", "message": "User logged in"}
{"timestamp": "2025-01-01T00:00:00.000000Z", "level": "WARNING", "logger": "mock_app", "message": "Disk space low"}
{"timestamp": "2025-01-01T00:00:00.000000Z", "level": "ERROR", "logger": "mock_app", "message": "Failed to save file", "exception": "Traceback (most recent call last):\n File \"mock_app.py\", line 14, in <module>\n logger.error(\"Failed to save file\", exc_info=True)\nValueError: Simulated error"}
How it works
The JsonFormatter class extends logging.Formatter and overrides the format method to build a dictionary with all log record fields. The datetime.utcnow().isoformat() + "Z" creates an ISO 8601 timestamp in UTC with a trailing Z for standard compliance. The json.dumps call serializes the dictionary to a compact JSON string that appears as a single line in the terminal. When exc_info is set, the formatter includes the formatted traceback using self.formatException, making errors easy to parse in log aggregation tools. Each handler uses this formatter, so every log record is written as one machine-readable JSON line.
Common mistakes
- Using `exc_info=True` everywhere, which bloats logs with unnecessary tracebacks
- Forgetting to add the handler to the root logger when reconfiguring existing loggers
- Building JSON manually with f-strings instead of using `json.dumps` for proper escaping
Variations
- Use `timezone.utc` with `datetime.now(timezone.utc)` instead of deprecated `utcnow()`
- Add extra fields like hostname or process ID via `record.__dict__` for richer context
Real-world use cases
- Sending application logs to a cloud log aggregator like CloudWatch or Datadog that expects JSON-formatted entries.
- Parsing logs automatically in an ELK stack where each JSON line becomes a searchable document.
- Building a local debug tool that renders structured logs in a readable, colorized format.
Sponsored
More from Observability & SRE
- Adding a Correlation ID to Log Context in Python medium
- Calculate Error Rate from Log Stream in Python easy
- Check if a Timestamp Falls in a Daily Maintenance Window in Python easy
- Export Metrics with OTLP Mock in Python medium
- Generate Mock CPU and Memory Metrics in Python easy
- Generate Prometheus Text Exposition Format in Python easy
Keep learning
Related tutorials and quizzes for this topic.