django-structlog
django-structlog is a structured logging integration for Django projects that leverages the `structlog` library. It enriches logs with cohesive metadata, simplifying event and incident tracking. The current version is 10.0.0, and the library maintains an active release cadence with multiple updates throughout the year to support new Django and Python versions.
Warnings
- breaking For `django-structlog` v10.0.0+, the `RequestMiddleware` now relies on the `django.dispatch.signal.got_request_exception` signal for exception handling, rather than the older `process_exception` middleware method. This change primarily affects how unhandled exceptions are intercepted and may impact custom exception handling logic.
- breaking When upgrading to `django-structlog` v8.0.0+, the optional signals (`bind_extra_request_metadata`, `bind_extra_request_finished_metadata`, `bind_extra_request_failed_metadata`) now include a new `log_kwargs` argument. If your signal receivers do not accept `**kwargs`, you will need to update their signatures to include `log_kwargs` if you intend to modify the log metadata.
- breaking From `django-structlog` v3.0.0+ onwards, the library transitioned from `structlog.threadlocal` to `structlog.contextvars`. This requires updating your `structlog.configure()` settings to include `structlog.contextvars.merge_contextvars` as the first processor and removing `context_class=structlog.threadlocal.wrap_dict(dict)`. Additionally, all calls to `logger.bind()` should be replaced with `structlog.contextvars.bind_contextvars()`.
- breaking With `django-structlog` v7.0.0+, the `django-ipware` dependency was upgraded to version 6. If you have custom configurations or rely on specific behaviors of `django-ipware` versions prior to 6, this upgrade may introduce breaking changes. Most users should not be affected, but customizations may require adjustments.
- gotcha When using Django REST Framework's `TokenAuthentication` (or similar DRF authentications), the `user_id` may only be present in `request_finished` and `request_failed` log events, rather than in every log produced during the request.
- gotcha Integrating with Celery requires additional configuration beyond the basic `django-structlog` setup. Simply installing `django-structlog[celery]` is not sufficient; specific settings for Celery task logging need to be applied as detailed in the documentation.
Install
-
pip install django-structlog -
pip install django-structlog[celery]
Imports
- get_logger
import structlog logger = structlog.get_logger(__name__)
- RequestMiddleware
from django_structlog.middlewares import RequestMiddleware
- bind_contextvars
from structlog import contextvars contextvars.bind_contextvars(user_id=request.user.id)
Quickstart
import structlog
# settings.py
INSTALLED_APPS = [
# ...
"django_structlog",
# ...
]
MIDDLEWARE = [
# ...
"django_structlog.middlewares.RequestMiddleware",
# ...
]
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json_formatter": {
"()": structlog.stdlib.ProcessorFormatter,
"processor": structlog.processors.JSONRenderer(),
"foreign_pre_chain": [
structlog.contextvars.merge_contextvars,
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
],
},
"plain_console": {
"()": structlog.stdlib.ProcessorFormatter,
"processor": structlog.dev.ConsoleRenderer(),
"foreign_pre_chain": [
structlog.contextvars.merge_contextvars,
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
],
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "plain_console",
},
"json_file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "logs/json.log",
"maxBytes": 1024 * 1024 * 5, # 5 MB
"backupCount": 5,
"formatter": "json_formatter",
},
},
"loggers": {
"django_structlog": {
"handlers": ["console", "json_file"],
"level": "INFO",
"propagate": False,
},
"django": {
"handlers": ["console", "json_file"],
"level": "INFO",
"propagate": False,
},
"my_app": { # Example for your application logs
"handlers": ["console", "json_file"],
"level": "DEBUG",
"propagate": False,
},
"root": {
"handlers": ["console", "json_file"],
"level": "INFO",
},
},
}
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars, # MUST be the first processor
structlog.stdlib.filter_by_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
# views.py (example usage)
import structlog
from django.http import HttpResponse
logger = structlog.get_logger(__name__)
def my_view(request):
logger.info("request_received", path=request.path, method=request.method)
# Your view logic
return HttpResponse("Hello from django-structlog!")