Logging¶
Django-Bolt provides a comprehensive logging system with queue-based non-blocking logging, configurable log levels, request/response logging, and header obfuscation.
Quick start¶
Create a logging middleware with default settings:
Or configure it with custom options:
from django_bolt.logging import LoggingConfig, LoggingMiddleware
config = LoggingConfig(
logger_name="myapp.api",
skip_paths={"/health", "/metrics"},
sample_rate=0.1, # Log 10% of successful requests
)
middleware = LoggingMiddleware(config)
LoggingConfig¶
The LoggingConfig class controls all logging behavior.
Basic options¶
from django_bolt.logging import LoggingConfig
config = LoggingConfig(
# Logger name (default: "django.server")
logger_name="myapp.api",
# Log level (default: "INFO")
log_level="DEBUG",
# Skip these paths (default includes health checks)
skip_paths={"/health", "/ready", "/metrics"},
# Skip these status codes
skip_status_codes={204, 304},
)
Request logging fields¶
Control which request fields are logged:
config = LoggingConfig(
request_log_fields={
"method", # HTTP method
"path", # Request path
"query", # Query parameters
"headers", # Request headers (obfuscated)
"body", # Request body
"client_ip", # Client IP address
"user_agent", # User agent string
}
)
Default: {"method", "path", "status_code"}
Response logging fields¶
Control which response fields are logged:
config = LoggingConfig(
response_log_fields={
"status_code", # HTTP status code
"duration", # Response time in ms
"size", # Response size in bytes
}
)
Security: Header obfuscation¶
Sensitive headers are automatically obfuscated:
config = LoggingConfig(
obfuscate_headers={
"authorization",
"cookie",
"x-api-key",
"x-auth-token",
}
)
These headers appear as "***" in logs:
Security: Cookie obfuscation¶
Sensitive cookies are also obfuscated:
Request body logging¶
Enable request body logging with size limits:
config = LoggingConfig(
log_request_body=True,
max_body_log_size=1000, # Skip bodies larger than 1KB
)
Binary data is logged as size information:
Production settings¶
For production, use sampling and duration thresholds to reduce log volume:
config = LoggingConfig(
# Only log 5% of successful requests
sample_rate=0.05,
# Only log requests taking longer than 250ms
min_duration_ms=250,
)
Important: Errors (4xx, 5xx) are always logged regardless of sampling or duration settings.
Sample rate¶
sample_rate controls what percentage of 2xx/3xx responses are logged:
1.0= Log all requests (default)0.1= Log 10% of successful requests0.0= Don't log any successful requests
Minimum duration¶
min_duration_ms skips logging fast successful requests:
A request that completes in 100ms won't be logged unless it's an error.
Log levels¶
Django-Bolt uses appropriate log levels based on status codes:
| Status Code | Log Level | Description |
|---|---|---|
| 2xx | INFO | Successful requests |
| 3xx | INFO | Redirects |
| 4xx | WARNING | Client errors |
| 5xx | ERROR | Server errors |
Requests are logged at DEBUG level (before processing). Responses are logged at the level above.
LoggingMiddleware¶
Using with handlers¶
from django_bolt import BoltAPI
from django_bolt.logging import LoggingConfig, LoggingMiddleware
config = LoggingConfig(
logger_name="myapp.api",
skip_paths={"/health"},
)
middleware = LoggingMiddleware(config)
# Log request
middleware.log_request({
"method": "POST",
"path": "/api/users",
"headers": {"content-type": "application/json"},
})
# Log response
middleware.log_response(
request={"method": "POST", "path": "/api/users"},
status_code=201,
duration=0.05, # seconds
)
Exception logging¶
try:
# handler code
pass
except Exception as e:
middleware.log_exception(
request={"method": "GET", "path": "/api/error"},
exception=e,
exc_info=True, # Include traceback
)
Custom exception handler¶
def my_exception_handler(logger, request, exception, exc_info):
# Send to error tracking service
sentry_sdk.capture_exception(exception)
# Still log it
logger.error(f"Exception: {exception}", exc_info=exc_info)
config = LoggingConfig(
exception_logging_handler=my_exception_handler
)
Client IP extraction¶
The middleware extracts client IP from proxy headers:
X-Forwarded-For(first IP in chain)X-Real-IP- Falls back to connection IP
config = LoggingConfig(
request_log_fields={"method", "path", "client_ip"}
)
# Logs: GET /api/users - client_ip: 192.168.1.1
Queue-based logging¶
Django-Bolt uses queue-based logging to prevent I/O blocking:
from django_bolt.logging.config import setup_django_logging
# Called automatically by runbolt
setup_django_logging()
This creates a QueueHandler that offloads log writing to a background thread, ensuring log I/O doesn't block request handling.
Django integration¶
With Django's LOGGING setting¶
If you have a custom LOGGING configuration in settings.py, Django-Bolt respects it:
# settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"django.server": {
"handlers": ["console"],
"level": "INFO",
},
},
}
Without custom LOGGING¶
If no LOGGING is configured, Django-Bolt sets up sensible defaults with queue-based handlers.
Example: Complete production setup¶
from django_bolt import BoltAPI
from django_bolt.logging import LoggingConfig, create_logging_middleware
api = BoltAPI()
# Production logging config
config = LoggingConfig(
logger_name="myapp.api",
# Performance: Don't log every request
sample_rate=0.1, # Log 10% of successful requests
min_duration_ms=100, # Only log requests > 100ms
# Skip noisy paths
skip_paths={"/health", "/ready", "/metrics", "/favicon.ico"},
skip_status_codes={204, 304},
# Request logging
request_log_fields={"method", "path", "client_ip", "user_agent"},
# Response logging
response_log_fields={"status_code", "duration"},
# Security
obfuscate_headers={"authorization", "cookie", "x-api-key"},
obfuscate_cookies={"sessionid", "csrftoken", "jwt"},
# Body logging (careful in production)
log_request_body=False,
)
middleware = create_logging_middleware(
logger_name=config.logger_name,
skip_paths=config.skip_paths,
sample_rate=config.sample_rate,
)
Checking log configuration¶
config = LoggingConfig()
# Check if a path should be logged
config.should_log_request("/api/users") # True
config.should_log_request("/health") # False
# Check with status code
config.should_log_request("/api/users", 204) # False if 204 in skip_status_codes
# Get the logger
logger = config.get_logger()