
Introduction
Logging is one of the most overlooked yet critical aspects of building reliable and scalable applications. In traditional Python applications, developers commonly rely on the built-in
loggingmodule, sprinkling simple log messages likeprint()orlogger.info()throughout their code.
However, as systems become distributed, containerized, cloud-native, or integrated into microservices, structured logging has become a necessity. Structured logs — logs with machine-readable key-value data instead of plain text — allow powerful querying, filtering, and visualization in modern observability tools like ELK (Elasticsearch-Logstash-Kibana), Loki, Datadog, and AWS CloudWatch.
This is where structlog comes in. It’s a modern, high-performance Python library for structured logging that integrates cleanly with the existing logging module but transforms your logs into JSON-ready, structured messages.
This article offers a deep dive into structlog, covering:
Why structured logging matters
How
structlogworks under the hoodIntegrating it with Python’s
loggingCustom processors and contextual logging
JSON logs for modern deployments
Performance considerations
Production-ready logging setups with
structlog
Why Structured Logging?
In traditional applications, logs look like this:
[INFO] User Vinay logged in from IP 192.168.1.1
In modern, distributed systems, this is insufficient. Instead, you'd want:
{
"level": "info",
"event": "user_logged_in",
"user": "Vinay",
"ip": "192.168.1.1",
"timestamp": "2025-06-20T08:00:00Z"
}
Advantages:
Easily query logs by fields (user, IP, timestamp)
Structured, consistent logs for dashboards
Machine-readable (JSON/YAML/Key-Value pairs)
Better correlation in distributed tracing
Simplifies debugging, alerting, and anomaly detection
What is structlog?
structlogis a Python logging library designed for structured logging.
Key features:
Lightweight and fast
Integrates with
loggingor works independentlySupports context-aware, thread-safe logging
Serializes logs into JSON or custom formats
Pluggable processor pipeline to enrich or transform log events
Production-ready for microservices and cloud apps
Installing structlog
Install using pip:
pip install structlog
For JSON logging:
pip install structlog[json]
Basic Usage
A simple example:
import structlog
log = structlog.get_logger()
log.info("user_logged_in", user="Vinay", ip="192.168.1.1")
Output:
2025-06-20 08:00.00 [info ] user_logged_in user=Vinay ip=192.168.1.1
This is human-readable, but you can swap the processor chain to output JSON as well.
How structlog Works
At its core, structlog separates concerns:
Logger binding: Attach context (key-value data) to logger instances
Event processors: Modify, filter, or enrich log events
Renderer: Transform the final log event into a string or JSON before emitting it
This pipeline design makes it extremely flexible.
Integrating structlog with Python’s logging Module
To capture logs from third-party libraries or use existing handlers like RotatingFileHandler, integrate with logging.
Example setup:
import logging
import structlog
logging.basicConfig(
format="%(message)s",
stream=sys.stdout,
level=logging.INFO
)
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.KeyValueRenderer(key_order=["timestamp", "level", "event"]),
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True
)
log = structlog.get_logger()
log.info("user_logged_in", user="Vinay", ip="192.168.1.1")
Output:
timestamp=2025-06-20T08:00:00Z level=info event=user_logged_in user=Vinay ip=192.168.1.1
Structured JSON Logs
In modern deployments, JSON logs are preferred for log aggregators.
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.JSONRenderer()
],
...
)
log = structlog.get_logger()
log.info("user_logged_in", user="Vinay", ip="192.168.1.1")
Output:
{
"timestamp": "2025-06-20T08:00:00Z",
"level": "info",
"event": "user_logged_in",
"user": "Vinay",
"ip": "192.168.1.1"
}
Perfect for ELK stack or CloudWatch ingestion.
Adding Contextual Metadata
Attach global context to loggers, avoiding repeated parameters.
log = structlog.get_logger().bind(service="authentication")
log.info("user_logged_in", user="Vinay")
log.error("invalid_token", user="Kumar")
Every log will now include service=authentication.
Dynamic context via threadlocal is possible using structlog.threadlocal.wrap_dict.
Custom Event Processors
Create processors to modify events at runtime.
Example: Add a UUID to every log.
import uuid
def add_request_id(logger, method_name, event_dict):
event_dict["request_id"] = str(uuid.uuid4())
return event_dict
structlog.configure(
processors=[
add_request_id,
structlog.processors.JSONRenderer()
],
...
)
Now every log has a unique request_id.
Production Logging Setup Example
A complete logging pipeline for a FastAPI microservice or API.
import logging
import structlog
from structlog.stdlib import LoggerFactory
from structlog.processors import JSONRenderer, TimeStamper, add_log_level
from structlog.threadlocal import wrap_dict
logging.basicConfig(
format="%(message)s",
level=logging.INFO
)
structlog.configure(
processors=[
TimeStamper(fmt="iso"),
add_log_level,
JSONRenderer()
],
context_class=wrap_dict(dict),
logger_factory=LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True
)
log = structlog.get_logger().bind(service="api")
log.info("server_started", port=8080)
This setup:
Emits JSON logs
Includes timestamps and levels
Supports dynamic context for API endpoints and job workers
Performance Considerations
structlogis fast and efficient for JSON logging.Disabling logs in production:
Usestructlog.dev.ConsoleRenderer()in dev andJSONRenderer()in prod.Can integrate with
lru_cacheor third-party logging transports.
Migration From logging
Convert existing logging calls easily:
Before:
logger.info("User %s logged in from %s", user, ip)
After:
log.info("user_logged_in", user=user, ip=ip)
Fewer format errors, better structure, no positional argument issues.
Testing Logs
Unit test log outputs via:
from structlog.testing import capture_logs
with capture_logs() as logs:
log.info("test_event", status="ok")
assert logs[0]["status"] == "ok"
Supports clean, testable logging codebases.
Conclusion
structlogis one of Python’s most valuable libraries for modern, structured, JSON-ready logging — yet surprisingly underused in mainstream projects.
It’s a no-brainer choice for:
Microservices
FastAPI / Django apps
Serverless functions
Asynchronous job queues
Cloud-native APIs
Data pipelines and ETL jobs
About the author
I am a Tech Enthusiast having 13+ years of experience in 𝐈𝐓 as a 𝐂𝐨𝐧𝐬𝐮𝐥𝐭𝐚𝐧𝐭, 𝐂𝐨𝐫𝐩𝐨𝐫𝐚𝐭𝐞 𝐓𝐫𝐚𝐢𝐧𝐞𝐫, 𝐌𝐞𝐧𝐭𝐨𝐫, with 12+ years in training and mentoring in 𝐒𝐨𝐟𝐭𝐰𝐚𝐫𝐞 𝐄𝐧𝐠𝐢𝐧𝐞𝐞𝐫𝐢𝐧𝐠, 𝐃𝐚𝐭𝐚 𝐄𝐧𝐠𝐢𝐧𝐞𝐞𝐫𝐢𝐧𝐠, 𝐓𝐞𝐬𝐭 𝐀𝐮𝐭𝐨𝐦𝐚𝐭𝐢𝐨𝐧 𝐚𝐧𝐝 𝐃𝐚𝐭𝐚 𝐒𝐜𝐢𝐞𝐧𝐜𝐞. I have 𝒕𝒓𝒂𝒊𝒏𝒆𝒅 𝒎𝒐𝒓𝒆 𝒕𝒉𝒂𝒏 10,000+ 𝑰𝑻 𝑷𝒓𝒐𝒇𝒆𝒔𝒔𝒊𝒐𝒏𝒂𝒍𝒔 and 𝒄𝒐𝒏𝒅𝒖𝒄𝒕𝒆𝒅 𝒎𝒐𝒓𝒆 𝒕𝒉𝒂𝒏 500+ 𝒕𝒓𝒂𝒊𝒏𝒊𝒏𝒈 𝒔𝒆𝒔𝒔𝒊𝒐𝒏𝒔 in the areas of 𝐒𝐨𝐟𝐭𝐰𝐚𝐫𝐞 𝐃𝐞𝐯𝐞𝐥𝐨𝐩𝐦𝐞𝐧𝐭, 𝐃𝐚𝐭𝐚 𝐄𝐧𝐠𝐢𝐧𝐞𝐞𝐫𝐢𝐧𝐠, 𝐂𝐥𝐨𝐮𝐝, 𝐃𝐚𝐭𝐚 𝐀𝐧𝐚𝐥𝐲𝐬𝐢𝐬, 𝐃𝐚𝐭𝐚 𝐕𝐢𝐬𝐮𝐚𝐥𝐢𝐳𝐚𝐭𝐢𝐨𝐧𝐬, 𝐀𝐫𝐭𝐢𝐟𝐢𝐜𝐢𝐚𝐥 𝐈𝐧𝐭𝐞𝐥𝐥𝐢𝐠𝐞𝐧𝐜𝐞 𝐚𝐧𝐝 𝐌𝐚𝐜𝐡𝐢𝐧𝐞 𝐋𝐞𝐚𝐫𝐧𝐢𝐧𝐠. I am interested in 𝐰𝐫𝐢𝐭𝐢𝐧𝐠 𝐛𝐥𝐨𝐠𝐬, 𝐬𝐡𝐚𝐫𝐢𝐧𝐠 𝐭𝐞𝐜𝐡𝐧𝐢𝐜𝐚𝐥 𝐤𝐧𝐨𝐰𝐥𝐞𝐝𝐠𝐞, 𝐬𝐨𝐥𝐯𝐢𝐧𝐠 𝐭𝐞𝐜𝐡𝐧𝐢𝐜𝐚𝐥 𝐢𝐬𝐬𝐮𝐞𝐬, 𝐫𝐞𝐚𝐝𝐢𝐧𝐠 𝐚𝐧𝐝 𝐥𝐞𝐚𝐫𝐧𝐢𝐧𝐠 new subjects.
Comments
Join the discussionNo comments yet. Be the first to comment.


