| | import logging |
| | import platform |
| | import re |
| | import sys |
| | from typing import Dict, Optional |
| |
|
| | |
| | COLORS = { |
| | "DEBUG": "\033[34m", |
| | "INFO": "\033[32m", |
| | "WARNING": "\033[33m", |
| | "ERROR": "\033[31m", |
| | "CRITICAL": "\033[1;31m", |
| | } |
| |
|
| |
|
| | |
| | if platform.system() == "Windows": |
| | import ctypes |
| |
|
| | kernel32 = ctypes.windll.kernel32 |
| | kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) |
| |
|
| |
|
| | class ColoredFormatter(logging.Formatter): |
| | """ |
| | 自定义的日志格式化器,添加颜色支持 |
| | """ |
| |
|
| | def format(self, record): |
| | |
| | color = COLORS.get(record.levelname, "") |
| | |
| | record.levelname = f"{color}{record.levelname}\033[0m" |
| | |
| | record.fileloc = f"[{record.filename}:{record.lineno}]" |
| | return super().format(record) |
| |
|
| |
|
| | class AccessLogFormatter(logging.Formatter): |
| | """ |
| | Custom access log formatter that redacts API keys in URLs |
| | """ |
| |
|
| | |
| | API_KEY_PATTERNS = [ |
| | r"\bAIza[0-9A-Za-z_-]{35}", |
| | r"\bsk-[0-9A-Za-z_-]{20,}", |
| | ] |
| |
|
| | def __init__(self, *args, **kwargs): |
| | super().__init__(*args, **kwargs) |
| | |
| | self.compiled_patterns = [ |
| | re.compile(pattern) for pattern in self.API_KEY_PATTERNS |
| | ] |
| |
|
| | def format(self, record): |
| | |
| | formatted_msg = super().format(record) |
| |
|
| | |
| | return self._redact_api_keys_in_message(formatted_msg) |
| |
|
| | def _redact_api_keys_in_message(self, message: str) -> str: |
| | """ |
| | Replace API keys in log message with redacted versions |
| | """ |
| | try: |
| | for pattern in self.compiled_patterns: |
| |
|
| | def replace_key(match): |
| | key = match.group(0) |
| | return redact_key_for_logging(key) |
| |
|
| | message = pattern.sub(replace_key, message) |
| |
|
| | return message |
| | except Exception as e: |
| | |
| | import logging |
| |
|
| | logger = logging.getLogger(__name__) |
| | logger.error(f"Error redacting API keys in access log: {e}") |
| | return "[LOG_REDACTION_ERROR]" |
| |
|
| |
|
| | def redact_key_for_logging(key: str) -> str: |
| | """ |
| | Redacts API key for secure logging by showing only first and last 6 characters. |
| | |
| | Args: |
| | key: API key to redact |
| | |
| | Returns: |
| | str: Redacted key in format "first6...last6" or descriptive placeholder for edge cases |
| | """ |
| | if not key: |
| | return key |
| |
|
| | if len(key) <= 12: |
| | return f"{key[:3]}...{key[-3:]}" |
| | else: |
| | return f"{key[:6]}...{key[-6:]}" |
| |
|
| |
|
| | |
| | FORMATTER = ColoredFormatter( |
| | "%(asctime)s | %(levelname)-17s | %(fileloc)-30s | %(message)s" |
| | ) |
| |
|
| | |
| | LOG_LEVELS = { |
| | "debug": logging.DEBUG, |
| | "info": logging.INFO, |
| | "warning": logging.WARNING, |
| | "error": logging.ERROR, |
| | "critical": logging.CRITICAL, |
| | } |
| |
|
| |
|
| | class Logger: |
| | def __init__(self): |
| | pass |
| |
|
| | _loggers: Dict[str, logging.Logger] = {} |
| |
|
| | @staticmethod |
| | def setup_logger(name: str) -> logging.Logger: |
| | """ |
| | 设置并获取logger |
| | :param name: logger名称 |
| | :return: logger实例 |
| | """ |
| | |
| | from app.config.config import settings |
| |
|
| | |
| | log_level_str = settings.LOG_LEVEL.lower() |
| | level = LOG_LEVELS.get(log_level_str, logging.INFO) |
| |
|
| | if name in Logger._loggers: |
| | |
| | existing_logger = Logger._loggers[name] |
| | if existing_logger.level != level: |
| | existing_logger.setLevel(level) |
| | return existing_logger |
| |
|
| | logger = logging.getLogger(name) |
| | logger.setLevel(level) |
| | logger.propagate = False |
| |
|
| | |
| | console_handler = logging.StreamHandler(sys.stdout) |
| | console_handler.setFormatter(FORMATTER) |
| | logger.addHandler(console_handler) |
| |
|
| | Logger._loggers[name] = logger |
| | return logger |
| |
|
| | @staticmethod |
| | def get_logger(name: str) -> Optional[logging.Logger]: |
| | """ |
| | 获取已存在的logger |
| | :param name: logger名称 |
| | :return: logger实例或None |
| | """ |
| | return Logger._loggers.get(name) |
| |
|
| | @staticmethod |
| | def update_log_levels(log_level: str): |
| | """ |
| | 根据当前的全局配置更新所有已创建 logger 的日志级别。 |
| | """ |
| | log_level_str = log_level.lower() |
| | new_level = LOG_LEVELS.get(log_level_str, logging.INFO) |
| |
|
| | updated_count = 0 |
| | for logger_name, logger_instance in Logger._loggers.items(): |
| | if logger_instance.level != new_level: |
| | logger_instance.setLevel(new_level) |
| | |
| | |
| | updated_count += 1 |
| |
|
| |
|
| | |
| | def get_openai_logger(): |
| | return Logger.setup_logger("openai") |
| |
|
| |
|
| | def get_gemini_logger(): |
| | return Logger.setup_logger("gemini") |
| |
|
| |
|
| | def get_chat_logger(): |
| | return Logger.setup_logger("chat") |
| |
|
| |
|
| | def get_model_logger(): |
| | return Logger.setup_logger("model") |
| |
|
| |
|
| | def get_security_logger(): |
| | return Logger.setup_logger("security") |
| |
|
| |
|
| | def get_key_manager_logger(): |
| | return Logger.setup_logger("key_manager") |
| |
|
| |
|
| | def get_main_logger(): |
| | return Logger.setup_logger("main") |
| |
|
| |
|
| | def get_embeddings_logger(): |
| | return Logger.setup_logger("embeddings") |
| |
|
| |
|
| | def get_request_logger(): |
| | return Logger.setup_logger("request") |
| |
|
| |
|
| | def get_retry_logger(): |
| | return Logger.setup_logger("retry") |
| |
|
| |
|
| | def get_image_create_logger(): |
| | return Logger.setup_logger("image_create") |
| |
|
| |
|
| | def get_exceptions_logger(): |
| | return Logger.setup_logger("exceptions") |
| |
|
| |
|
| | def get_application_logger(): |
| | return Logger.setup_logger("application") |
| |
|
| |
|
| | def get_initialization_logger(): |
| | return Logger.setup_logger("initialization") |
| |
|
| |
|
| | def get_middleware_logger(): |
| | return Logger.setup_logger("middleware") |
| |
|
| |
|
| | def get_routes_logger(): |
| | return Logger.setup_logger("routes") |
| |
|
| |
|
| | def get_config_routes_logger(): |
| | return Logger.setup_logger("config_routes") |
| |
|
| |
|
| | def get_config_logger(): |
| | return Logger.setup_logger("config") |
| |
|
| |
|
| | def get_database_logger(): |
| | return Logger.setup_logger("database") |
| |
|
| |
|
| | def get_log_routes_logger(): |
| | return Logger.setup_logger("log_routes") |
| |
|
| |
|
| | def get_stats_logger(): |
| | return Logger.setup_logger("stats") |
| |
|
| |
|
| | def get_update_logger(): |
| | return Logger.setup_logger("update_service") |
| |
|
| |
|
| | def get_scheduler_routes(): |
| | return Logger.setup_logger("scheduler_routes") |
| |
|
| |
|
| | def get_message_converter_logger(): |
| | return Logger.setup_logger("message_converter") |
| |
|
| |
|
| | def get_api_client_logger(): |
| | return Logger.setup_logger("api_client") |
| |
|
| |
|
| | def get_openai_compatible_logger(): |
| | return Logger.setup_logger("openai_compatible") |
| |
|
| |
|
| | def get_error_log_logger(): |
| | return Logger.setup_logger("error_log") |
| |
|
| |
|
| | def get_request_log_logger(): |
| | return Logger.setup_logger("request_log") |
| |
|
| |
|
| | def get_files_logger(): |
| | return Logger.setup_logger("files") |
| |
|
| |
|
| | def get_vertex_express_logger(): |
| | return Logger.setup_logger("vertex_express") |
| |
|
| |
|
| | def get_gemini_embedding_logger(): |
| | return Logger.setup_logger("gemini_embedding") |
| |
|
| |
|
| | def setup_access_logging(): |
| | """ |
| | Configure uvicorn access logging with API key redaction |
| | |
| | This function sets up a custom access log formatter that automatically |
| | redacts API keys in HTTP access logs. It works by: |
| | |
| | 1. Intercepting uvicorn's access log messages |
| | 2. Using regex patterns to find API keys in URLs |
| | 3. Replacing them with redacted versions (first6...last6) |
| | |
| | Supported API key formats: |
| | - Google/Gemini API keys: AIza[35 chars] |
| | - OpenAI API keys: sk-[48 chars] |
| | - General sk- prefixed keys: sk-[20+ chars] |
| | |
| | Usage: |
| | - Automatically called in main.py when running with uvicorn |
| | - For production deployment with gunicorn, ensure this is called in startup |
| | """ |
| | |
| | access_logger = logging.getLogger("uvicorn.access") |
| |
|
| | |
| | for handler in access_logger.handlers[:]: |
| | access_logger.removeHandler(handler) |
| |
|
| | |
| | handler = logging.StreamHandler(sys.stdout) |
| | access_formatter = AccessLogFormatter("%(asctime)s | %(levelname)-8s | %(message)s") |
| | handler.setFormatter(access_formatter) |
| |
|
| | |
| | access_logger.addHandler(handler) |
| | access_logger.setLevel(logging.INFO) |
| | access_logger.propagate = False |
| |
|
| | return access_logger |
| |
|