跳转至

Logger API 参考

qka.utils.logger

JSONFormatter

Bases: Formatter

JSON格式的日志格式化器

Source code in qka/utils/logger.py
class JSONFormatter(logging.Formatter):
    """JSON格式的日志格式化器"""

    def format(self, record):
        log_entry = {
            'timestamp': datetime.fromtimestamp(record.created).isoformat(),
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno
        }

        # 添加异常信息
        if record.exc_info:
            log_entry['exception'] = self.formatException(record.exc_info)

        # 添加自定义字段
        if hasattr(record, 'extra_fields'):
            log_entry.update(record.extra_fields)

        return json.dumps(log_entry, ensure_ascii=False)

ColoredFormatter

Bases: Formatter

带颜色的控制台格式化器

Source code in qka/utils/logger.py
class ColoredFormatter(logging.Formatter):
    """带颜色的控制台格式化器"""

    # ANSI颜色代码
    COLORS = {
        'DEBUG': '\033[36m',     # 青色
        'INFO': '\033[32m',      # 绿色
        'WARNING': '\033[33m',   # 黄色
        'ERROR': '\033[31m',     # 红色
        'CRITICAL': '\033[35m',  # 紫色
        'RESET': '\033[0m'       # 重置
    }

    def format(self, record):
        color = self.COLORS.get(record.levelname, self.COLORS['RESET'])
        reset = self.COLORS['RESET']

        # 格式化消息
        formatted = super().format(record)
        return f"{color}{formatted}{reset}"

RemoveAnsiEscapeCodes

Bases: Filter

移除ANSI转义码的过滤器

Source code in qka/utils/logger.py
class RemoveAnsiEscapeCodes(logging.Filter):
    """移除ANSI转义码的过滤器"""

    def filter(self, record):
        record.msg = re.sub(r'\033\[[0-9;]*m', '', str(record.msg))
        return True

StructuredLogger

结构化日志记录器

Source code in qka/utils/logger.py
class StructuredLogger:
    """结构化日志记录器"""

    def __init__(self, name: str = 'qka'):
        self.logger = logging.getLogger(name)
        self._lock = threading.Lock()

    def log(self, level: int, message: str, **kwargs):
        """记录结构化日志"""
        with self._lock:
            # 创建日志记录,附加额外字段
            if kwargs:
                record = self.logger.makeRecord(
                    self.logger.name, level, '', 0, message, (), None
                )
                record.extra_fields = kwargs
                self.logger.handle(record)
            else:
                self.logger.log(level, message)

    def debug(self, message: str, **kwargs):
        self.log(logging.DEBUG, message, **kwargs)

    def info(self, message: str, **kwargs):
        self.log(logging.INFO, message, **kwargs)

    def warning(self, message: str, **kwargs):
        self.log(logging.WARNING, message, **kwargs)

    def error(self, message: str, **kwargs):
        self.log(logging.ERROR, message, **kwargs)

    def critical(self, message: str, **kwargs):
        self.log(logging.CRITICAL, message, **kwargs)

log

log(level: int, message: str, **kwargs)

记录结构化日志

Source code in qka/utils/logger.py
def log(self, level: int, message: str, **kwargs):
    """记录结构化日志"""
    with self._lock:
        # 创建日志记录,附加额外字段
        if kwargs:
            record = self.logger.makeRecord(
                self.logger.name, level, '', 0, message, (), None
            )
            record.extra_fields = kwargs
            self.logger.handle(record)
        else:
            self.logger.log(level, message)

create_logger

create_logger(name: str = 'qka', level: str = 'INFO', console_output: bool = True, file_output: bool = True, log_dir: str = 'logs', max_file_size: str = '10MB', backup_count: int = 10, json_format: bool = False, colored_console: bool = True)

创建增强的日志记录器

Parameters:

Name Type Description Default
name str

日志记录器名称

'qka'
level str

日志级别

'INFO'
console_output bool

是否输出到控制台

True
file_output bool

是否输出到文件

True
log_dir str

日志文件目录

'logs'
max_file_size str

最大文件大小

'10MB'
backup_count int

备份文件数量

10
json_format bool

是否使用JSON格式

False
colored_console bool

控制台是否使用颜色

