08.2 - Custom Exceptions & the logging Module
Theory 25 min Intermediate
Custom Exceptions
Create custom exceptions by subclassing Exception:
# Base exception for your application
class AppError(Exception):
"""Base exception for all application errors."""
pass
# Specific exceptions
class ValidationError(AppError):
"""Raised when input validation fails."""
def __init__(self, field, value, message):
self.field = field
self.value = value
super().__init__(f"Validation failed for '{field}': {message} (got: {value!r})")
class DatabaseError(AppError):
"""Raised when database operations fail."""
def __init__(self, operation, message):
self.operation = operation
super().__init__(f"Database {operation} failed: {message}")
class NotFoundError(AppError):
"""Raised when a requested resource doesn't exist."""
def __init__(self, resource, identifier):
self.resource = resource
self.identifier = identifier
super().__init__(f"{resource} with id={identifier!r} not found")
# Usage
def get_user(user_id):
if not isinstance(user_id, int):
raise ValidationError("user_id", user_id, "must be an integer")
user = db.find(user_id)
if user is None:
raise NotFoundError("User", user_id)
return user
try:
user = get_user("abc")
except ValidationError as e:
print(f"Invalid input: {e.field} = {e.value}")
except NotFoundError as e:
print(f"Not found: {e.resource} #{e.identifier}")
except AppError as e:
print(f"Application error: {e}")
The logging Module
logging is the professional alternative to print() for debugging and monitoring.
Log Levels
| Level | Numeric | Use |
|---|---|---|
DEBUG | 10 | Detailed diagnostics |
INFO | 20 | Normal operations |
WARNING | 30 | Potential issues |
ERROR | 40 | Errors that allow continuing |
CRITICAL | 50 | Fatal — app cannot continue |
Basic Configuration
import logging
# Basic setup (for scripts)
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
logger.debug("Debug info: processing item %d", item_id)
logger.info("User %s logged in", username)
logger.warning("Disk space low: %d%% remaining", pct)
logger.error("Failed to connect to database: %s", error)
logger.critical("System out of memory — shutting down")
Production-Ready Logging Setup
import logging
from logging.handlers import RotatingFileHandler
def setup_logging(log_file="app.log", level=logging.INFO):
"""Configure application-wide logging."""
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)-8s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# Root logger
root_logger = logging.getLogger()
root_logger.setLevel(level)
# Console handler
console = logging.StreamHandler()
console.setFormatter(formatter)
console.setLevel(logging.INFO)
# File handler (rotates at 5MB, keeps 3 backups)
file_handler = RotatingFileHandler(
log_file, maxBytes=5 * 1024 * 1024, backupCount=3
)
file_handler.setFormatter(formatter)
file_handler.setLevel(logging.DEBUG)
root_logger.addHandler(console)
root_logger.addHandler(file_handler)
# In each module:
logger = logging.getLogger(__name__)
Logging Exceptions
try:
result = risky_operation()
except Exception as e:
# Log with full traceback
logger.exception("Operation failed") # equivalent to logger.error(..., exc_info=True)
# OR
logger.error("Operation failed: %s", e, exc_info=True)
print() vs logging
| Feature | print() | logging |
|---|---|---|
| Levels | None | DEBUG/INFO/WARNING/ERROR/CRITICAL |
| File output | >>sys.stderr only | File, rotating, network |
| Timestamps | Manual | Built-in |
| Module name | Manual | %(name)s |
| Production use | No | Yes |
| Performance | Slow (always runs) | Fast (filtered by level) |
Key Vocabulary
| Term | Definition |
|---|---|
| Custom exception | A class inheriting from Exception for domain-specific errors |
| Exception hierarchy | Organizing related exceptions in a class tree |
logging | Standard library module for professional application logging |
| Log level | Severity of a log message: DEBUG < INFO < WARNING < ERROR < CRITICAL |
| Handler | Destination for log messages (console, file, network) |
| Formatter | Template for log message format |
logger.exception() | Log an error with full stack trace |
RotatingFileHandler | File handler that rotates when a size limit is reached |
Summary
- Create custom exceptions by subclassing
Exception— add fields for structured error data - Organize exceptions in a hierarchy:
AppError→ValidationError,NotFoundError - Replace
print()withloggingin any code beyond simple scripts - Use
logging.getLogger(__name__)in every module - Configure handlers (console + file) once in your entry point
logger.exception()automatically includes the full stack trace
📄️ 08.1 - Exceptions
Handle Python exceptions gracefully with try/except/else/finally, understand the exception hierarchy, and raise exceptions correctly
📄️ 08.2 - Custom Exceptions & Logging
Create custom exception hierarchies and replace print() debugging with Python's professional logging module
📄️ 08.3 - Debugging
Debug Python programs using breakpoints, pdb, VS Code debugger, assertions, and traceback analysis
📄️ Lab - Module 08
Build a CLI contact manager with custom exceptions, professional logging, and input validation
📄️ Quiz - Module 08
30 questions on exceptions, try/except, custom errors, logging, and debugging