# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Structured logging handlers and middleware. LoggingMiddleware (request/response logging with timing), filter_sensitive_data (structlog processor for sanitization), and get_logger (factory for structured loggers). """ import re import time from typing import Callable import structlog from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware from utils.native_path_leases import redact_native_paths logger = structlog.get_logger(__name__) _NATIVE_PATH_LEASE_RE = re.compile( r"(?i)(\b(?:native_path_lease|nativePathLease)[\"']?\s*[:=]\s*[\"']?)[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+" ) class LoggingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next: Callable) -> Response: start_time = time.time() try: response = await call_next(request) process_time = (time.time() - start_time) * 1000 EXCLUDED_PATHS = { "/api/train/status", "/api/train/metrics", "/api/train/hardware", "/api/system", } is_excluded = ( request.url.path in EXCLUDED_PATHS or request.url.path.startswith("/assets/") or request.url.path.endswith( (".png", ".jpg", ".jpeg", ".ico", ".woff", ".woff2", ".ttf") ) ) if not is_excluded: logger.info( "request_completed", method = request.method, path = request.url.path, status_code = response.status_code, process_time_ms = round(process_time, 2), ) return response except Exception as e: logger.error( "request_failed", path = request.url.path, method = request.method, error = str(e), exc_info = True, ) raise def filter_sensitive_data(logger, method_name, event_dict): """Structlog processor to redact native path leases from logs.""" def filter_value(value): if isinstance(value, str): try: value = redact_native_paths(value) except Exception: pass value = _NATIVE_PATH_LEASE_RE.sub(r"\1", value) return value elif isinstance(value, dict): return { k: "" if str(k).replace("_", "").lower() == "nativepathlease" else filter_value(v) for k, v in value.items() } elif isinstance(value, list): return [filter_value(item) for item in value] return value return { k: "" if str(k).replace("_", "").lower() == "nativepathlease" else filter_value(v) for k, v in event_dict.items() } def get_logger(name: str) -> structlog.BoundLogger: """Get a bound structured logger for a module (name is usually __name__).""" return structlog.get_logger(name)