# 日志系统学习 - shared/logger.py ## 📋 模块概述 `shared/logger.py` 实现了Chat-Room项目的统一日志系统。一个好的日志系统是软件开发和运维的重要工具,它帮助我们调试问题、监控系统状态、分析用户行为。 ## 🎯 为什么需要日志系统? ### 开发阶段的价值 **调试问题**: ```python # 没有日志的代码 def authenticate_user(username, password): user = database.get_user(username) if user and verify_password(password, user.password_hash): return user return None # 不知道失败的原因 # 有日志的代码 def authenticate_user(username, password): logger.info(f"用户认证开始: {username}") user = database.get_user(username) if not user: logger.warning(f"用户不存在: {username}") return None if verify_password(password, user.password_hash): logger.info(f"用户认证成功: {username}") return user else: logger.warning(f"密码错误: {username}") return None ``` ### 生产环境的价值 **监控和分析**: - **性能监控**:记录操作耗时,发现性能瓶颈 - **错误追踪**:记录错误详情,快速定位问题 - **用户行为**:分析用户使用模式 - **安全审计**:记录敏感操作,发现安全问题 ## 🏗️ 日志系统架构 ### 日志级别设计 ```mermaid graph TD A[CRITICAL
严重错误] --> B[ERROR
错误] B --> C[WARNING
警告] C --> D[INFO
信息] D --> E[DEBUG
调试] style A fill:#ff6b6b style B fill:#ffa726 style C fill:#ffeb3b style D fill:#66bb6a style E fill:#42a5f5 ``` **级别说明**: - **CRITICAL (50)**:系统崩溃级别的错误 - **ERROR (40)**:严重错误,但程序可以继续运行 - **WARNING (30)**:警告信息,可能的问题 - **INFO (20)**:一般信息,记录重要事件 - **DEBUG (10)**:详细的调试信息 ### 日志器层次结构 ```python # Chat-Room项目的日志器层次 root_logger = logging.getLogger() # 根日志器 chatroom_logger = logging.getLogger("chatroom") # 项目根日志器 server_logger = logging.getLogger("chatroom.server") # 服务器日志器 client_logger = logging.getLogger("chatroom.client") # 客户端日志器 # 更细粒度的日志器 auth_logger = logging.getLogger("chatroom.server.auth") # 认证模块 db_logger = logging.getLogger("chatroom.server.database") # 数据库模块 ai_logger = logging.getLogger("chatroom.server.ai") # AI模块 ``` **层次结构的优势**: - **继承配置**:子日志器继承父日志器的配置 - **分级控制**:可以为不同模块设置不同的日志级别 - **便于管理**:统一的命名规范,便于配置和过滤 ## 🔧 日志配置系统 ### 基础配置函数 ```python def setup_logger(name: str, log_file: str = None, level: int = logging.INFO) -> logging.Logger: """ 设置日志器 Args: name: 日志器名称 log_file: 日志文件路径 level: 日志级别 Returns: 配置好的日志器 """ logger = logging.getLogger(name) logger.setLevel(level) # 避免重复添加处理器 if logger.handlers: return logger # 创建格式化器 formatter = logging.Formatter( '%(asctime)s [%(levelname)s] %(name)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # 控制台处理器 console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) logger.addHandler(console_handler) # 文件处理器(如果指定了文件) if log_file: # 确保日志目录存在 os.makedirs(os.path.dirname(log_file), exist_ok=True) file_handler = logging.FileHandler(log_file, encoding='utf-8') file_handler.setFormatter(formatter) logger.addHandler(file_handler) return logger ``` ### 高级配置特性 ```python def setup_rotating_logger(name: str, log_file: str, max_bytes: int = 10*1024*1024, # 10MB backup_count: int = 5) -> logging.Logger: """ 设置轮转日志器 Args: max_bytes: 单个日志文件最大大小 backup_count: 保留的备份文件数量 """ from logging.handlers import RotatingFileHandler logger = logging.getLogger(name) logger.setLevel(logging.INFO) # 轮转文件处理器 rotating_handler = RotatingFileHandler( log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8' ) formatter = logging.Formatter( '%(asctime)s [%(levelname)s] %(name)s: %(message)s' ) rotating_handler.setFormatter(formatter) logger.addHandler(rotating_handler) return logger ``` ## 📝 专用日志记录器 ### 数据库操作日志 ```python def log_database_operation(operation: str, table: str, user_id: int = None, **kwargs): """ 记录数据库操作日志 Args: operation: 操作类型 (SELECT, INSERT, UPDATE, DELETE) table: 表名 user_id: 用户ID **kwargs: 额外的上下文信息 """ logger = logging.getLogger("chatroom.database") context = { "operation": operation, "table": table, "user_id": user_id, **kwargs } # 使用结构化日志格式 logger.info(f"数据库操作: {operation} {table}", extra=context) ``` **使用示例**: ```python # 在数据库操作中使用 def create_user(self, username: str, password: str) -> int: log_database_operation("INSERT", "users", username=username) try: with self.get_connection() as conn: cursor = conn.cursor() cursor.execute( "INSERT INTO users (username, password_hash) VALUES (?, ?)", (username, hash_password(password)) ) user_id = cursor.lastrowid log_database_operation("INSERT", "users", user_id=user_id, username=username, status="success") return user_id except Exception as e: log_database_operation("INSERT", "users", username=username, status="error", error=str(e)) raise ``` ### AI操作日志 ```python def log_ai_operation(operation: str, model: str, user_id: int = None, **kwargs): """ 记录AI操作日志 Args: operation: 操作类型 (generate_reply, process_message) model: AI模型名称 user_id: 用户ID **kwargs: 额外信息(如响应时间、token数量等) """ logger = logging.getLogger("chatroom.ai") context = { "operation": operation, "model": model, "user_id": user_id, **kwargs } logger.info(f"AI操作: {operation} using {model}", extra=context) ``` ### 网络操作日志 ```python def log_network_operation(operation: str, client_info: dict = None, **kwargs): """ 记录网络操作日志 Args: operation: 操作类型 (connect, disconnect, send_message) client_info: 客户端信息 (IP, port, user_id) **kwargs: 额外信息 """ logger = logging.getLogger("chatroom.network") context = { "operation": operation, "client_info": client_info or {}, **kwargs } logger.info(f"网络操作: {operation}", extra=context) ``` ## 🎨 日志格式化 ### 自定义格式化器 ```python class ChatRoomFormatter(logging.Formatter): """Chat-Room专用日志格式化器""" def __init__(self): super().__init__() # 不同级别使用不同颜色(终端支持) self.COLORS = { 'DEBUG': '\033[36m', # 青色 'INFO': '\033[32m', # 绿色 'WARNING': '\033[33m', # 黄色 'ERROR': '\033[31m', # 红色 'CRITICAL': '\033[35m', # 紫色 'RESET': '\033[0m' # 重置 } def format(self, record): # 基础格式 log_time = self.formatTime(record, '%Y-%m-%d %H:%M:%S') level_name = record.levelname logger_name = record.name message = record.getMessage() # 添加颜色(如果是终端输出) if hasattr(record, 'stream') and hasattr(record.stream, 'isatty'): color = self.COLORS.get(level_name, '') reset = self.COLORS['RESET'] level_name = f"{color}{level_name}{reset}" # 构建基础日志 log_line = f"{log_time} [{level_name}] {logger_name}: {message}" # 添加额外的上下文信息 if hasattr(record, 'user_id') and record.user_id: log_line += f" user_id={record.user_id}" if hasattr(record, 'operation') and record.operation: log_line += f" operation={record.operation}" if hasattr(record, 'response_time') and record.response_time: log_line += f" response_time={record.response_time:.3f}s" return log_line ``` ### JSON格式日志 ```python import json class JSONFormatter(logging.Formatter): """JSON格式的日志格式化器""" def format(self, record): log_entry = { "timestamp": self.formatTime(record), "level": record.levelname, "logger": record.name, "message": record.getMessage(), "module": record.module, "function": record.funcName, "line": record.lineno } # 添加额外的字段 for key, value in record.__dict__.items(): if key not in ['name', 'msg', 'args', 'levelname', 'levelno', 'pathname', 'filename', 'module', 'lineno', 'funcName', 'created', 'msecs', 'relativeCreated', 'thread', 'threadName', 'processName', 'process']: log_entry[key] = value return json.dumps(log_entry, ensure_ascii=False) ``` ## 🔍 日志分析工具 ### 日志查看器 ```python class LogViewer: """日志查看和分析工具""" def __init__(self, log_file: str): self.log_file = log_file def tail_logs(self, lines: int = 50): """显示最新的日志""" try: with open(self.log_file, 'r', encoding='utf-8') as f: all_lines = f.readlines() recent_lines = all_lines[-lines:] for line in recent_lines: print(line.rstrip()) except FileNotFoundError: print(f"日志文件不存在: {self.log_file}") def filter_logs(self, level: str = None, keyword: str = None): """过滤日志""" try: with open(self.log_file, 'r', encoding='utf-8') as f: for line in f: # 级别过滤 if level and f"[{level}]" not in line: continue # 关键词过滤 if keyword and keyword not in line: continue print(line.rstrip()) except FileNotFoundError: print(f"日志文件不存在: {self.log_file}") def analyze_errors(self): """分析错误日志""" error_count = 0 error_types = {} try: with open(self.log_file, 'r', encoding='utf-8') as f: for line in f: if "[ERROR]" in line or "[CRITICAL]" in line: error_count += 1 # 提取错误类型 if ":" in line: error_msg = line.split(":", 2)[-1].strip() error_type = error_msg.split()[0] if error_msg else "Unknown" error_types[error_type] = error_types.get(error_type, 0) + 1 print(f"总错误数: {error_count}") print("错误类型统计:") for error_type, count in sorted(error_types.items(), key=lambda x: x[1], reverse=True): print(f" {error_type}: {count}") except FileNotFoundError: print(f"日志文件不存在: {self.log_file}") ``` ## 💡 实际应用示例 ### 服务器启动日志 ```python # server/main.py def start_server(): logger = get_logger("chatroom.server") logger.info("服务器启动开始") logger.info(f"配置信息: host={config.host}, port={config.port}") try: # 初始化数据库 logger.info("初始化数据库") init_database() logger.info("数据库初始化完成") # 启动AI服务 if config.ai_enabled: logger.info("启动AI服务") ai_manager = AIManager(config.ai_api_key) logger.info(f"AI服务状态: {'启用' if ai_manager.is_enabled() else '禁用'}") # 启动网络服务 logger.info(f"启动网络服务,监听 {config.host}:{config.port}") server = ChatRoomServer(config.host, config.port) server.start() logger.info("服务器启动成功") except Exception as e: logger.critical(f"服务器启动失败: {e}", exc_info=True) raise ``` ### 用户操作日志 ```python # 用户登录 def handle_login(self, client_socket, message: LoginRequest): logger = get_logger("chatroom.server.auth") client_ip = client_socket.getpeername()[0] logger.info("用户登录请求", extra={"username": message.username, "client_ip": client_ip}) try: user_info = self.user_manager.authenticate_user( message.username, message.password ) logger.info("用户登录成功", extra={"user_id": user_info['id'], "username": message.username, "client_ip": client_ip}) except AuthenticationError as e: logger.warning("用户登录失败", extra={"username": message.username, "client_ip": client_ip, "reason": str(e)}) ``` ## 🛠️ 日志配置管理 ### 配置文件方式 ```yaml # config/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:%(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/chatroom.log maxBytes: 10485760 # 10MB backupCount: 5 encoding: utf-8 loggers: chatroom: level: DEBUG handlers: [console, file] propagate: false chatroom.server: level: INFO handlers: [file] propagate: true chatroom.database: level: WARNING handlers: [file] propagate: true root: level: WARNING handlers: [console] ``` ### 加载配置 ```python import yaml import logging.config def load_logging_config(config_file: str = "config/logging.yaml"): """加载日志配置""" try: with open(config_file, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) # 确保日志目录存在 for handler_name, handler_config in config.get('handlers', {}).items(): if 'filename' in handler_config: log_dir = os.path.dirname(handler_config['filename']) os.makedirs(log_dir, exist_ok=True) logging.config.dictConfig(config) print("日志配置加载成功") except Exception as e: print(f"日志配置加载失败: {e}") # 使用基础配置 logging.basicConfig(level=logging.INFO) ``` ## 🤔 思考题 1. **如何平衡日志的详细程度和性能?** - 合理设置日志级别 - 使用异步日志记录 - 避免在循环中记录过多日志 2. **敏感信息如何处理?** - 密码等敏感信息不记录 - 使用脱敏处理 - 分离敏感和非敏感日志 3. **生产环境的日志管理策略?** - 日志轮转和清理 - 集中化日志收集 - 实时监控和告警 ## 📚 扩展学习 ### Python日志进阶 - **异步日志**:使用`concurrent.futures`提高性能 - **结构化日志**:JSON格式便于分析 - **日志聚合**:ELK Stack、Fluentd等工具 ### 监控和运维 - **日志分析**:正则表达式、数据分析 - **告警系统**:基于日志的自动告警 - **性能监控**:APM工具集成 --- **下一步**:学习服务器核心模块 → [../03-server-modules/](../03-server-modules/)