diff --git a/app/log/logger.py b/app/log/logger.py index b6472a5..eda2a70 100644 --- a/app/log/logger.py +++ b/app/log/logger.py @@ -1,6 +1,7 @@ import logging import platform import sys +import re from typing import Dict, Optional # ANSI转义序列颜色代码 @@ -12,6 +13,17 @@ COLORS = { "CRITICAL": "\033[1;31m", # 红色加粗 } + +def _redact_key_for_logging(key: str) -> str: + """ + Redacts API key for secure logging by showing only first and last 6 characters. + (Internal function to avoid circular imports) + """ + if not key or len(key) <= 12: + return "***" + + return f"{key[:6]}...{key[-6:]}" + # Windows系统启用ANSI支持 if platform.system() == "Windows": import ctypes @@ -35,6 +47,44 @@ class ColoredFormatter(logging.Formatter): return super().format(record) +class AccessLogFormatter(logging.Formatter): + """ + Custom access log formatter that redacts API keys in URLs + """ + + # API key patterns to match in URLs + API_KEY_PATTERNS = [ + r'AIza[0-9A-Za-z_-]{35}', # Google API keys (like Gemini) + r'sk-[0-9A-Za-z]{48}', # OpenAI API keys + r'sk-[0-9A-Za-z_-]{20,}', # General sk- prefixed keys + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Compile regex patterns for better performance + self.compiled_patterns = [re.compile(pattern) for pattern in self.API_KEY_PATTERNS] + + def format(self, record): + # Format the record normally first + formatted_msg = super().format(record) + + # Redact API keys in the formatted message + 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 + """ + 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 + + # 日志格式 - 使用 fileloc 并设置固定宽度 (例如 30) FORMATTER = ColoredFormatter( "%(asctime)s | %(levelname)-17s | %(fileloc)-30s | %(message)s" @@ -235,3 +285,43 @@ def get_files_logger(): def get_vertex_express_logger(): return Logger.setup_logger("vertex_express") + +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 + """ + # Get the uvicorn access logger + access_logger = logging.getLogger("uvicorn.access") + + # Remove existing handlers to avoid duplicate logs + for handler in access_logger.handlers[:]: + access_logger.removeHandler(handler) + + # Create new handler with our custom formatter that includes timestamp and log level + handler = logging.StreamHandler(sys.stdout) + access_formatter = AccessLogFormatter("%(asctime)s | %(levelname)-8s | %(message)s") + handler.setFormatter(access_formatter) + + # Add the handler to uvicorn access logger + access_logger.addHandler(handler) + access_logger.setLevel(logging.INFO) + access_logger.propagate = False + + return access_logger + diff --git a/app/main.py b/app/main.py index bbf9b50..5d6abeb 100644 --- a/app/main.py +++ b/app/main.py @@ -5,7 +5,10 @@ from dotenv import load_dotenv load_dotenv() from app.core.application import create_app -from app.log.logger import get_main_logger +from app.log.logger import get_main_logger, setup_access_logging + +# Setup access logging with API key redaction when app is imported (for CLI usage) +setup_access_logging() app = create_app()