Logging
Logging allows you to record what your code is doing. Good logging helps you understand how your application behaves in production, diagnose issues, and trace requests across different parts of the system.
A solid logging setup allows you to adjust the level of detail you see, where logs are sent, and how they’re formatted.
When it comes to implementing logging in your code, you can follow the best practices below:
- Use the standard
loggingpackage instead ofprint()calls. For production code, use loggers to control log levels, destinations, and log formats without requiring code modifications. Reserveprint()for quick local debugging or small scripts. - Create a module-level logger with
__name__. Calllogging.getLogger(__name__)in each module. This practice names log records after the module they came from and lets you tweak logging behavior per module or package. - Choose appropriate logging levels. Use
.debug()for noisy diagnostics,.info()for normal operations,.warning()for unexpected but handled situations,.error()for failures, and.critical()for unrecoverable conditions. Consistent use of logging levels facilitates filtering and alerting. - Configure logging once, near the entry point of your code. Set up log handlers, formatters, and levels in the
if __name__ == "__main__":block, a CLI entry point, or a dedicated configuration module likelogging_config.py. Avoid callingbasicConfig()or changing global logging settings in imported modules. This practice makes logging behavior predictable, centralized, and maintainable. - Include useful context in log messages. Log enough information to understand what happened. Include details, such as identifiers, user IDs, resource names, but ensure that you don’t leak sensitive data. Use structured messages,
extra, orLoggerAdapterwhen you need richer context. - Log exceptions with stack traces when appropriate. Use
logger.exception()insideexceptblocks or passexc_info=Trueto include traceback information. This is invaluable for debugging errors in production. - Avoid global side effects in library code. Don’t configure logging at import time in reusable modules or libraries. Such code should create loggers and emit messages, but let application code decide how logs are handled. Avoid calling
logging.basicConfig()or adding global handlers in library code, as this can unexpectedly change an application’s logging behavior.
To see these ideas in practice, compare the following two modules that record user login events. While auth.py could be part of an application, it’s written here as if it were a reusable module.
🔴 Avoid this:
auth.py
import logging
logging.basicConfig(level=logging.INFO)
def authenticate_user(username: str, password: str) -> bool:
if username != "admin" or password != "secret":
logging.error("Authentication failed for user %s", username)
return False
logging.info("User %s authenticated successfully", username)
return True
This code works, but it has a few problems. It calls logging.basicConfig() at import time inside a library module, which affects the application’s global logging configuration. Additionally, there’s no clear indication in the output of which module produced the log messages.
✅ Favor this:
auth.py
import logging
logger = logging.getLogger(__name__)
def authenticate_user(username: str, password: str) -> bool:
if username != "admin" or password != "secret":
logger.warning("Authentication failed for user %s", username)
return False
logger.info("User %s authenticated successfully", username)
return True
Application code:
app.py
# ...
if __name__ == "__main__":
# Configure logging here...
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s:%(name)s:%(message)s",
)
authenticate_user("alice", "wrong-password")
In this improved version, auth.py obtains a module-level logger with logging.getLogger(__name__), and authenticate_user() only emits log messages.
In app.py, you configure logging once at the application entry point. This approach keeps reusable modules free of global side effects while allowing application code to decide how to format, filter, and route log messages.
Related Resources
Tutorial
Logging in Python
If you use Python's print() function to get information about the flow of your programs, logging is the natural next step. Create your first logs and curate them to grow with your projects.
For additional information on related topics, take a look at the following resources:
- How to Use Loguru for Simpler Python Logging (Tutorial)
- Python Logging: A Stroll Through the Source Code (Tutorial)
- Logging Inside Python (Course)
- Logging in Python (Quiz)
- Python Logging With the Loguru Library (Quiz)