mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-02 20:22:10 +00:00
* fixes for generic api logger * tests for generic api logger * test_generic_api_callback_multiple_logs * allow health checking generic api endpoints * docs generic api endpoint for logging * allow setting headers for generic api callback * fix for test_init_custom_logger_compatible_class_as_callback * fix linting
265 lines
8.9 KiB
Python
265 lines
8.9 KiB
Python
"""
|
|
Callback to log events to a Generic API Endpoint
|
|
|
|
- Creates a StandardLoggingPayload
|
|
- Adds to batch queue
|
|
- Flushes based on CustomBatchLogger settings
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import traceback
|
|
import uuid
|
|
from typing import Dict, List, Optional, Union
|
|
|
|
import litellm
|
|
from litellm._logging import verbose_logger
|
|
from litellm.integrations.custom_batch_logger import CustomBatchLogger
|
|
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
|
from litellm.llms.custom_httpx.http_handler import (
|
|
get_async_httpx_client,
|
|
httpxSpecialProvider,
|
|
)
|
|
from litellm.types.utils import StandardLoggingPayload
|
|
|
|
|
|
class GenericAPILogger(CustomBatchLogger):
|
|
def __init__(
|
|
self,
|
|
endpoint: Optional[str] = None,
|
|
headers: Optional[dict] = None,
|
|
**kwargs,
|
|
):
|
|
"""
|
|
Initialize the GenericAPILogger
|
|
|
|
Args:
|
|
endpoint: Optional[str] = None,
|
|
headers: Optional[dict] = None,
|
|
"""
|
|
#########################################################
|
|
# Init httpx client
|
|
#########################################################
|
|
self.async_httpx_client = get_async_httpx_client(
|
|
llm_provider=httpxSpecialProvider.LoggingCallback
|
|
)
|
|
endpoint = endpoint or os.getenv("GENERIC_LOGGER_ENDPOINT")
|
|
if endpoint is None:
|
|
raise ValueError(
|
|
"endpoint not set for GenericAPILogger, GENERIC_LOGGER_ENDPOINT not found in environment variables"
|
|
)
|
|
|
|
self.headers: Dict = self._get_headers(headers)
|
|
self.endpoint: str = endpoint
|
|
verbose_logger.debug(
|
|
f"in init GenericAPILogger, endpoint {self.endpoint}, headers {self.headers}"
|
|
)
|
|
|
|
#########################################################
|
|
# Init variables for batch flushing logs
|
|
#########################################################
|
|
self.flush_lock = asyncio.Lock()
|
|
super().__init__(**kwargs, flush_lock=self.flush_lock)
|
|
asyncio.create_task(self.periodic_flush())
|
|
self.log_queue: List[Union[Dict, StandardLoggingPayload]] = []
|
|
|
|
def _get_headers(self, headers: Optional[dict] = None):
|
|
"""
|
|
Get headers for the Generic API Logger
|
|
|
|
Returns:
|
|
Dict: Headers for the Generic API Logger
|
|
|
|
Args:
|
|
headers: Optional[dict] = None
|
|
"""
|
|
# Process headers from different sources
|
|
headers_dict = {}
|
|
|
|
# 1. First check for headers from env var
|
|
env_headers = os.getenv("GENERIC_LOGGER_HEADERS")
|
|
if env_headers:
|
|
try:
|
|
# Parse headers in format "key1=value1,key2=value2" or "key1=value1"
|
|
header_items = env_headers.split(",")
|
|
for item in header_items:
|
|
if "=" in item:
|
|
key, value = item.split("=", 1)
|
|
headers_dict[key.strip()] = value.strip()
|
|
except Exception as e:
|
|
verbose_logger.warning(
|
|
f"Error parsing headers from environment variables: {str(e)}"
|
|
)
|
|
|
|
# 2. Update with litellm generic headers if available
|
|
if litellm.generic_logger_headers:
|
|
headers_dict.update(litellm.generic_logger_headers)
|
|
|
|
# 3. Override with directly provided headers if any
|
|
if headers:
|
|
headers_dict.update(headers)
|
|
|
|
return headers_dict
|
|
|
|
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
|
|
"""
|
|
Async Log success events to Generic API Endpoint
|
|
|
|
- Creates a StandardLoggingPayload
|
|
- Adds to batch queue
|
|
- Flushes based on CustomBatchLogger settings
|
|
|
|
Raises:
|
|
Raises a NON Blocking verbose_logger.exception if an error occurs
|
|
"""
|
|
from litellm.proxy.utils import _premium_user_check
|
|
|
|
_premium_user_check()
|
|
|
|
try:
|
|
verbose_logger.debug(
|
|
"Generic API Logger - Enters logging function for model %s", kwargs
|
|
)
|
|
standard_logging_payload = kwargs.get("standard_logging_object", None)
|
|
|
|
# Backwards compatibility with old logging payload
|
|
if litellm.generic_api_use_v1 is True:
|
|
payload = self._get_v1_logging_payload(
|
|
kwargs=kwargs,
|
|
response_obj=response_obj,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
self.log_queue.append(payload)
|
|
else:
|
|
# New logging payload, StandardLoggingPayload
|
|
self.log_queue.append(standard_logging_payload)
|
|
|
|
if len(self.log_queue) >= self.batch_size:
|
|
await self.async_send_batch()
|
|
|
|
except Exception as e:
|
|
verbose_logger.exception(
|
|
f"Generic API Logger Error - {str(e)}\n{traceback.format_exc()}"
|
|
)
|
|
pass
|
|
|
|
async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time):
|
|
"""
|
|
Async Log failure events to Generic API Endpoint
|
|
|
|
- Creates a StandardLoggingPayload
|
|
- Adds to batch queue
|
|
"""
|
|
from litellm.proxy.utils import _premium_user_check
|
|
|
|
_premium_user_check()
|
|
|
|
try:
|
|
verbose_logger.debug(
|
|
"Generic API Logger - Enters logging function for model %s", kwargs
|
|
)
|
|
standard_logging_payload = kwargs.get("standard_logging_object", None)
|
|
|
|
if litellm.generic_api_use_v1 is True:
|
|
payload = self._get_v1_logging_payload(
|
|
kwargs=kwargs,
|
|
response_obj=response_obj,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
self.log_queue.append(payload)
|
|
else:
|
|
self.log_queue.append(standard_logging_payload)
|
|
|
|
if len(self.log_queue) >= self.batch_size:
|
|
await self.async_send_batch()
|
|
|
|
except Exception as e:
|
|
verbose_logger.exception(
|
|
f"Generic API Logger Error - {str(e)}\n{traceback.format_exc()}"
|
|
)
|
|
|
|
async def async_send_batch(self):
|
|
"""
|
|
Sends the batch of messages to Generic API Endpoint
|
|
"""
|
|
try:
|
|
if not self.log_queue:
|
|
return
|
|
|
|
verbose_logger.debug(
|
|
f"Generic API Logger - about to flush {len(self.log_queue)} events"
|
|
)
|
|
|
|
# make POST request to Generic API Endpoint
|
|
response = await self.async_httpx_client.post(
|
|
url=self.endpoint,
|
|
headers=self.headers,
|
|
data=safe_dumps(self.log_queue),
|
|
)
|
|
|
|
verbose_logger.debug(
|
|
f"Generic API Logger - sent batch to {self.endpoint}, status code {response.status_code}"
|
|
)
|
|
|
|
except Exception as e:
|
|
verbose_logger.exception(
|
|
f"Generic API Logger Error sending batch - {str(e)}\n{traceback.format_exc()}"
|
|
)
|
|
finally:
|
|
self.log_queue.clear()
|
|
|
|
def _get_v1_logging_payload(
|
|
self, kwargs, response_obj, start_time, end_time
|
|
) -> dict:
|
|
"""
|
|
Maintained for backwards compatibility with old logging payload
|
|
|
|
Returns a dict of the payload to send to the Generic API Endpoint
|
|
"""
|
|
verbose_logger.debug(
|
|
f"GenericAPILogger Logging - Enters logging function for model {kwargs}"
|
|
)
|
|
|
|
# construct payload to send custom logger
|
|
# follows the same params as langfuse.py
|
|
litellm_params = kwargs.get("litellm_params", {})
|
|
metadata = (
|
|
litellm_params.get("metadata", {}) or {}
|
|
) # if litellm_params['metadata'] == None
|
|
messages = kwargs.get("messages")
|
|
cost = kwargs.get("response_cost", 0.0)
|
|
optional_params = kwargs.get("optional_params", {})
|
|
call_type = kwargs.get("call_type", "litellm.completion")
|
|
cache_hit = kwargs.get("cache_hit", False)
|
|
usage = response_obj["usage"]
|
|
id = response_obj.get("id", str(uuid.uuid4()))
|
|
|
|
# Build the initial payload
|
|
payload = {
|
|
"id": id,
|
|
"call_type": call_type,
|
|
"cache_hit": cache_hit,
|
|
"startTime": start_time,
|
|
"endTime": end_time,
|
|
"model": kwargs.get("model", ""),
|
|
"user": kwargs.get("user", ""),
|
|
"modelParameters": optional_params,
|
|
"messages": messages,
|
|
"response": response_obj,
|
|
"usage": usage,
|
|
"metadata": metadata,
|
|
"cost": cost,
|
|
}
|
|
|
|
# Ensure everything in the payload is converted to str
|
|
for key, value in payload.items():
|
|
try:
|
|
payload[key] = str(value)
|
|
except Exception:
|
|
# non blocking if it can't cast to a str
|
|
pass
|
|
|
|
return payload
|