← 返回首页
📝

日志系统:logging模块、Handler、Formatter与最佳实践

📂 python ⏱ 4 min 703 words

日志系统:logging模块、Handler、Formatter与最佳实践

日志是程序运行时的重要记录,帮助我们调试问题、监控系统状态、追踪用户行为。Python的logging模块提供了灵活且强大的日志功能。本文将全面介绍日志系统的使用。

为什么需要日志

logging基础

import logging

# 基础配置
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# 获取logger
logger = logging.getLogger(__name__)

# 不同级别的日志
logger.debug("调试信息")
logger.info("一般信息")
logger.warning("警告信息")
logger.error("错误信息")
logger.critical("严重错误")

日志级别

import logging

# 日志级别从低到高
# DEBUG < INFO < WARNING < ERROR < CRITICAL

logger = logging.getLogger('example')
logger.setLevel(logging.DEBUG)

# 临时改变级别
logger.setLevel(logging.WARNING)
logger.info("这条不会显示")      # 被过滤
logger.warning("这条会显示")     # 会显示

# 检查级别
print(f"当前级别: {logger.level}")
print(f"DEBUG是否启用: {logger.isEnabledFor(logging.DEBUG)}")

Formatter格式化

import logging

# 自定义格式
formatter = logging.Formatter(
    fmt='%(asctime)s | %(name)s | %(levelname)s | %(filename)s:%(lineno)d | %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# 常用格式变量
# %(name)s      - Logger名称
# %(levelname)s - 日志级别
# %(asctime)s   - 时间戳
# %(filename)s  - 文件名
# %(lineno)d    - 行号
# %(funcName)s  - 函数名
# %(message)s   - 日志消息
# %(thread)d    - 线程ID
# %(process)d   - 进程ID

# 创建handler并设置格式
handler = logging.StreamHandler()
handler.setFormatter(formatter)

logger = logging.getLogger('formatted')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

logger.info("格式化日志消息")

Handler处理器

Handler决定日志输出到哪里:

import logging
import sys
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler

# 创建logger
logger = logging.getLogger('app')
logger.setLevel(logging.DEBUG)

# 1. 控制台Handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(logging.Formatter(
    '%(asctime)s - %(levelname)s - %(message)s'
))

# 2. 文件Handler
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))

# 3. 滚动文件Handler(按大小)
rotating_handler = RotatingFileHandler(
    'app_rotating.log',
    maxBytes=10*1024*1024,  # 10MB
    backupCount=5
)

# 4. 定时滚动文件Handler
timed_handler = TimedRotatingFileHandler(
    'app_timed.log',
    when='midnight',
    interval=1,
    backupCount=30
)

# 添加handlers
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.addHandler(rotating_handler)

# 使用
logger.info("应用启动")
logger.debug("详细调试信息")
logger.error("发生错误")

多模块日志配置

# config/logging_config.py
import logging
import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
        'detailed': {
            'format': '%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(funcName)s: %(message)s'
        }
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'standard',
            'stream': 'ext://sys.stdout'
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'DEBUG',
            'formatter': 'detailed',
            'filename': 'app.log',
            'maxBytes': 10485760,
            'backupCount': 5,
            'encoding': 'utf-8'
        }
    },
    'root': {
        'level': 'DEBUG',
        'handlers': ['console', 'file']
    },
    'loggers': {
        'app': {
            'level': 'DEBUG',
            'handlers': ['console', 'file'],
            'propagate': False
        },
        'app.db': {
            'level': 'WARNING',
            'handlers': ['file'],
            'propagate': False
        }
    }
}

def setup_logging():
    logging.config.dictConfig(LOGGING_CONFIG)

使用logging.config

import logging
import logging.config
import yaml

# 从YAML文件加载配置
def setup_logging_from_yaml(config_path):
    with open(config_path, 'r', encoding='utf-8') as f:
        config = yaml.safe_load(f)
    logging.config.dictConfig(config)

# logging.yaml 示例内容
"""
version: 1
disable_existing_loggers: false
formatters:
  standard:
    format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: standard
    stream: ext://sys.stdout
root:
  level: INFO
  handlers:
    - console
"""

# 从字典配置
config = {
    'version': 1,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO'
        }
    },
    'root': {
        'level': 'INFO',
        'handlers': ['console']
    }
}
logging.config.dictConfig(config)

