The Problem With Default Python Logging
Look, I’ve shipped enough Python projects to know this pattern: you start with print() statements, graduate to basic logging.info(), and eventually end up with a mess of different logging configs across your services. Then one day you’re debugging in production and realize half your logs are missing while httpx is dumping every HTTP header into your terminal.
The default logging setup in Python is… fine. But it’s not great. You get plain text output, no color coding, and every third-party library thinks its DEBUG logs are fascinating. I’ve wasted too many hours scrolling past httpx connection details to find my actual application logs.
Why Rich Changes Everything
I stumbled into using Rich’s logging handler about two years ago, and honestly, I can’t go back. The difference is night and day:
- Automatic syntax highlighting for variables and data structures
- Clean, readable timestamps without configuration hell
- Smart exception formatting that actually helps you debug
- Path visibility control based on your environment
The best part? It’s not just prettier—it’s genuinely faster to parse visually when you’re hunting for that one error in a wall of logs.
The Configuration That Actually Works
Here’s the exact setup I copy into every project. It’s opinionated, but that’s the point—consistency beats customization.
Set Up Production-Ready Logging
Configure Python logging with Rich handler and proper filtering
Install Rich
First, add Rich to your dependencies:
| |
Or add it to your requirements.txt or pyproject.toml. Rich is stable, well-maintained, and the only external dependency you need.
Create the Configuration Dictionary
This is the core. I usually put this in a logging_config.py or at the top of my main module:
| |
The magic is in those logger overrides. httpx and uvicorn.access are silenced unless something actually goes wrong.
Initialize the Logger
Create a simple initialization function and call it once at startup:
| |
That’s it. Import this logger anywhere in your project and use it. No per-file configuration, no repeated setup code.
The Details That Matter
Environment-Aware Path Display
Notice this line in the config:
| |
In development, you want to see which file and line number each log came from. In production? That’s just noise. This lambda switches behavior based on your environment without any code changes.
Silencing Noisy Dependencies
The httpx and uvicorn.access logger configs are crucial. These libraries are chatty by design—they log every request, every connection pool event, every retry. Great for debugging the HTTP layer, terrible for reading your application logs.
By setting their level to WARNING and propagate to False, you only see their logs when something breaks. Perfect balance.
Why Not Rich Tracebacks?
You might notice rich_tracebacks: False. Rich’s enhanced tracebacks are gorgeous, but they’re also long. In a containerized environment or CI/CD pipeline, that extra formatting can actually make logs harder to scan. I keep them off by default and enable them locally when I need to deep-dive on an exception.
When This Setup Breaks Down
This isn’t perfect for everyone. Here’s when you might need something different:
If you’re shipping libraries: Don’t configure the root logger. Let the consuming application handle that. Just get a logger with your package name.
If you need structured JSON logs: Rich is great for humans, terrible for log aggregation tools like Datadog or Splunk. You’ll want a JSON formatter instead.
If you’re running on AWS Lambda: The Lambda runtime has opinions about logging. You might need to stick closer to the defaults or use their powertools library.
If you have complex multi-process setups: You’ll need QueueHandler and QueueListener to safely log from worker processes. This config is for single-process apps or simple async setups.
The One Thing I Always Customize
The only thing I consistently change across projects is adding more noisy loggers to the silence list. Common culprits:
urllib3- Similar to httpx, loves to talk about connectionsbotocore- AWS SDK logs EVERYTHINGasyncio- In debug mode, it’s overwhelmingparamiko- SSH operations get verbose fast
Just add them to the loggers dict with level WARNING or ERROR, and you’re golden.
FAQ
Why not use structlog or loguru instead?
How do I change log levels for different environments?
os.getenv("LOG_LEVEL", "INFO") and set LOG_LEVEL=DEBUG in development. Or use separate config files loaded based on environment.Can I use this with FastAPI or Flask?
configure_logging() before you create your app instance. FastAPI and Flask will use whatever logging config you’ve set up. Just make sure to silence their access logs in the config if you don’t need request-level logging.What about logging to files?
handlers dict and include it in the root logger’s handlers list. I typically use RotatingFileHandler with maxBytes and backupCount for production file logging.Why This Should Be Default
Here’s my controversial take: this configuration—or something very close to it—should be what Python ships with. Not the bare-bones plain text output we get today.
The default logging config optimizes for simplicity, which makes sense for a standard library. But in practice, every production Python project ends up building something like this anyway. We all independently discover that third-party libraries are too noisy, that colorized output helps, and that environment-specific behavior matters.
Having a blessed “production-ready” logging config in the Python docs would save thousands of developers from reinventing this wheel. Until then, I’m copy-pasting this file into every new project.
Conclusion
Key Takeaways
- Standard Python logging works fine but needs configuration to be great for production use
- Rich handler provides colorized, readable output with minimal setup overhead
- Silencing noisy third-party loggers (httpx, uvicorn, boto) dramatically improves log readability
- Environment-aware configuration through environment variables keeps the same code working in dev and prod
- One logging config file copied across projects ensures consistency and saves setup time
- Disable rich_tracebacks in production to keep logs scannable in containerized environments
- This setup works best for single-process applications and async services, not multi-process workers
The best logging config is the one you never think about after the initial setup. Copy this, adjust the noisy logger list for your stack, and move on to actually building features. Your future debugging self will thank you.