杰瑞科技汇

python loghandler

Of course! The logging module in Python is a powerful and flexible framework for emitting log messages from your applications. It's a standard library module, so you don't need to install anything.

A Handler is a core component of the logging system. Its job is to take a log record created by a Logger and send it to a specific destination. Common destinations include the console, files, network sockets, email, etc.

Let's break down Handlers, starting with the basics and moving to more advanced configurations.


The Core Concept: Logger, Handler, Formatter

Think of the logging process as an assembly line:

  1. Logger: The entry point. Your application code calls methods on a logger (e.g., logger.info("Hello")). The logger creates a LogRecord object containing the message, level, timestamp, etc.
  2. Handler: The worker. The logger passes the LogRecord to one or more of its handlers. The handler's job is to decide what to do with the record.
  3. Formatter: The label machine. Before writing the record, the handler passes it to a Formatter. The formatter formats the record into a human-readable string according to a specified pattern.
  4. Destination: The output. The handler writes the formatted string to its target (e.g., the console, a file).

Common Built-in Handlers

The logging module provides several handy built-in handlers.

a. StreamHandler

Sends log records to a stream (like sys.stdout or sys.stderr, which is the console by default).

import logging
import sys
# 1. Create a logger
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG) # Set the lowest severity level this logger will handle
# 2. Create a handler
# By default, it goes to sys.stderr
console_handler = logging.StreamHandler()
# You can also specify a stream, e.g., sys.stdout
# console_handler = logging.StreamHandler(sys.stdout)
# 3. Create a formatter and add it to the handler
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
# 4. Add the handler to the logger
logger.addHandler(console_handler)
# 5. Log some messages
logger.debug("This is a debug message.")
logger.info("This is an info message.")
logger.warning("This is a warning message.")
logger.error("This is an error message.")
logger.critical("This is a critical message.")

b. FileHandler

Sends log records to a disk file. If the file doesn't exist, it's created. If it does exist, new logs are appended by default.

import logging
# 1. Create a logger
logger = logging.getLogger("my_file_app")
logger.setLevel(logging.INFO)
# 2. Create a handler that writes to a file
file_handler = logging.FileHandler('app.log')
# 3. Create a formatter and add it to the handler
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 4. Add the handler to the logger
logger.addHandler(file_handler)
# 5. Log some messages
logger.info("Application started.")
logger.warning("This warning will go to the file.")
logger.error("An error occurred!")

c. RotatingFileHandler

This is extremely useful for long-running applications. It logs to a file, but when the file reaches a certain size, it "rotates" the logs. It keeps a certain number of backup files.

This prevents a single log file from growing indefinitely and consuming all your disk space.

import logging
from logging.handlers import RotatingFileHandler
# 1. Create a logger
logger = logging.getLogger("rotating_app")
logger.setLevel(logging.INFO)
# 2. Create a handler
# 'app.log' is the filename
# 'maxBytes=1' means rotate after 1 byte (for demonstration)
# 'backupCount=3' means keep up to 3 backup files (e.g., app.log.1, app.log.2)
rotating_handler = RotatingFileHandler(
    'app.log',
    maxBytes=1,  # In a real app, this would be much larger, e.g., 5*1024*1024 for 5MB
    backupCount=3,
    encoding='utf-8'
)
# 3. Create a formatter and add it to the handler
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
rotating_handler.setFormatter(formatter)
# 4. Add the handler to the logger
logger.addHandler(rotating_handler)
# 5. Log messages to trigger rotation
for i in range(10):
    logger.info(f"This is log message number {i}.")

After running this, you'll see app.log with the latest logs, and app.log.1, app.log.2, etc., with the older logs.

d. TimedRotatingFileHandler

Similar to RotatingFileHandler, but it rotates logs based on time instead of size. You can specify rotation at midnight ('midnight'), on a certain day of the week ('W0' for Monday), every hour ('H'), etc.

