Set up structured JSON logging in Python using the standard logging module. Ship logs to LogFlow via HTTP API with automatic retries.
This tutorial sets up structured JSON logging in Python and ships logs to LogFlow via the HTTP API. Works with any Python framework — Flask, Django, FastAPI, or plain scripts.
LogFlow doesn't require a special Python SDK. Use the standard requests library (or httpx, urllib3, or even urllib):
pip install requests
Create logflow.py:
import logging
import json
import threading
import queue
import atexit
import requests
LOGFLOW_API_KEY = "lf_your_api_key" # Use env vars in production
LOGFLOW_URL = "https://api.getlogflow.com/v1/logs"
SERVICE_NAME = "my-python-app"
# Background queue for non-blocking log shipping
_log_queue: queue.Queue = queue.Queue(maxsize=10_000)
_stop_event = threading.Event()
class JSONFormatter(logging.Formatter):
"""Format log records as JSON with extra fields."""
def format(self, record: logging.LogRecord) -> str:
log_entry = {
"timestamp": self.formatTime(record, datefmt="%Y-%m-%dT%H:%M:%S.000Z"),
"level": record.levelname.lower(),
"message": record.getMessage(),
"service": SERVICE_NAME,
"module": record.module,
"function": record.funcName,
}
# Include any extra fields passed via logger.info("msg", extra={...})
for key in ("user_id", "request_id", "order_id", "duration_ms", "path", "status", "error", "trace_id"):
if hasattr(record, key):
log_entry[key] = getattr(record, key)
return json.dumps(log_entry)
class LogFlowHandler(logging.Handler):
"""Sends log records to LogFlow API in a background thread."""
def emit(self, record: logging.LogRecord):
try:
msg = self.format(record)
_log_queue.put_nowait(msg)
except queue.Full:
pass # Drop logs rather than blocking the app
def _flush_worker():
"""Background thread that batches and sends logs."""
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {LOGFLOW_API_KEY}",
"Content-Type": "application/json",
})
while not _stop_event.is_set():
batch = []
try:
# Wait up to 2 seconds for the first item
item = _log_queue.get(timeout=2)
batch.append(json.loads(item))
except queue.Empty:
continue
# Drain up to 99 more items
while len(batch) < 100:
try:
item = _log_queue.get_nowait()
batch.append(json.loads(item))
except queue.Empty:
break
if batch:
try:
session.post(LOGFLOW_URL, json=batch, timeout=5)
except requests.RequestException:
pass # Log shipping should never crash the app
# Final flush on shutdown
batch = []
while not _log_queue.empty():
try:
batch.append(json.loads(_log_queue.get_nowait()))
except queue.Empty:
break
if batch:
try:
session.post(LOGFLOW_URL, json=batch, timeout=5)
except requests.RequestException:
pass
# Start background thread
_worker = threading.Thread(target=_flush_worker, daemon=True)
_worker.start()
def _shutdown():
_stop_event.set()
_worker.join(timeout=5)
atexit.register(_shutdown)
def get_logger(name: str) -> logging.Logger:
"""Get a configured logger that outputs JSON and ships to LogFlow."""
logger = logging.getLogger(name)
if not logger.handlers:
# Console handler (JSON to stdout)
console = logging.StreamHandler()
console.setFormatter(JSONFormatter())
logger.addHandler(console)
# LogFlow handler (background shipping)
logflow = LogFlowHandler()
logflow.setFormatter(JSONFormatter())
logger.addHandler(logflow)
logger.setLevel(logging.INFO)
return logger
from logflow import get_logger
logger = get_logger(__name__)
# Basic logging
logger.info("server started", extra={"port": 8000})
# With context
logger.info("order.created", extra={
"order_id": "ORD-1234",
"user_id": 42,
"amount": 99.00,
})
# Errors with exception info
try:
process_payment(order)
except Exception as e:
logger.error("payment.failed", extra={
"order_id": order.id,
"error": str(e),
}, exc_info=True)
For Flask, Django, or FastAPI, add a middleware that attaches request ID and user info to every log:
import contextvars
import uuid
request_id_var = contextvars.ContextVar("request_id", default="-")
user_id_var = contextvars.ContextVar("user_id", default=None)
# Flask middleware example
from flask import Flask, request, g
app = Flask(__name__)
@app.before_request
def set_request_context():
g.request_id = request.headers.get("X-Request-ID", str(uuid.uuid4())[:8])
request_id_var.set(g.request_id)
@app.after_request
def log_request(response):
logger.info("request.handled", extra={
"request_id": g.request_id,
"path": request.path,
"method": request.method,
"status": response.status_code,
})
return response
For framework-specific details, see:
Keep noisy libraries quiet:
import logging
# Quiet third-party noise
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)
# Verbose for your code when debugging
logging.getLogger("myapp.payments").setLevel(logging.DEBUG)
Open LogFlow Logs Explorer and search:
service:my-python-app
You should see your structured logs with all extra fields searchable and filterable.
Free plan available. No credit card required. Up and running in 2 minutes.
Get started freeStructured Logging in Flask
Add production-ready structured logging to any Flask app in under 10 minutes.
Structured Logging in Laravel
Add production-ready structured logging to Laravel and ship logs to LogFlow.
Logging in AWS Lambda with LogFlow
AWS Lambda logs disappear into CloudWatch. Here's how to ship structured logs to LogFlow instead.