# Python Logging

> When to activate: Python logging, structlog, JSON logs, correlation IDs, log levels, observability setup

- Skill: `mattakushi432/python-logging` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/python-logging`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/python-logging/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/python-logging

---


# Python Logging Patterns

## Structured Logging with structlog
```python
import structlog
import logging
import sys

def configure_logging(json_logs: bool = False, log_level: str = "INFO") -> None:
    shared_processors = [
        structlog.contextvars.merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.stdlib.add_logger_name,
    ]
    
    if json_logs:
        processors = shared_processors + [structlog.processors.JSONRenderer()]
    else:
        processors = shared_processors + [structlog.dev.ConsoleRenderer()]
    
    structlog.configure(
        processors=processors,
        wrapper_class=structlog.make_filtering_bound_logger(
            logging.getLevelName(log_level)
        ),
        logger_factory=structlog.PrintLoggerFactory(file=sys.stdout),
    )

# Usage
logger = structlog.get_logger(__name__)

async def create_user(data: dict) -> User:
    logger.info("creating_user", email=data["email"])
    try:
        user = await user_repo.create(data)
        logger.info("user_created", user_id=user.id, email=user.email)
        return user
    except DuplicateEmailError:
        logger.warning("duplicate_email", email=data["email"])
        raise
    except Exception:
        logger.exception("user_creation_failed", email=data["email"])
        raise
```

## Correlation IDs in FastAPI
```python
from contextvars import ContextVar
import uuid
import structlog

REQUEST_ID: ContextVar[str] = ContextVar("request_id", default="")

@app.middleware("http")
async def add_correlation_id(request: Request, call_next):
    request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
    REQUEST_ID.set(request_id)
    structlog.contextvars.bind_contextvars(request_id=request_id)
    
    response = await call_next(request)
    response.headers["X-Request-ID"] = request_id
    structlog.contextvars.clear_contextvars()
    return response
```

## Standard Library Logging (minimal setup)
```python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(name)s %(levelname)s %(message)s",
    handlers=[logging.StreamHandler(sys.stdout)],
)

# In modules: always use module-level logger
logger = logging.getLogger(__name__)

# Log with context
logger.info("Processing order", extra={"order_id": order_id, "user_id": user_id})

# Don't use f-strings in log calls (deferred formatting)
logger.debug("User %s logged in from %s", user.id, ip_address)  # Good
logger.debug(f"User {user.id} logged in from {ip_address}")     # Bad: formats even if DEBUG disabled
```

## Log Levels Guide
| Level | Use for |
|-------|---------|
| DEBUG | Detailed diagnostic information (disabled in production) |
| INFO | Operational events (request received, user created) |
| WARNING | Unexpected but handled situations (rate limit hit, retry) |
| ERROR | Failures that need attention (DB down, 3rd party API failed) |
| CRITICAL | System-level failures requiring immediate action |