import logging
from logging.handlers import TimedRotatingFileHandler
# 1. Create a logger
logger = logging.getLogger("timed_rotating_app")
logger.setLevel(logging.INFO)
# 2. Create a handler
# 'when=D' means rotate daily
# 'interval=1' means every 1 day
# 'backupCount=7' means keep up to 7 days of logs
timed_handler = TimedRotatingFileHandler(
    'timed_app.log',
    when='D',
    interval=1,
    backupCount=7,
    encoding='utf-8'
)
# 3. Create a formatter and add it to the handler
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
timed_handler.setFormatter(formatter)
# 4. Add the handler to the logger
logger.addHandler(timed_handler)
# 5. Log a message
logger.info("This log will be rotated daily.")

Advanced Handler Features

a. Filtering Handlers

Sometimes you don't want a handler to process every log message. You can use a Filter for this. For example, you might want a file to only contain warnings and above, while the console shows everything.

import logging
# Create a logger
logger = logging.getLogger("filter_app")
logger.setLevel(logging.DEBUG)
# Console handler shows all messages
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG) # Show DEBUG and up
# File handler only shows WARNING and up
file_handler = logging.FileHandler('warnings_only.log')
file_handler.setLevel(logging.WARNING) # Show WARNING and up
# Add formatters (omitted for brevity, but you should add them!)
# ... formatter setup ...
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.debug("This goes only to the console.")
logger.info("This also goes only to the console.")
logger.warning("This goes to BOTH the console and the file.")
logger.error("This also goes to BOTH.")

b. Custom Handlers

You can create your own handler by subclassing logging.Handler. This is useful for sending logs to custom services, like a database, a monitoring system (e.g., Datadog, New Relic), or a message queue.

Here's a simple example of a custom handler that prints logs in a special format.

import logging
class UppercaseFormatter(logging.Formatter):
    """A custom formatter that converts the message to uppercase."""
    def format(self, record):
        # Call the parent's format method to get the full message string
        original_message = super().format(record)
        return original_message.upper()
class CustomPrintHandler(logging.Handler):
    """A custom handler that prints logs in a specific way."""
    def emit(self, record):
        log_entry = self.format(record)
        # Add a custom prefix
        print(f"[CUSTOM_LOG] {log_entry}")
# --- Using the custom handler ---
logger = logging.getLogger("custom_app")
logger.setLevel(logging.INFO)
# Create an instance of our custom handler
my_handler = CustomPrintHandler()
# Create an instance of our custom formatter
my_formatter = UppercaseFormatter('%(asctime)s - %(levelname)s - %(message)s')
my_handler.setFormatter(my_formatter)
logger.addHandler(my_handler)
logger.info("This message will be processed by our custom handler.")
logger.warning("So will this one.")

Best Practices and The logging.config Module

Manually configuring loggers in your code can become messy. For any non-trivial application, it's highly recommended to use a configuration file. The logging.config module allows you to set up your entire logging setup using a dictionary or an external file (like YAML or JSON).

This separates your logging configuration from your application logic.

Example using dictConfig (in Python code)

import logging
import logging.config
import yaml
# Define the logging configuration
LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'detailed': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s'
        },
        'simple': {
            'format': '%(levelname)s: %(message)s'
        }
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'simple',
            'stream': 'ext://sys.stdout'
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'DEBUG',
            'formatter': 'detailed',
            'filename': 'app.log',
            'maxBytes': 10485760, # 10MB
            'backupCount': 5,
            'encoding': 'utf8'
        }
    },
    'loggers': {
        'my_app': {
            'level': 'DEBUG',
            'handlers': ['console', 'file'],
            'propagate': False
        }
    },
    'root': {
        'level': 'WARNING',
        'handlers': ['console']
    }
}
# Apply the configuration
logging.config.dictConfig(LOGGING_CONFIG)
# Get the configured logger
logger = logging.getLogger("my_app")
logger.debug("This debug message will only appear in the file.")
logger.info("This info message will appear in both console and file.")
logger.warning("This warning will appear in the root logger (console) and my_app logger (console+file).")

This approach is much cleaner and easier to manage. For even more power, you can use a YAML file and logging.config.fileConfig() or logging.config.dictConfig() with a YAML loader.

分享:
扫描分享到社交APP
上一篇
下一篇