Logging Best Practices¶
Guidelines for effective logging in production applications.
Mental Model
Good logging is like a flight recorder: it captures just enough detail at each level so you can reconstruct what happened after the fact. Use named loggers per module, log at the right level, never log sensitive data, and configure output in one place. These habits turn logs from noisy clutter into a reliable debugging tool.
Use Named Loggers¶
Create loggers for each module using name.
```python import logging
In each module, use name for the logger¶
logger = logging.getLogger(name)
def process_data(data): logger.debug(f"Processing data: {data}") try: result = sum(data) logger.info(f"Processed successfully: {result}") return result except Exception as e: logger.error(f"Error processing: {e}") raise
logging.basicConfig(level=logging.DEBUG) process_data([1, 2, 3]) ```
DEBUG:__main__:Processing data: [1, 2, 3]
INFO:__main__:Processed successfully: 6
Structured Logging¶
Include context in log messages.
```python import logging import json
logger = logging.getLogger(name)
class StructuredFormatter(logging.Formatter): def format(self, record): log_obj = { 'timestamp': self.formatTime(record), 'level': record.levelname, 'message': record.getMessage(), 'logger': record.name } return json.dumps(log_obj)
handler = logging.StreamHandler() handler.setFormatter(StructuredFormatter()) logger.addHandler(handler) logger.setLevel(logging.INFO)
logger.info("User login", extra={'user_id': 123}) ```
{"timestamp": "2026-02-12 12:34:56,789", "level": "INFO", "message": "User login", "logger": "__main__"}
Exercises¶
Exercise 1.
Create a named logger for a module called "myapp.database". Configure it with a StreamHandler that uses the format "%(name)s - %(levelname)s - %(message)s". Log an INFO message "Connection established" and a WARNING message "Slow query detected". Verify the output includes the logger name.
Solution to Exercise 1
```python import logging
logger = logging.getLogger("myapp.database") logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler() formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) logger.addHandler(handler)
logger.info("Connection established") logger.warning("Slow query detected")
myapp.database - INFO - Connection established¶
myapp.database - WARNING - Slow query detected¶
```
Exercise 2.
Write a function setup_file_logger that creates a logger writing to a specified file path with rotation (using RotatingFileHandler with maxBytes=1024 and backupCount=3). The format should include the timestamp, level, and message. Return the configured logger.
Solution to Exercise 2
```python import logging from logging.handlers import RotatingFileHandler
def setup_file_logger(name, file_path): logger = logging.getLogger(name) logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler(
file_path, maxBytes=1024, backupCount=3
)
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
Usage:¶
logger = setup_file_logger("myapp", "app.log")¶
logger.info("Application started")¶
```
Exercise 3.
Write a context manager log_execution_time that logs the start and end of a code block, along with the elapsed time in seconds. Use a logger (not print) for all output. For example, with log_execution_time(logger, "data processing"): should log "Starting data processing" and "Finished data processing in 0.5s".
Solution to Exercise 3
```python import logging import time from contextlib import contextmanager
@contextmanager def log_execution_time(logger, task_name): logger.info(f"Starting {task_name}") start = time.time() try: yield finally: elapsed = time.time() - start logger.info(f"Finished {task_name} in {elapsed:.2f}s")
Test¶
logger = logging.getLogger("timer") logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler())
with log_execution_time(logger, "data processing"): time.sleep(0.1) # Simulate work ```