import logging import sys from contextvars import ContextVar from logging.handlers import RotatingFileHandler # Храним trace_id текущего потока/корутины. TRACE_ID: ContextVar[str] = ContextVar("trace_id", default="-") class TraceIdFilter(logging.Filter): # Добавляет trace_id в каждую запись лога для сквозной трассировки. def filter(self, record: logging.LogRecord) -> bool: record.trace_id = TRACE_ID.get() return True def set_trace_id(trace_id: str) -> None: TRACE_ID.set(trace_id) def setup_logging(level: str = "INFO", log_file: str | None = None) -> None: # stderr — Docker и Celery prefork корректно его подхватывают. handlers: list[logging.Handler] = [logging.StreamHandler(sys.stderr)] if log_file: # Ротация файлового лога: 50MB × 5 файлов, чтобы диск VPS не переполнился # при многомесячной работе. handlers.append( RotatingFileHandler( log_file, maxBytes=50 * 1024 * 1024, backupCount=5, encoding="utf-8", ) ) trace_filter = TraceIdFilter() for handler in handlers: handler.addFilter(trace_filter) handler.setLevel(getattr(logging, level.upper(), logging.INFO)) root = logging.getLogger() root.setLevel(getattr(logging, level.upper(), logging.INFO)) # Убираем старые хендлеры, чтобы не дублировать после fork. root.handlers.clear() for handler in handlers: root.addHandler(handler) fmt = logging.Formatter( "%(asctime)s | %(levelname)s | %(name)s | trace=%(trace_id)s | %(message)s" ) for handler in root.handlers: handler.setFormatter(fmt)