True
Source code in qka/utils/logger.py
def create_logger(
    name: str = 'qka',
    level: str = 'INFO',
    console_output: bool = True,
    file_output: bool = True,
    log_dir: str = 'logs',
    max_file_size: str = '10MB',
    backup_count: int = 10,
    json_format: bool = False,
    colored_console: bool = True
):
    """
    创建增强的日志记录器

    Args:
        name: 日志记录器名称
        level: 日志级别
        console_output: 是否输出到控制台
        file_output: 是否输出到文件
        log_dir: 日志文件目录
        max_file_size: 最大文件大小
        backup_count: 备份文件数量
        json_format: 是否使用JSON格式
        colored_console: 控制台是否使用颜色
    """
    logger = logging.getLogger(name)
    logger.setLevel(getattr(logging, level.upper()))

    # 清除现有处理器
    logger.handlers.clear()

    # 创建格式化器
    if json_format:
        file_formatter = JSONFormatter()
        console_formatter = JSONFormatter()
    else:
        file_formatter = logging.Formatter(
            '[%(levelname)s][%(asctime)s][%(name)s] %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        console_formatter = ColoredFormatter(
            '[%(levelname)s][%(asctime)s] %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        ) if colored_console else file_formatter

    # 控制台处理器
    if console_output:
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(console_formatter)
        logger.addHandler(stream_handler)

    # 文件处理器
    if file_output:
        # 创建日志文件夹
        Path(log_dir).mkdir(parents=True, exist_ok=True)

        # 解析文件大小
        size_map = {'KB': 1024, 'MB': 1024*1024, 'GB': 1024*1024*1024}
        size_unit = max_file_size[-2:].upper()
        size_value = int(max_file_size[:-2])
        max_bytes = size_value * size_map.get(size_unit, 1024*1024)

        # 创建轮转文件处理器
        file_handler = RotatingFileHandler(
            f"{log_dir}/{date.today().strftime('%Y-%m-%d')}.log",
            maxBytes=max_bytes,
            backupCount=backup_count,
            encoding='utf-8'
        )
        file_handler.setLevel(logging.DEBUG)
        file_handler.setFormatter(file_formatter)
        file_handler.addFilter(RemoveAnsiEscapeCodes())
        logger.addHandler(file_handler)

    return logger

setup_logging_from_config

setup_logging_from_config(config_dict: Dict[str, Any]) -> logging.Logger

从配置字典设置日志

Source code in qka/utils/logger.py
def setup_logging_from_config(config_dict: Dict[str, Any]) -> logging.Logger:
    """从配置字典设置日志"""
    return create_logger(**config_dict)

get_structured_logger

get_structured_logger(name: str = 'qka') -> StructuredLogger

获取结构化日志记录器

Source code in qka/utils/logger.py
def get_structured_logger(name: str = 'qka') -> StructuredLogger:
    """获取结构化日志记录器"""
    return StructuredLogger(name)

add_wechat_handler

add_wechat_handler(logger_instance: Logger, webhook_url: str, level: str = 'ERROR')

为日志记录器添加微信通知处理器

Parameters:

Name Type Description Default
logger_instance Logger

日志记录器实例

required
webhook_url str

企业微信机器人webhook地址

required
level str

通知级别

'ERROR'
Source code in qka/utils/logger.py
def add_wechat_handler(logger_instance: logging.Logger, webhook_url: str, level: str = 'ERROR'):
    """
    为日志记录器添加微信通知处理器

    Args:
        logger_instance: 日志记录器实例
        webhook_url: 企业微信机器人webhook地址
        level: 通知级别
    """
    wechat_handler = WeChatHandler(webhook_url)
    wechat_handler.setLevel(getattr(logging, level.upper()))

    # 创建简单格式化器用于微信消息
    formatter = logging.Formatter(
        '[%(levelname)s][%(asctime)s] %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    wechat_handler.setFormatter(formatter)
    wechat_handler.addFilter(RemoveAnsiEscapeCodes())
    logger_instance.addHandler(wechat_handler)

日志系统使用指南

基本用法

from qka.utils.logger import Logger

# 创建日志实例
logger = Logger(name='my_app')

# 基本日志记录
logger.debug("调试信息")
logger.info("普通信息")
logger.warning("警告信息")
logger.error("错误信息")
logger.critical("严重错误")

彩色日志输出

from qka.utils.logger import Logger, ColorFormatter

# 启用彩色输出
logger = Logger(name='colorful_app', enable_color=True)

# 日志会以不同颜色显示
logger.info("这是蓝色的信息日志")
logger.warning("这是黄色的警告日志")
logger.error("这是红色的错误日志")

结构化日志

from qka.utils.logger import StructuredLogger

# 创建结构化日志记录器
logger = StructuredLogger(name='structured_app')

# 记录结构化日志
logger.info("用户登录", extra={
    'user_id': 12345,
    'username': 'john_doe',
    'ip_address': '192.168.1.100',
    'user_agent': 'Mozilla/5.0...'
})

logger.error("数据库连接失败", extra={
    'database': 'mysql',
    'host': 'localhost',
    'port': 3306,
    'error_code': 2003
})

文件日志配置

# 配置文件日志
logger = Logger(
    name='file_app',
    log_file='logs/app.log',
    max_size='10MB',
    backup_count=5,
    compression=True
)

# 日志会自动轮转和压缩
for i in range(10000):
    logger.info(f"日志消息 {i}")

微信通知集成

# 配置微信通知
logger = Logger(
    name='notify_app',
    wechat_webhook='https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY'
)

# 发送重要通知到微信
logger.critical("系统出现严重错误", notify_wechat=True)
logger.error("数据库连接失败", notify_wechat=True)

性能监控日志

import time
from qka.utils.logger import Logger

logger = Logger(name='perf_app')

# 记录函数执行时间
@logger.log_performance
def slow_function():
    time.sleep(2)
    return "完成"

# 记录代码块执行时间
with logger.timer("数据处理"):
    # 数据处理代码
    process_data()

日志过滤和格式化

import logging
from qka.utils.logger import Logger

# 自定义日志过滤器
class SensitiveFilter(logging.Filter):
    def filter(self, record):
        # 过滤敏感信息
        if hasattr(record, 'msg'):
            record.msg = record.msg.replace('password=', 'password=***')
        return True

# 应用过滤器
logger = Logger(name='secure_app')
logger.logger.addFilter(SensitiveFilter())

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

上下文日志

from contextvars import ContextVar
from qka.utils.logger import Logger

# 定义上下文变量
request_id = ContextVar('request_id', default='unknown')

# 自定义格式化器
class ContextFormatter(logging.Formatter):
    def format(self, record):
        record.request_id = request_id.get()
        return super().format(record)

# 配置日志
logger = Logger(name='context_app')
formatter = ContextFormatter(
    '%(asctime)s [%(request_id)s] %(name)s - %(levelname)s - %(message)s'
)
logger.set_formatter(formatter)

# 使用上下文
request_id.set('req_123456')
logger.info("处理请求")  # 日志中会包含 request_id

异步日志

import asyncio
from qka.utils.logger import Logger

# 异步日志处理
logger = Logger(name='async_app', async_mode=True)

async def async_operation():
    logger.info("开始异步操作")
    await asyncio.sleep(1)
    logger.info("异步操作完成")

# 运行异步代码
asyncio.run(async_operation())

日志分析和监控

# 日志统计
stats = logger.get_statistics()
print(f"总日志条数: {stats['total_logs']}")
print(f"错误日志数: {stats['error_count']}")
print(f"警告日志数: {stats['warning_count']}")

# 设置日志阈值告警
logger.set_error_threshold(100)  # 错误数超过100时告警
logger.set_warning_threshold(500)  # 警告数超过500时告警

# 日志采样(高频日志场景)
logger.set_sampling_rate(0.1)  # 只记录10%的日志

多进程日志

import multiprocessing
from qka.utils.logger import Logger

def worker_process(worker_id):
    # 每个进程创建独立的日志实例
    logger = Logger(
        name=f'worker_{worker_id}',
        log_file=f'logs/worker_{worker_id}.log'
    )

    for i in range(100):
        logger.info(f"工作进程 {worker_id} 处理任务 {i}")

# 启动多个工作进程
processes = []
for i in range(4):
    p = multiprocessing.Process(target=worker_process, args=(i,))
    processes.append(p)
    p.start()

for p in processes:
    p.join()

配置示例

日志配置文件

# logging.yaml
version: 1
disable_existing_loggers: false

formatters:
  standard:
    format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
    datefmt: '%Y-%m-%d %H:%M:%S'

  detailed:
    format: '%(asctime)s [%(levelname)s] %(name)s [%(filename)s:%(lineno)d] %(message)s'
    datefmt: '%Y-%m-%d %H:%M:%S'

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

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

loggers:
  qka:
    level: DEBUG
    handlers: [console, file]
    propagate: false

root:
  level: WARNING
  handlers: [console]

加载配置文件

import logging.config
import yaml

# 加载日志配置
with open('logging.yaml', 'r') as f:
    config = yaml.safe_load(f.read())
    logging.config.dictConfig(config)

# 使用配置的日志器
logger = logging.getLogger('qka.strategy')
logger.info("策略启动")

最佳实践

  1. 日志级别使用
  2. DEBUG: 详细的调试信息
  3. INFO: 一般信息记录
  4. WARNING: 警告但不影响运行
  5. ERROR: 错误信息,影响功能
  6. CRITICAL: 严重错误,可能导致程序终止

  7. 结构化日志

  8. 使用统一的日志格式
  9. 包含足够的上下文信息
  10. 便于日志分析和查询

  11. 性能考虑

  12. 避免在热点路径记录大量日志
  13. 使用异步日志处理
  14. 合理设置日志级别

  15. 安全注意

  16. 不要记录敏感信息
  17. 使用日志过滤器清理敏感数据
  18. 控制日志文件访问权限