From 212cd0e4aadae0522392f9550a0ca4c968061eca Mon Sep 17 00:00:00 2001 From: Harshit28j Date: Wed, 11 Mar 2026 17:28:19 +0530 Subject: [PATCH] Fix pod lock collision, empty credential validation, and interval parsing - Override initialize_focus_export_job in VantageLogger to use VANTAGE_USAGE_DATA_JOB_NAME as the Redis pod lock key, preventing silent export skips when both Focus and Vantage loggers are configured - Add field_validator to VantageInitRequest rejecting empty-string api_key and integration_token at init time instead of at export time - Guard FocusLogger FOCUS_INTERVAL_SECONDS with try/except matching the pattern already used in VantageLogger Co-Authored-By: Claude Opus 4.6 --- litellm/integrations/focus/focus_logger.py | 10 +++++- .../integrations/vantage/vantage_logger.py | 31 +++++++++++++++++++ litellm/types/proxy/vantage_endpoints.py | 9 +++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/litellm/integrations/focus/focus_logger.py b/litellm/integrations/focus/focus_logger.py index a1b75fbfffe..f461f8c9fa7 100644 --- a/litellm/integrations/focus/focus_logger.py +++ b/litellm/integrations/focus/focus_logger.py @@ -53,7 +53,15 @@ class FocusLogger(CustomLogger): if interval_seconds is not None else os.getenv("FOCUS_INTERVAL_SECONDS") ) - self.interval_seconds = int(raw_interval) if raw_interval is not None else None + self.interval_seconds: Optional[int] = None + if raw_interval is not None: + try: + self.interval_seconds = int(raw_interval) + except (ValueError, TypeError): + verbose_logger.warning( + "Invalid FOCUS_INTERVAL_SECONDS value: %s, ignoring", + raw_interval, + ) env_prefix = os.getenv("FOCUS_PREFIX") self.prefix: str = ( prefix if prefix is not None else (env_prefix if env_prefix else "focus_exports") diff --git a/litellm/integrations/vantage/vantage_logger.py b/litellm/integrations/vantage/vantage_logger.py index 5dbcce8c33d..6689d932749 100644 --- a/litellm/integrations/vantage/vantage_logger.py +++ b/litellm/integrations/vantage/vantage_logger.py @@ -86,6 +86,37 @@ class VantageLogger(FocusLogger): resolved_token[:4] + "***" if resolved_token and len(resolved_token) > 4 else "***", ) + async def initialize_focus_export_job(self) -> None: + """Override to use the Vantage-specific pod lock key. + + Without this, VantageLogger and FocusLogger would compete for the + same ``FOCUS_USAGE_DATA_JOB_NAME`` lock, causing one to silently + skip its export cycle when both are configured simultaneously. + """ + from litellm.proxy.proxy_server import proxy_logging_obj + + pod_lock_manager = None + if proxy_logging_obj is not None: + writer = getattr(proxy_logging_obj, "db_spend_update_writer", None) + if writer is not None: + pod_lock_manager = getattr(writer, "pod_lock_manager", None) + + if pod_lock_manager and pod_lock_manager.redis_cache: + acquired = await pod_lock_manager.acquire_lock( + cronjob_id=VANTAGE_USAGE_DATA_JOB_NAME + ) + if not acquired: + verbose_logger.debug("Vantage export: unable to acquire pod lock") + return + try: + await self._run_scheduled_export() + finally: + await pod_lock_manager.release_lock( + cronjob_id=VANTAGE_USAGE_DATA_JOB_NAME + ) + else: + await self._run_scheduled_export() + @staticmethod async def init_vantage_background_job( scheduler: AsyncIOScheduler, diff --git a/litellm/types/proxy/vantage_endpoints.py b/litellm/types/proxy/vantage_endpoints.py index 12d9099f819..86b5a26dbca 100644 --- a/litellm/types/proxy/vantage_endpoints.py +++ b/litellm/types/proxy/vantage_endpoints.py @@ -5,7 +5,7 @@ Vantage endpoint types for LiteLLM Proxy from datetime import datetime from typing import Any, Dict, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator class VantageInitRequest(BaseModel): @@ -20,6 +20,13 @@ class VantageInitRequest(BaseModel): description="Vantage API base URL (default: https://api.vantage.sh)", ) + @field_validator("api_key", "integration_token") + @classmethod + def must_be_non_empty(cls, v: str) -> str: + if not v.strip(): + raise ValueError("must be a non-empty string") + return v + class VantageInitResponse(BaseModel): """Response model for Vantage initialization"""