python-observability
wshobson/agents
Structured logging, metrics, and distributed tracing patterns for Python applications.
What is python-observability?
Instrument Python applications with structured logs, metrics, and traces to answer "what, where, and why" when production issues occur. Use this skill when adding logging, implementing metrics collection, setting up tracing, or debugging production systems.
- Configure structured JSON logging with consistent fields using structlog
- Propagate correlation IDs through request chains for end-to-end tracing
- Track the four golden signals: latency, traffic, errors, and saturation
- Implement semantic log levels (DEBUG, INFO, WARNING, ERROR) consistently
- Set up context variables to thread observability data through async operations
- Bind observability context to logs automatically without polluting business logic
How to install python-observability
npx skills add https://github.com/wshobson/agents --skill python-observability- Python 3.7+
- structlog library (pip install structlog)
- Optional: Prometheus client for metrics collection
- Optional: OpenTelemetry libraries for distributed tracing
How to use python-observability
- 1.Call configure_logging() at application startup to initialize structlog with JSON output
- 2.Generate or extract a correlation ID at ingress (e.g., from request headers) and set it with set_correlation_id()
- 3.Use structlog.get_logger() to obtain a logger and emit logs with consistent fields (correlation_id, user_id, etc.)
- 4.Add middleware to your web framework to automatically set correlation IDs and propagate them to downstream services
- 5.Include context variables in log entries using structlog.contextvars.bind_contextvars()
- 6.Log at appropriate semantic levels (DEBUG for diagnostics, INFO for operations, WARNING for anomalies, ERROR for failures)
- 7.Propagate correlation IDs to outbound HTTP requests via X-Correlation-ID headers
Use cases
- Adding structured logging to a new Python service for production debugging
- Implementing correlation ID propagation across microservices to trace requests end-to-end
- Setting up metrics collection with Prometheus to monitor service health
- Debugging production issues by querying JSON logs with consistent field names
- Configuring FastAPI middleware to capture and propagate request context
- Backend engineers building production Python services
- DevOps engineers setting up observability for microservices
- Full-stack developers debugging distributed systems
- Teams implementing SRE practices and monitoring
python-observability FAQ
Use DEBUG for development diagnostics like variable values and internal state. Use INFO for request lifecycle events and normal operations like request start/end or job completion. Never log expected behavior (like wrong passwords) at ERROR level.
Keep metric label values bounded. Never use unbounded values like user IDs as metric labels, as this explodes storage costs. Use correlation IDs in logs instead, which are naturally bounded.
Use Python's contextvars module to store values like correlation IDs. These automatically propagate through async/await boundaries. Bind them to structlog using structlog.contextvars.bind_contextvars() so they appear in all logs.
Latency (how long requests take), traffic (request volume), errors (failure rate), and saturation (resource utilization). Track these for every service boundary to understand system health.
No. Use context managers, middleware, and context variables to keep observability code separate from business logic. This makes testing easier and keeps code clean.
Full instructions (SKILL.md)
Source of truth, from wshobson/agents.
name: python-observability description: Python observability patterns including structured logging, metrics, and distributed tracing. Use when adding logging, implementing metrics collection, setting up tracing, or debugging production systems.
Python Observability
Instrument Python applications with structured logs, metrics, and traces. When something breaks in production, you need to answer "what, where, and why" without deploying new code.
When to Use This Skill
- Adding structured logging to applications
- Implementing metrics collection with Prometheus
- Setting up distributed tracing across services
- Propagating correlation IDs through request chains
- Debugging production issues
- Building observability dashboards
Core Concepts
1. Structured Logging
Emit logs as JSON with consistent fields for production environments. Machine-readable logs enable powerful queries and alerts. For local development, consider human-readable formats.
2. The Four Golden Signals
Track latency, traffic, errors, and saturation for every service boundary.
3. Correlation IDs
Thread a unique ID through all logs and spans for a single request, enabling end-to-end tracing.
4. Bounded Cardinality
Keep metric label values bounded. Unbounded labels (like user IDs) explode storage costs.
Quick Start
import structlog
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
)
logger = structlog.get_logger()
logger.info("Request processed", user_id="123", duration_ms=45)
Fundamental Patterns
Pattern 1: Structured Logging with Structlog
Configure structlog for JSON output with consistent fields.
import logging
import structlog
def configure_logging(log_level: str = "INFO") -> None:
"""Configure structured logging for the application."""
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.make_filtering_bound_logger(
getattr(logging, log_level.upper())
),
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=True,
)
# Initialize at application startup
configure_logging("INFO")
logger = structlog.get_logger()
Pattern 2: Consistent Log Fields
Every log entry should include standard fields for filtering and correlation.
import structlog
from contextvars import ContextVar
# Store correlation ID in context
correlation_id: ContextVar[str] = ContextVar("correlation_id", default="")
logger = structlog.get_logger()
def process_request(request: Request) -> Response:
"""Process request with structured logging."""
logger.info(
"Request received",
correlation_id=correlation_id.get(),
method=request.method,
path=request.path,
user_id=request.user_id,
)
try:
result = handle_request(request)
logger.info(
"Request completed",
correlation_id=correlation_id.get(),
status_code=200,
duration_ms=elapsed,
)
return result
except Exception as e:
logger.error(
"Request failed",
correlation_id=correlation_id.get(),
error_type=type(e).__name__,
error_message=str(e),
)
raise
Pattern 3: Semantic Log Levels
Use log levels consistently across the application.
| Level | Purpose | Examples |
|---|---|---|
DEBUG | Development diagnostics | Variable values, internal state |
INFO | Request lifecycle, operations | Request start/end, job completion |
WARNING | Recoverable anomalies | Retry attempts, fallback used |
ERROR | Failures needing attention | Exceptions, service unavailable |
# DEBUG: Detailed internal information
logger.debug("Cache lookup", key=cache_key, hit=cache_hit)
# INFO: Normal operational events
logger.info("Order created", order_id=order.id, total=order.total)
# WARNING: Abnormal but handled situations
logger.warning(
"Rate limit approaching",
current_rate=950,
limit=1000,
reset_seconds=30,
)
# ERROR: Failures requiring investigation
logger.error(
"Payment processing failed",
order_id=order.id,
error=str(e),
payment_provider="stripe",
)
Never log expected behavior at ERROR. A user entering a wrong password is INFO, not ERROR.
Pattern 4: Correlation ID Propagation
Generate a unique ID at ingress and thread it through all operations.
from contextvars import ContextVar
import uuid
import structlog
correlation_id: ContextVar[str] = ContextVar("correlation_id", default="")
def set_correlation_id(cid: str | None = None) -> str:
"""Set correlation ID for current context."""
cid = cid or str(uuid.uuid4())
correlation_id.set(cid)
structlog.contextvars.bind_contextvars(correlation_id=cid)
return cid
# FastAPI middleware example
from fastapi import Request
async def correlation_middleware(request: Request, call_next):
"""Middleware to set and propagate correlation ID."""
# Use incoming header or generate new
cid = request.headers.get("X-Correlation-ID") or str(uuid.uuid4())
set_correlation_id(cid)
response = await call_next(request)
response.headers["X-Correlation-ID"] = cid
return response
Propagate to outbound requests:
import httpx
async def call_downstream_service(endpoint: str, data: dict) -> dict:
"""Call downstream service with correlation ID."""
async with httpx.AsyncClient() as client:
response = await client.post(
endpoint,
json=data,
headers={"X-Correlation-ID": correlation_id.get()},
)
return response.json()
Detailed worked examples and patterns
Detailed sections (starting with ## Advanced Patterns) live in references/details.md. Read that file when the navigation summary above is insufficient.
Best Practices Summary
- Use structured logging - JSON logs with consistent fields
- Propagate correlation IDs - Thread through all requests and logs
- Track the four golden signals - Latency, traffic, errors, saturation
- Bound label cardinality - Never use unbounded values as metric labels
- Log at appropriate levels - Don't cry wolf with ERROR
- Include context - User ID, request ID, operation name in logs
- Use context managers - Consistent timing and error handling
- Separate concerns - Observability code shouldn't pollute business logic
- Test your observability - Verify logs and metrics in integration tests
- Set up alerts - Metrics are useless without alerting
Related skills
More from wshobson/agents and the wider catalog.

python-packaging
Create and publish distributable Python packages with modern project structure and PyPI integration.

python-performance-optimization
Profile and optimize Python code to identify bottlenecks and improve performance.

python-project-structure
Design well-organized Python projects with clear module boundaries, explicit public interfaces, and maintainable directory structures.

python-resilience
Automatic retries, exponential backoff, and fault-tolerant decorators for resilient Python services.

python-resource-management
Manage Python resources deterministically with context managers, cleanup patterns, and streaming.

python-testing-patterns
Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development.