Skip to main content Brad's PyNotes

Logging Module: Professional Application Logging

TL;DR

The logging module provides flexible, configurable logging with different levels (DEBUG, INFO, WARNING, ERROR, CRITICAL), handlers for various outputs, and formatters for customized log messages.

Interesting!

Python’s logging module is thread-safe by default and supports hierarchical loggers - child loggers automatically inherit configuration from parent loggers, making complex application logging manageable.

Basic Logging

python code snippet start

import logging

# Configure basic logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Create logger
logger = logging.getLogger(__name__)

# Log messages at different levels
logger.debug("This is a debug message")     # Won't show (level too low)
logger.info("Application started")          # Will show
logger.warning("This is a warning")         # Will show
logger.error("An error occurred")           # Will show
logger.critical("Critical system failure")  # Will show

python code snippet end

Professional Logging Setup

python code snippet start

import logging
import logging.handlers
from pathlib import Path

def setup_logging():
    # Create logs directory
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)
    
    # Create logger
    logger = logging.getLogger("myapp")
    logger.setLevel(logging.DEBUG)
    
    # Create formatters
    detailed_formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
    )
    simple_formatter = logging.Formatter('%(levelname)s - %(message)s')
    
    # File handler (rotating)
    file_handler = logging.handlers.RotatingFileHandler(
        log_dir / "app.log",
        maxBytes=10*1024*1024,  # 10MB
        backupCount=5
    )
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(detailed_formatter)
    
    # Console handler
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(simple_formatter)
    
    # Add handlers to logger
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger

# Usage
logger = setup_logging()
logger.info("Logging system initialized")

python code snippet end

Structured Logging

python code snippet start

import logging
import json
from datetime import datetime

class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_data = {
            'timestamp': datetime.fromtimestamp(record.created).isoformat(),
            'level': record.levelname,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno
        }
        
        # Add extra fields if present
        if hasattr(record, 'user_id'):
            log_data['user_id'] = record.user_id
        if hasattr(record, 'request_id'):
            log_data['request_id'] = record.request_id
            
        return json.dumps(log_data)

# Setup structured logging
logger = logging.getLogger("structured")
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# Log with extra context
logger.info("User login", extra={'user_id': 123, 'request_id': 'abc-123'})

python code snippet end

Exception Logging

python code snippet start

def risky_operation(x, y):
    try:
        result = x / y
        logger.info(f"Division successful: {x}/{y} = {result}")
        return result
    except ZeroDivisionError:
        logger.error(f"Division by zero attempted: {x}/{y}", exc_info=True)
        raise
    except Exception as e:
        logger.critical(f"Unexpected error in division: {e}", exc_info=True)
        raise

# Usage
try:
    risky_operation(10, 0)
except Exception:
    logger.exception("Operation failed")  # Automatically includes traceback

python code snippet end

Configuration-Based Logging

python code snippet start

import logging.config
import yaml

# logging_config.yaml
config = """
version: 1
formatters:
  default:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  detailed:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'

handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: default
    stream: ext://sys.stdout

  file:
    class: logging.handlers.RotatingFileHandler
    level: DEBUG
    formatter: detailed
    filename: logs/app.log
    maxBytes: 10485760
    backupCount: 5

loggers:
  myapp:
    level: DEBUG
    handlers: [console, file]
    propagate: no

root:
  level: WARNING
  handlers: [console]
"""

# Load configuration
config_dict = yaml.safe_load(config)
logging.config.dictConfig(config_dict)

# Use configured logger
logger = logging.getLogger("myapp")
logger.info("Configuration-based logging active")

python code snippet end

The logging module provides enterprise-grade logging capabilities essential for monitoring, debugging, and maintaining production applications.

Logging works hand-in-hand with proper exception handling to create robust, maintainable applications that provide clear feedback when things go wrong.

Reference: Python Logging Module Documentation