Your Page Title
🔍

    Logging in Python

    Logging in Python is a module provided in the language, allowing it to have flexibility in emitting log messages from programs. It is mainly used to track events, debug, and record activities in programs for further analysis.

    What is Logging?

    Logging is recording events, warnings, errors, or other messages about the program’s execution. These logs, which contain messages, assist the developers in monitoring the program:

    1. Track Program Behaviour: Understand what the program is doing.
    2. Debugging issues: Identify issues and locate errors.
    3. Audit: History of an action or any transaction by the system.

    Unlike using the print() function, logging provides:

    • Configurability: You can control where logs go (console, files, etc.) and what gets logged (severity levels).
    • Structure: Log messages are formatted with timestamps, levels, and other context.
    • Scalability: Logging works well for simple scripts and large, distributed systems.

    Core Elements of Python’s Logging Framework

    1. Logger

    The logger is the entry point to use the logging framework, responsible for:

    • Receiving log messages.
    • Deciding their importance (based on the log level).
    • Forwarding messages to Handlers for output.

    A logger has a name. Most of the time, you will use logging.getLogger(name) to obtain or create a logger, such as:

    import logging
    
    logger = logging.getLogger("MyApp")
    logger.info("An informational message from MyApp")

    2. Log Levels

    Log levels are used to categorize the severity of messages. Each level has a numeric value:

    LevelNumeric ValueUsage
    DEBUG10Detailed diagnostic information useful for debugging.
    INFO20Confirmation that the program is running as expected.
    WARNING30An indication of something unexpected or suboptimal.
    ERROR40A more serious issue that prevents part of the program from working.
    CRITICAL50A severe error indicating the program itself may not continue.

    Example:

    import logging
    
    logging.basicConfig(level=logging.DEBUG)  # Set the lowest log level (DEBUG)
    
    logging.debug("This is for debugging")
    logging.info("General information")
    logging.warning("This is a warning")
    logging.error("An error occurred")
    logging.critical("Critical failure!")

    3. Handlers

    Handlers define where log messages go. Examples:

    • StreamHandler: Which sends logs to the console (stdout/stderr).
    • FileHandler: Which logs to a file.
    • RotatingFileHandler: When the log file reaches a certain size, this handler rotates it.
    • SMTPHandler: Sends logs via an email.

    A logger can have multiple handlers, and each handler can define its log level.

    Example:

    import logging
    
    # Create logger
    logger = logging.getLogger("ExampleLogger")
    logger.setLevel(logging.DEBUG)
    
    # Create console handler
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.DEBUG)
    
    # Create file handler
    file_handler = logging.FileHandler("example.log")
    file_handler.setLevel(logging.ERROR)
    
    # Add handlers to the logger
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)
    
    # Log messages
    logger.debug("This appears in the console only")
    logger.error("This appears in both the console and file")

    4. Formatters

    Formatters define how log messages are displayed. You can include details like:

    • Timestamp (%(asctime)s)
    • Logger name (%(name)s)
    • Log level (%(levelname)s)
    • Message (%(message)s)

    Example:

    import logging
    
    # Custom formatter
    formatter = logging.Formatter(
        fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # Apply formatter to handlers
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    
    logger = logging.getLogger("FormattedLogger")
    logger.addHandler(console_handler)
    logger.setLevel(logging.INFO)
    
    logger.info("This is a formatted log message")

    Output:

    2025-01-09 13:00:00 - FormattedLogger - INFO - This is a formatted log message

    5. Filters

    Filters provide fine-grained control over which log records get passed to handlers.

    Example:

    import logging
    
    class CustomFilter(logging.Filter):
        def filter(self, record):
            # Only allow messages that contain the word 'Important'
            return 'Important' in record.msg
    
    logger = logging.getLogger("FilteredLogger")
    logger.setLevel(logging.DEBUG)
    
    # Add filter to a console handler
    console_handler = logging.StreamHandler()
    console_handler.addFilter(CustomFilter())
    logger.addHandler(console_handler)
    
    logger.info("This is not important")  # Will NOT be logged
    logger.info("This is Important!")     # Will be logged

    Configuration Methods

    1. basicConfig (Quick Setup)

    basicConfig is the simplest way to configure logging. Example:

    import logging
    
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    logging.info("Logging configured with basicConfig")

    2. Configuring Multiple Loggers

    For larger applications, you may want to configure multiple loggers differently.

    import logging
    
    # Logger for module1
    logger1 = logging.getLogger("module1")
    logger1.setLevel(logging.DEBUG)
    console_handler = logging.StreamHandler()
    logger1.addHandler(console_handler)
    
    # Logger for module2
    logger2 = logging.getLogger("module2")
    logger2.setLevel(logging.WARNING)
    file_handler = logging.FileHandler("module2.log")
    logger2.addHandler(file_handler)
    
    logger1.debug("Debug from module1")  # Console
    logger2.warning("Warning from module2")  # File

    3. Using Configuration Files

    Logging can be configured using a dictionary or a file (YAML, INI).

    YAML Example:

    version: 1
    formatters:
      detailed:
        format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    handlers:
      console:
        class: logging.StreamHandler
        level: DEBUG
        formatter: detailed
      file:
        class: logging.FileHandler
        filename: app.log
        level: ERROR
        formatter: detailed
    loggers:
      MyApp:
        level: DEBUG
        handlers: [console, file]

    Python code to load the configuration:

    import logging.config
    import yaml
    
    with open("logging_config.yaml", "r") as f:
        config = yaml.safe_load(f)
    logging.config.dictConfig(config)
    
    logger = logging.getLogger("MyApp")
    logger.debug("This will appear in the console")
    logger.error("This will appear in the console and app.log")

    Best Practices for Logging

    1. Use Log Levels Wisely: Avoid using DEBUG or INFO in production.
    2. Centralize Logging Configuration: Keep all settings in a configuration file.
    3. Avoid Sensitive Data: Never log passwords or personal information.
    4. Use Rotating Logs: Prevent disk space issues by using RotatingFileHandler.
    5. Custom Loggers for Modules: Use separate loggers for different parts of the application.