How to Do Structured JSON Line Logging in Python

Create a simple JSON-lines logger that writes one JSON object per line to stdout with timestamp, level, message, and custom context fields.

Easy Python 3.6+ Aug 9, 2026 Observability & SRE 15 views 0 copies

Python code

23 lines
Python 3.6+
import json
import sys
from datetime import datetime

class JsonLineLogger:
    def __init__(self, stream=sys.stdout):
        self.stream = stream

    def log(self, level, message, **context):
        record = {
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "level": level,
            "message": message,
            **context
        }
        self.stream.write(json.dumps(record) + "\n")
        self.stream.flush()

if __name__ == "__main__":
    logger = JsonLineLogger()
    logger.log("INFO", "Application started", app="demo", version="1.0.0")
    logger.log("WARNING", "High memory usage", memory_mb=1024, threshold_mb=512)
    logger.log("ERROR", "Failed to connect", host="localhost", port=8080, retries=3)

Output

stdout
{"timestamp": "2025-01-01T00:00:00.000000Z", "level": "INFO", "message": "Application started", "app": "demo", "version": "1.0.0"}
{"timestamp": "2025-01-01T00:00:00.000000Z", "level": "WARNING", "message": "High memory usage", "memory_mb": 1024, "threshold_mb": 512}
{"timestamp": "2025-01-01T00:00:00.000000Z", "level": "ERROR", "message": "Failed to connect", "host": "localhost", "port": 8080, "retries": 3}

How it works

This code builds a dictionary per log call, then serializes it with json.dumps and writes it to the stream followed by a newline. Using json.dumps ensures all context values are converted to valid JSON types (e.g., integers stay numbers, not strings). The flush() call makes each line immediately visible, which matters for real-time tailing or piping to a log collector. The datetime.utcnow().isoformat() plus "Z" gives a UTC timestamp in ISO 8601 format, a common standard for machine logs. By accepting **context, the logger accepts any number of extra fields, making it flexible for production use.

Common mistakes

  • Using `print()` with dict formatting instead of `json.dumps`, which may produce invalid JSON for nested objects.
  • Forgetting to add a newline, causing multiple records to merge into one line and break line-based log parsers.
  • Using `datetime.now()` instead of `datetime.utcnow()` for logs, leading to timezone-inconsistent timestamps.
  • Not flushing the stream, so lines may be buffered and lost on a crash.

Variations

  1. Use `datetime.now(timezone.utc).isoformat()` in Python 3.9+ for a timezone-aware timestamp.
  2. Replace the custom class with a lambda or function that formats and writes directly to a file handler.

Real-world use cases

  • Sending application logs to a log aggregator like ELK or Loki that expects JSON-lines format.
  • Storing audit records in a file where each line is a searchable JSON event for compliance.
  • Streaming user actions or errors to a message queue for real-time monitoring and alerting.

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.