deepresearch/app/utils/logger.py
2025-07-02 15:35:36 +08:00

223 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
日志配置工具
"""
import os
import logging
import logging.handlers
from datetime import datetime
from pythonjsonlogger import jsonlogger
def setup_logging(app):
"""设置应用日志"""
log_level = app.config.get('LOG_LEVEL', 'INFO')
log_dir = app.config.get('LOG_DIR', 'logs')
# 确保日志目录存在
os.makedirs(log_dir, exist_ok=True)
# 设置根日志器
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, log_level))
# 清除现有的处理器
root_logger.handlers = []
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(getattr(logging, log_level))
console_formatter = ColoredFormatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)
root_logger.addHandler(console_handler)
# 文件处理器 - 一般日志
file_handler = logging.handlers.RotatingFileHandler(
os.path.join(log_dir, 'app.log'),
maxBytes=10485760, # 10MB
backupCount=10
)
file_handler.setLevel(logging.INFO)
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(file_formatter)
root_logger.addHandler(file_handler)
# 错误日志文件
error_handler = logging.handlers.RotatingFileHandler(
os.path.join(log_dir, 'error.log'),
maxBytes=10485760,
backupCount=10
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(file_formatter)
root_logger.addHandler(error_handler)
# JSON格式日志用于日志分析
json_handler = logging.handlers.RotatingFileHandler(
os.path.join(log_dir, 'app.json.log'),
maxBytes=10485760,
backupCount=10
)
json_formatter = CustomJsonFormatter()
json_handler.setFormatter(json_formatter)
json_handler.setLevel(logging.INFO)
root_logger.addHandler(json_handler)
# 研究任务专用日志
research_logger = logging.getLogger('research')
research_handler = logging.handlers.RotatingFileHandler(
os.path.join(log_dir, 'research.log'),
maxBytes=10485760,
backupCount=10
)
research_handler.setFormatter(file_formatter)
research_logger.addHandler(research_handler)
research_logger.setLevel(logging.DEBUG)
# 设置第三方库的日志级别
logging.getLogger('urllib3').setLevel(logging.WARNING)
logging.getLogger('requests').setLevel(logging.WARNING)
logging.getLogger('openai').setLevel(logging.WARNING)
app.logger.info(f"日志系统初始化完成,级别: {log_level}")
class ColoredFormatter(logging.Formatter):
"""带颜色的控制台日志格式化器"""
COLORS = {
'DEBUG': '\033[36m', # 青色
'INFO': '\033[32m', # 绿色
'WARNING': '\033[33m', # 黄色
'ERROR': '\033[31m', # 红色
'CRITICAL': '\033[35m', # 紫色
}
RESET = '\033[0m'
def format(self, record):
log_color = self.COLORS.get(record.levelname, self.RESET)
record.levelname = f"{log_color}{record.levelname}{self.RESET}"
return super().format(record)
class CustomJsonFormatter(jsonlogger.JsonFormatter):
"""自定义JSON日志格式化器"""
def add_fields(self, log_record, record, message_dict):
super().add_fields(log_record, record, message_dict)
# 添加额外字段
log_record['timestamp'] = datetime.utcnow().isoformat()
log_record['level'] = record.levelname
log_record['logger'] = record.name
# 添加异常信息
if record.exc_info:
log_record['exception'] = self.formatException(record.exc_info)
# 添加额外的上下文信息
if hasattr(record, 'session_id'):
log_record['session_id'] = record.session_id
if hasattr(record, 'subtopic_id'):
log_record['subtopic_id'] = record.subtopic_id
if hasattr(record, 'user_id'):
log_record['user_id'] = record.user_id
def get_logger(name: str) -> logging.Logger:
"""获取指定名称的日志器"""
return logging.getLogger(name)
def log_performance(func):
"""性能日志装饰器"""
import functools
import time
@functools.wraps(func)
def wrapper(*args, **kwargs):
logger = logging.getLogger(func.__module__)
start_time = time.time()
try:
result = func(*args, **kwargs)
elapsed_time = time.time() - start_time
logger.info(
f"{func.__name__} 执行成功,耗时: {elapsed_time:.3f}",
extra={'performance': {'function': func.__name__, 'duration': elapsed_time}}
)
return result
except Exception as e:
elapsed_time = time.time() - start_time
logger.error(
f"{func.__name__} 执行失败,耗时: {elapsed_time:.3f}秒,错误: {str(e)}",
extra={'performance': {'function': func.__name__, 'duration': elapsed_time}},
exc_info=True
)
raise
return wrapper
def log_api_call(service_name: str):
"""API调用日志装饰器"""
def decorator(func):
import functools
@functools.wraps(func)
def wrapper(*args, **kwargs):
logger = logging.getLogger('api_calls')
# 记录请求
logger.info(
f"调用 {service_name} API: {func.__name__}",
extra={
'api_service': service_name,
'api_method': func.__name__,
'args': str(args)[:200], # 限制长度
'kwargs': str(kwargs)[:200]
}
)
try:
result = func(*args, **kwargs)
logger.info(
f"{service_name} API 调用成功: {func.__name__}",
extra={
'api_service': service_name,
'api_method': func.__name__,
'success': True
}
)
return result
except Exception as e:
logger.error(
f"{service_name} API 调用失败: {func.__name__} - {str(e)}",
extra={
'api_service': service_name,
'api_method': func.__name__,
'success': False,
'error': str(e)
},
exc_info=True
)
raise
return wrapper
return decorator
class SessionLoggerAdapter(logging.LoggerAdapter):
"""带会话ID的日志适配器"""
def process(self, msg, kwargs):
if 'extra' not in kwargs:
kwargs['extra'] = {}
if hasattr(self, 'session_id'):
kwargs['extra']['session_id'] = self.session_id
return msg, kwargs
def get_session_logger(session_id: str, logger_name: str = 'research') -> SessionLoggerAdapter:
"""获取带会话ID的日志器"""
logger = logging.getLogger(logger_name)
adapter = SessionLoggerAdapter(logger, {})
adapter.session_id = session_id
return adapter