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.

Easy Python 3.9+ Aug 9, 2026 Observability & SRE 14 views 0 copies

Python code

32 lines
Python 3.9+
import 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

stdout
{"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

  1. Use `timezone.utc` with `datetime.now(timezone.utc)` instead of deprecated `utcnow()`
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Observability & SRE

Related tutorials and quizzes for this topic.