repo_description: LLM驱动的 A/H/美股智能分析器：多数据源行情 + 实时新闻 + LLM决策仪表盘 + 多渠道推送，零成本定时运行，纯白嫖. LLM-powered stock analysis system for A/H/US markets.
repo_topics: agent ai aigc gemini llm quant quantitative-trading rag stock
code: # -*- coding: utf-8 -*-
  """
  ===================================
  日志配置模块 - 统一的日志系统初始化
  ===================================
  
  职责：
  1. 提供统一的日志格式和配置常量
  2. 支持控制台 + 文件（常规/调试）三层日志输出
  3. 自动降低第三方库日志级别
  """
  
  import logging
  import sys
  from datetime import datetime
  from logging.handlers import RotatingFileHandler
  from pathlib import Path
  from typing import List, Optional
  
  
  LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(pathname)s:%(lineno)d | %(message)s"
  LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
  
  
  class RelativePathFormatter(logging.Formatter):
      """自定义 Formatter，输出相对路径而非绝对路径"""
  
      def __init__(self, fmt=None, datefmt=None, relative_to=None):
          super().__init__(fmt, datefmt)
          self.relative_to = Path(relative_to) if relative_to else Path.cwd()
  
      def format(self, record):
          # 将绝对路径转为相对路径
          try:
              record.pathname = str(Path(record.pathname).relative_to(self.relative_to))
          except ValueError:
              # 如果无法转换为相对路径，保持原样
              pass
          return super().format(record)
  
  
  
  # 默认需要降低日志级别的第三方库
  DEFAULT_QUIET_LOGGERS = [
      'urllib3',
      'sqlalchemy',
      'google',
      'httpx',
  ]
  
  
  def setup_logging(
      log_prefix: str = "app",
      log_dir: str = "./logs",
      console_level: Optional[int] = None,
      debug: bool = False,
      extra_quiet_loggers: Optional[List[str]] = None,
  ) -> None:
      """
      统一的日志系统初始化
  
      配置三层日志输出：
      1. 控制台：根据 debug 参数或 console_level 设置级别
      2. 常规日志文件：INFO 级别，10MB 轮转，保留 5 个备份
      3. 调试日志文件：DEBUG 级别，50MB 轮转，保留 3 个备份
  
      Args:
          log_prefix: 日志文件名前缀（如 "api_server" -> api_server_20240101.log）
          log_dir: 日志文件目录，默认 ./logs
          console_level: 控制台日志级别（可选，优先于 debug 参数）
          debug: 是否启用调试模式（控制台输出 DEBUG 级别）
          extra_quiet_loggers: 额外需要降低日志级别的第三方库列表
      """
      # 确定控制台日志级别
      if console_level is not None:
          level = console_level
      else:
          level = logging.DEBUG if debug else logging.INFO
  
      # 创建日志目录
      log_path = Path(log_dir)
      log_path.mkdir(parents=True, exist_ok=True)
  
      # 日志文件路径（按日期分文件）
      today_str = datetime.now().strftime('%Y%m%d')
      log_file = log_path / f"{log_prefix}_{today_str}.log"
      debug_log_file = log_path / f"{log_prefix}_debug_{today_str}.log"
  
      # 配置根 logger
      root_logger = logging.getLogger()
      root_logger.setLevel(logging.DEBUG)  # 根 logger 设为 DEBUG，由 handler 控制输出级别
  
      # 清除已有 handler，避免重复添加
      if root_logger.handlers:
          root_logger.handlers.clear()
      # 创建相对路径 Formatter（相对于项目根目录）
      project_root = Path.cwd()
      rel_formatter = RelativePathFormatter(
          LOG_FORMAT, LOG_DATE_FORMAT, relative_to=project_root
      )
      # Handler 1: 控制台输出
      console_handler = logging.StreamHandler(sys.stdout)
      console_handler.setLevel(level)
      console_handler.setFormatter(rel_formatter)
      root_logger.addHandler(console_handler)
  
      # Handler 2: 常规日志文件（INFO 级别，10MB 轮转）
      file_handler = RotatingFileHandler(
          log_file,
          maxBytes=10 * 1024 * 1024,  # 10MB
          backupCount=5,
          encoding='utf-8'
      )
      file_handler.setLevel(logging.INFO)
      file_handler.setFormatter(rel_formatter)
      root_logger.addHandler(file_handler)
  
      # Handler 3: 调试日志文件（DEBUG 级别，包含所有详细信息）
      debug_handler = RotatingFileHandler(
          debug_log_file,
          maxBytes=50 * 1024 * 1024,  # 50MB
          backupCount=3,
          encoding='utf-8'
      )
      debug_handler.setLevel(logging.DEBUG)
      debug_handler.setFormatter(rel_formatter)
      root_logger.addHandler(debug_handler)
  
      # 降低第三方库的日志级别
      quiet_loggers = DEFAULT_QUIET_LOGGERS.copy()
      if extra_quiet_loggers:
          quiet_loggers.extend(extra_quiet_loggers)
  
      for logger_name in quiet_loggers:
          logging.getLogger(logger_name).setLevel(logging.WARNING)
  
      # 输出初始化完成信息（使用相对路径）
      try:
          rel_log_path = log_path.resolve().relative_to(project_root)
      except ValueError:
          rel_log_path = log_path
  
      try:
          rel_log_file = log_file.resolve().relative_to(project_root)
      except ValueError:
          rel_log_file = log_file
  
      try:
          rel_debug_log_file = debug_log_file.resolve().relative_to(project_root)
      except ValueError:
          rel_debug_log_file = debug_log_file
  
      logging.info(f"日志系统初始化完成，日志目录: {rel_log_path}")
      logging.info(f"常规日志: {rel_log_file}")
      logging.info(f"调试日志: {rel_debug_log_file}")
  