高级用法

import logging
import sys
from functools import wraps

# 1. 添加额外字段
class ContextFilter(logging.Filter):
    def __init__(self, context):
        super().__init__()
        self.context = context
    
    def filter(self, record):
        record.context = self.context
        return True

logger = logging.getLogger('context')
context_filter = ContextFilter('user_id=123')
logger.addFilter(context_filter)

# 2. 日志装饰器
def log_execution(logger):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logger.info(f"开始执行 {func.__name__}")
            try:
                result = func(*args, **kwargs)
                logger.info(f"{func.__name__} 执行成功")
                return result
            except Exception as e:
                logger.error(f"{func.__name__} 执行失败: {e}")
                raise
        return wrapper
    return decorator

# 3. 异常日志
def process_data(data):
    try:
        result = data / 0
    except Exception:
        logger.exception("处理数据时发生异常")  # 自动记录堆栈
        raise

# 4. 性能计时日志
import time
from contextlib import contextmanager

@contextmanager
def log_time(logger, operation):
    start = time.time()
    yield
    elapsed = time.time() - start
    logger.info(f"{operation} 耗时: {elapsed:.2f}秒")

实战:Web应用日志

import logging
import logging.config
from datetime import datetime

class WebLogger:
    def __init__(self, name):
        self.logger = logging.getLogger(name)
        self.setup_logger()
    
    def setup_logger(self):
        self.logger.setLevel(logging.DEBUG)
        
        # 格式
        formatter = logging.Formatter(
            '%(asctime)s | %(levelname)s | %(name)s | %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        
        # 控制台
        console = logging.StreamHandler()
        console.setLevel(logging.INFO)
        console.setFormatter(formatter)
        
        # 文件
        file_handler = logging.FileHandler(
            f'app_{datetime.now().strftime("%Y%m%d")}.log',
            encoding='utf-8'
        )
        file_handler.setLevel(logging.DEBUG)
        file_handler.setFormatter(formatter)
        
        self.logger.addHandler(console)
        self.logger.addHandler(file_handler)
    
    def request(self, method, path, status):
        self.logger.info(f"{method} {path} - {status}")
    
    def error(self, message, exc_info=False):
        self.logger.error(message, exc_info=exc_info)
    
    def user_action(self, user_id, action):
        self.logger.info(f"User {user_id}: {action}")

# 使用示例
web_log = WebLogger('webapp')
web_log.request('GET', '/api/users', 200)
web_log.user_action(123, 'login')

最佳实践

import logging
import sys

# 1. 使用模块名作为logger名称
logger = logging.getLogger(__name__)

# 2. 合理使用日志级别
logger.debug("处理用户请求: %s", request_id)    # 详细调试
logger.info("用户登录成功: %s", username)         # 正常操作
logger.warning("磁盘空间不足: %d%%", space)      # 潜在问题
logger.error("数据库连接失败: %s", error)         # 错误
logger.critical("系统崩溃,请立即处理")            # 严重错误

# 3. 使用延迟格式化避免字符串拼接开销
# 错误方式(即使日志被过滤也会拼接字符串)
# logger.info("Processing " + str(data))

# 正确方式(使用%格式化)
logger.info("Processing %s", data)

# 4. 记录异常信息
try:
    risky_operation()
except Exception:
    logger.exception("操作失败")  # 自动记录堆栈

# 5. 结构化日志
logger.info("Order created", extra={
    'order_id': 12345,
    'amount': 99.99,
    'user_id': 67890
})

# 6. 敏感信息过滤
class SensitiveFilter(logging.Filter):
    PATTERNS = ['password', 'token', 'secret', 'api_key']
    
    def filter(self, record):
        msg = record.getMessage().lower()
        return not any(p in msg for p in self.PATTERNS)

生产环境配置示例

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            'level': 'WARNING',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/var/log/app/warning.log',
            'maxBytes': 1024*1024*5,  # 5MB
            'backupCount': 5,
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': True,
        },
        'app': {
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
            'propagate': True,
        },
    }
}

总结

合理的日志策略是生产系统的基石。记住:选择合适的日志级别、结构化日志消息、配置多个handler、在生产环境使用WARNING以上级别。好的日志能让你在问题发生时快速定位和解决。