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 <noreply@anthropic.com>
This commit is contained in:
Harshit28j 2026-03-11 17:28:19 +05:30
parent 4a9d03f1b1
commit 212cd0e4aa
3 changed files with 48 additions and 2 deletions

View file

@ -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")

View file

@ -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,

View file

@ -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"""