Spaces:
Paused
Paused
File size: 7,638 Bytes
a5784e9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | import logging
import logging.handlers
import os
import sys
from datetime import datetime
from typing import Any, Optional, Tuple
from zoneinfo import ZoneInfo
from config import (
ACTIVE_AUTH_DIR,
APP_LOG_FILE_PATH,
JSON_LOGS_ENABLED,
LOG_DIR,
LOG_FILE_BACKUP_COUNT,
LOG_FILE_MAX_BYTES,
SAVED_AUTH_DIR,
)
from models import StreamToLogger, WebSocketConnectionManager, WebSocketLogHandler
from .core.error_handler import setup_global_exception_handlers
from .grid_logger import (
GridFormatter,
JSONFormatter,
PlainGridFormatter,
)
class ColoredFormatter(logging.Formatter):
"""Cross-platform colored formatter using ANSI codes (legacy, kept for compatibility)."""
COLORS = {
"DEBUG": "\033[36m",
"INFO": "\033[0m",
"WARNING": "\033[93m",
"ERROR": "\033[91m",
"CRITICAL": "\033[41m\033[97m",
}
RESET = "\033[0m"
def __init__(
self, fmt: Any = None, datefmt: Any = None, use_color: bool = True
) -> None:
super().__init__(fmt, datefmt)
self.use_color = use_color
if use_color and sys.platform == "win32":
try:
import ctypes
kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined]
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
except Exception:
self.use_color = False
def formatTime(self, record: logging.LogRecord, datefmt: Any = None) -> str:
dt = datetime.fromtimestamp(record.created, tz=ZoneInfo("America/Chicago"))
if datefmt:
s = dt.strftime(datefmt)
else:
s = dt.strftime("%Y-%m-%d %H:%M:%S")
s = "%s,%03d" % (s, record.msecs)
return s
def format(self, record: logging.LogRecord) -> str:
if self.use_color and record.levelname in self.COLORS:
original_levelname = record.levelname
record.levelname = (
f"{self.COLORS[record.levelname]}{record.levelname}{self.RESET}"
)
result = super().format(record)
record.levelname = original_levelname
return result
return super().format(record)
def setup_server_logging(
logger_instance: logging.Logger,
log_ws_manager: Optional[WebSocketConnectionManager],
log_level_name: str = "INFO",
redirect_print_str: str = "false",
) -> Tuple[object, object]:
"""
Setup server logging system
Args:
logger_instance: Main logger instance
log_ws_manager: WebSocket connection manager
log_level_name: Log level name
redirect_print_str: Whether to redirect print output
Returns:
Tuple[object, object]: Original stdout and stderr streams
"""
log_level = getattr(logging, log_level_name.upper(), logging.INFO)
redirect_print = redirect_print_str.lower() in ("true", "1", "yes")
# Create necessary directories
os.makedirs(LOG_DIR, exist_ok=True)
os.makedirs(ACTIVE_AUTH_DIR, exist_ok=True)
os.makedirs(SAVED_AUTH_DIR, exist_ok=True)
# Clear existing handlers
if logger_instance.hasHandlers():
logger_instance.handlers.clear()
logger_instance.setLevel(log_level)
logger_instance.propagate = False
# Remove old log file
if os.path.exists(APP_LOG_FILE_PATH):
try:
os.remove(APP_LOG_FILE_PATH)
except OSError as e:
print(
f"Warning (setup_server_logging): Failed to remove old app.log file '{APP_LOG_FILE_PATH}': {e}. Will rely on mode='w' for truncation.",
file=sys.__stderr__,
)
# Use JSONFormatter for file logging if JSON_LOGS_ENABLED, otherwise PlainGridFormatter
if JSON_LOGS_ENABLED:
file_log_formatter = JSONFormatter()
else:
file_log_formatter = PlainGridFormatter()
file_handler = logging.handlers.RotatingFileHandler(
APP_LOG_FILE_PATH,
maxBytes=LOG_FILE_MAX_BYTES,
backupCount=LOG_FILE_BACKUP_COUNT,
encoding="utf-8",
mode="w",
)
file_handler.setFormatter(file_log_formatter)
file_handler.setLevel(log_level)
logger_instance.addHandler(file_handler)
# Add WebSocket handler
if log_ws_manager is None:
print(
"Critical Warning (setup_server_logging): log_ws_manager not initialized! WebSocket logging will be unavailable.",
file=sys.__stderr__,
)
else:
ws_handler = WebSocketLogHandler(log_ws_manager)
ws_handler.setLevel(log_level)
ws_handler.setFormatter(PlainGridFormatter())
logger_instance.addHandler(ws_handler)
# Add console handler (using GridFormatter with color)
console_grid_formatter = GridFormatter(show_tree=True, colorize=True)
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setFormatter(console_grid_formatter)
console_handler.setLevel(log_level)
logger_instance.addHandler(console_handler)
# Add AbortError filter (benign errors from Playwright navigation cancellations)
from logging_utils import AbortErrorFilter
logger_instance.addFilter(AbortErrorFilter())
# Save original streams
original_stdout = sys.stdout
original_stderr = sys.stderr
# Redirect print output (if needed)
if redirect_print:
print(
"--- Note: server.py is redirecting its print output to the logging system (File, WebSocket, and Console logger) ---",
file=original_stderr,
)
stdout_redirect_logger = logging.getLogger("AIStudioProxyServer.stdout")
stdout_redirect_logger.setLevel(logging.INFO)
stdout_redirect_logger.propagate = True
sys.stdout = StreamToLogger(stdout_redirect_logger, logging.INFO)
stderr_redirect_logger = logging.getLogger("AIStudioProxyServer.stderr")
stderr_redirect_logger.setLevel(logging.ERROR)
stderr_redirect_logger.propagate = True
sys.stderr = StreamToLogger(stderr_redirect_logger, logging.ERROR)
else:
print(
"--- server.py print output is NOT redirected to logging system (using original stdout/stderr) ---",
file=original_stderr,
)
# Configure third-party library log levels
logging.getLogger("uvicorn").setLevel(logging.WARNING)
logging.getLogger("uvicorn.error").setLevel(logging.INFO)
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
logging.getLogger("websockets").setLevel(logging.WARNING)
logging.getLogger("playwright").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.ERROR)
# Log initialization info
logger_instance.info(
"=" * 5
+ " AIStudioProxyServer Logging System Initialized in lifespan "
+ "=" * 5
)
logger_instance.info(f"Log level set to: {logging.getLevelName(log_level)}")
logger_instance.debug(f"Log file path: {APP_LOG_FILE_PATH}")
logger_instance.info("Console log handler added.")
logger_instance.info(
f"Print Redirection (controlled by SERVER_REDIRECT_PRINT env var): {'Enabled' if redirect_print else 'Disabled'}"
)
# Install global exception handlers
setup_global_exception_handlers()
return original_stdout, original_stderr
def restore_original_streams(original_stdout: object, original_stderr: object) -> None:
"""
Restore original stdout and stderr streams
Args:
original_stdout: Original stdout stream
original_stderr: Original stderr stream
"""
sys.stdout = original_stdout
sys.stderr = original_stderr
|