Configure Django's logging system to send structured JSON logs to LogFlow — including request logs, errors, database queries, and Celery task events.
Django's built-in logging is powerful but defaults to plain text output. By adding a custom handler that ships structured JSON logs to LogFlow, you get full-text search, alerting, and anomaly detection across your Django application.
Prerequisites:
pip install requests python-dotenv
We'll use the LogFlow HTTP API directly with requests. No extra library needed.
Create your_project/logflow_handler.py:
import json
import logging
import threading
from datetime import datetime, timezone
from typing import Any
import requests
class LogFlowHandler(logging.Handler):
"""
A logging handler that ships structured log records to LogFlow
via the batch ingestion API. Non-blocking — logs are sent in a
background thread so your application doesn't stall on network issues.
"""
API_URL = "https://api.getlogflow.com/v1/logs/batch"
def __init__(self, api_key: str, service: str, environment: str = "production"):
super().__init__()
self.api_key = api_key
self.service = service
self.environment = environment
self._session = requests.Session()
self._session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
})
def emit(self, record: logging.LogRecord) -> None:
# Run in background thread — never block the main request
thread = threading.Thread(target=self._send, args=(record,), daemon=True)
thread.start()
def _build_payload(self, record: logging.LogRecord) -> dict[str, Any]:
level_map = {
logging.DEBUG: "debug",
logging.INFO: "info",
logging.WARNING: "warn",
logging.ERROR: "error",
logging.CRITICAL: "fatal",
}
attributes: dict[str, str] = {
"logger": record.name,
"module": record.module,
"function": record.funcName,
"line": str(record.lineno),
}
# Attach exception info if present
if record.exc_info:
import traceback
attributes["exception"] = "".join(
traceback.format_exception(*record.exc_info)
)
# Attach any extra fields passed via extra={}
for key, value in record.__dict__.items():
if key.startswith("_") or key in (
"name", "msg", "args", "levelname", "levelno",
"pathname", "filename", "module", "exc_info",
"exc_text", "stack_info", "lineno", "funcName",
"created", "msecs", "relativeCreated", "thread",
"threadName", "processName", "process", "message",
):
continue
attributes[key] = str(value)
return {
"level": level_map.get(record.levelno, "info"),
"message": record.getMessage(),
"service": self.service,
"timestamp": datetime.fromtimestamp(
record.created, tz=timezone.utc
).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3],
"attributes": attributes,
}
def _send(self, record: logging.LogRecord) -> None:
try:
payload = self._build_payload(record)
self._session.post(
self.API_URL,
data=json.dumps({"logs": [payload]}),
timeout=5,
)
except Exception:
# Never let logging errors crash the application
pass
In settings.py, add LogFlow to Django's LOGGING dict:
import os
from dotenv import load_dotenv
load_dotenv()
LOGFLOW_API_KEY = os.environ.get("LOGFLOW_API_KEY", "")
ENVIRONMENT = os.environ.get("DJANGO_ENV", "production")
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "simple",
},
"logflow": {
"()": "your_project.logflow_handler.LogFlowHandler",
"api_key": LOGFLOW_API_KEY,
"service": "django-api",
"environment": ENVIRONMENT,
"level": "INFO", # Don't ship DEBUG to LogFlow
},
},
"formatters": {
"simple": {
"format": "{levelname} {name} {message}",
"style": "{",
},
},
"loggers": {
# Your application logs
"your_app": {
"handlers": ["console", "logflow"],
"level": "DEBUG", # DEBUG locally, INFO filters at handler level
"propagate": False,
},
# Django request logs (access log)
"django.request": {
"handlers": ["logflow"],
"level": "WARNING", # Only 4xx/5xx — too noisy otherwise
"propagate": True,
},
# Database query logs — be careful, this can be very verbose
"django.db.backends": {
"handlers": [], # Enable only when debugging query issues
"level": "DEBUG",
"propagate": False,
},
},
"root": {
"handlers": ["console"],
"level": "WARNING",
},
}
Add your API key to .env:
LOGFLOW_API_KEY=lf_your_api_key_here
DJANGO_ENV=production
Use Python's standard logging module — no LogFlow-specific API in your application code:
import logging
from django.http import JsonResponse
from django.views import View
logger = logging.getLogger("your_app.views")
class OrderView(View):
def get(self, request, order_id: str) -> JsonResponse:
logger.info(
"Fetching order",
extra={
"order_id": order_id,
"user_id": str(request.user.id),
"request_id": request.headers.get("X-Request-Id", ""),
},
)
try:
order = Order.objects.get(id=order_id, user=request.user)
return JsonResponse(order.to_dict())
except Order.DoesNotExist:
logger.warning(
"Order not found",
extra={"order_id": order_id, "user_id": str(request.user.id)},
)
return JsonResponse({"error": "Not found"}, status=404)
except Exception as e:
logger.error(
"Unexpected error fetching order",
exc_info=True,
extra={
"order_id": order_id,
"user_id": str(request.user.id),
},
)
return JsonResponse({"error": "Internal server error"}, status=500)
The exc_info=True flag attaches the full exception traceback to the log — it will appear in the exception attribute in LogFlow.
For request-level logging with duration and status code, add a middleware:
# your_project/middleware.py
import logging
import time
import uuid
logger = logging.getLogger("your_app.requests")
class RequestLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request_id = str(uuid.uuid4())[:8]
request.request_id = request_id
start = time.time()
response = self.get_response(request)
duration_ms = round((time.time() - start) * 1000)
level = logging.ERROR if response.status_code >= 500 else logging.INFO
logger.log(
level,
f"{request.method} {request.path}",
extra={
"method": request.method,
"path": request.path,
"status_code": str(response.status_code),
"duration_ms": str(duration_ms),
"request_id": request_id,
"user_id": str(request.user.id) if request.user.is_authenticated else "",
"ip": request.META.get("REMOTE_ADDR", ""),
},
)
return response
Register it in settings.py:
MIDDLEWARE = [
"your_project.middleware.RequestLoggingMiddleware",
# ... existing middleware ...
]
If you use Celery for background tasks, add LogFlow to Celery's logging config:
# celery.py
from celery import Celery
from celery.signals import setup_logging
app = Celery("your_project")
@setup_logging.connect
def configure_logging(**kwargs):
from django.conf import settings
import logging.config
logging.config.dictConfig(settings.LOGGING)
Then log in your tasks using standard logging:
import logging
from celery import shared_task
logger = logging.getLogger("your_app.tasks")
@shared_task
def send_weekly_report(user_id: int) -> None:
logger.info(
"Starting weekly report",
extra={"user_id": str(user_id), "task": "send_weekly_report"},
)
try:
# ... task logic ...
logger.info(
"Weekly report sent",
extra={"user_id": str(user_id), "task": "send_weekly_report"},
)
except Exception:
logger.error(
"Failed to send weekly report",
exc_info=True,
extra={"user_id": str(user_id), "task": "send_weekly_report"},
)
raise
With structured attributes, you can filter precisely in the Logs Explorer:
service:django-api level:error — all Django errorsuser_id:12345 — everything this user triggeredstatus_code:500 — all 500 errorstask:send_weekly_report level:error — failed Celery tasksCommon Django alerts to create in your Alerts dashboard:
Read Alerting Best Practices for guidance on thresholds and cooldowns.
Related: FastAPI Logging Tutorial · Structured Logging Guide · Alerting Best Practices
Free plan available. No credit card required. Up and running in 2 minutes.
Get started freeKubernetes Logging with LogFlow
Collect structured logs from your Kubernetes pods and send them to LogFlow with service, namespace, and pod metadata automatically attached.
Add Structured Logging to FastAPI in 10 Minutes
Set up structured JSON logging in FastAPI and ship logs to LogFlow with automatic request context and error tracking.
Centralized Logging for NestJS Applications
Replace NestJS's default logger with structured JSON and ship logs to LogFlow for real-time search and alerts.