diff --git a/enterprise/litellm_enterprise/litellm_core_utils/litellm_logging.py b/enterprise/litellm_enterprise/litellm_core_utils/litellm_logging.py new file mode 100644 index 0000000000..44ba0063ff --- /dev/null +++ b/enterprise/litellm_enterprise/litellm_core_utils/litellm_logging.py @@ -0,0 +1,28 @@ +""" +Enterprise specific logging utils +""" +from litellm.litellm_core_utils.litellm_logging import StandardLoggingMetadata + + +class StandardLoggingPayloadSetup: + @staticmethod + def apply_enterprise_specific_metadata( + standard_logging_metadata: StandardLoggingMetadata, + proxy_server_request: dict, + ) -> StandardLoggingMetadata: + """ + Adds enterprise-only metadata to the standard logging metadata. + """ + + _request_headers = proxy_server_request.get("headers", {}) + + if _request_headers: + custom_headers = { + k: v + for k, v in _request_headers.items() + if k.startswith("x-") and v is not None and isinstance(v, str) + } + + standard_logging_metadata["requester_custom_headers"] = custom_headers + + return standard_logging_metadata diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 242f0873c8..bfcbb3e14d 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -13,7 +13,18 @@ import traceback import uuid from datetime import datetime as dt_object from functools import lru_cache -from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union, cast +from typing import ( + Any, + Callable, + Dict, + List, + Literal, + Optional, + Tuple, + Type, + Union, + cast, +) from pydantic import BaseModel @@ -143,6 +154,13 @@ try: from litellm_enterprise.enterprise_callbacks.send_emails.smtp_email import ( SMTPEmailLogger, ) + from litellm_enterprise.litellm_core_utils.litellm_logging import ( + StandardLoggingPayloadSetup as EnterpriseStandardLoggingPayloadSetup, + ) + + EnterpriseStandardLoggingPayloadSetupVAR: Optional[ + Type[EnterpriseStandardLoggingPayloadSetup] + ] = EnterpriseStandardLoggingPayloadSetup except Exception as e: verbose_logger.debug( f"[Non-Blocking] Unable to import GenericAPILogger - LiteLLM Enterprise Feature - {str(e)}" @@ -150,6 +168,7 @@ except Exception as e: GenericAPILogger = CustomLogger # type: ignore ResendEmailLogger = CustomLogger # type: ignore SMTPEmailLogger = CustomLogger # type: ignore + EnterpriseStandardLoggingPayloadSetupVAR = None _in_memory_loggers: List[Any] = [] ### GLOBAL VARIABLES ### @@ -3328,6 +3347,7 @@ class StandardLoggingPayloadSetup: List[StandardLoggingVectorStoreRequest] ] = None, usage_object: Optional[dict] = None, + proxy_server_request: Optional[dict] = None, ) -> StandardLoggingMetadata: """ Clean and filter the metadata dictionary to include only the specified keys in StandardLoggingMetadata. @@ -3377,6 +3397,7 @@ class StandardLoggingPayloadSetup: mcp_tool_call_metadata=mcp_tool_call_metadata, vector_store_request_metadata=vector_store_request_metadata, usage_object=usage_object, + requester_custom_headers=None, ) if isinstance(metadata, dict): # Filter the metadata dictionary to include only the specified keys @@ -3399,6 +3420,16 @@ class StandardLoggingPayloadSetup: and isinstance(_potential_requester_metadata, dict) ): clean_metadata["requester_metadata"] = _potential_requester_metadata + + if ( + EnterpriseStandardLoggingPayloadSetupVAR + and proxy_server_request is not None + ): + clean_metadata = EnterpriseStandardLoggingPayloadSetupVAR.apply_enterprise_specific_metadata( + standard_logging_metadata=clean_metadata, + proxy_server_request=proxy_server_request, + ) + return clean_metadata @staticmethod @@ -3730,6 +3761,7 @@ def get_standard_logging_object_payload( clean_hidden_params = StandardLoggingPayloadSetup.get_hidden_params( hidden_params ) + # clean up litellm metadata clean_metadata = StandardLoggingPayloadSetup.get_standard_logging_metadata( metadata=metadata, @@ -3741,6 +3773,7 @@ def get_standard_logging_object_payload( "vector_store_request_metadata", None ), usage_object=usage.model_dump(), + proxy_server_request=proxy_server_request, ) _request_body = proxy_server_request.get("body", {}) @@ -3886,6 +3919,7 @@ def get_standard_logging_metadata( mcp_tool_call_metadata=None, vector_store_request_metadata=None, usage_object=None, + requester_custom_headers=None, ) if isinstance(metadata, dict): # Filter the metadata dictionary to include only the specified keys diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index b8ea7aa9a8..b7d1f4188f 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -72,3 +72,6 @@ model_list: model_info: id: my-unique-azure-deployment mode: batch + +litellm_settings: + success_callback: ["generic_api"] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 63c4835ae8..47c1547909 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -1785,6 +1785,9 @@ class StandardLoggingMetadata(StandardLoggingUserAPIKeyMetadata): ] # special param to log k,v pairs to spendlogs for a call requester_ip_address: Optional[str] requester_metadata: Optional[dict] + requester_custom_headers: Optional[ + Dict[str, str] + ] # Log any custom (`x-`) headers sent by the client to the proxy. prompt_management_metadata: Optional[StandardLoggingPromptManagementMetadata] mcp_tool_call_metadata: Optional[StandardLoggingMCPToolCall] vector_store_request_metadata: Optional[List[StandardLoggingVectorStoreRequest]] diff --git a/tests/logging_callback_tests/test_otel_logging.py b/tests/logging_callback_tests/test_otel_logging.py index 29f9d30517..7018471a53 100644 --- a/tests/logging_callback_tests/test_otel_logging.py +++ b/tests/logging_callback_tests/test_otel_logging.py @@ -277,6 +277,7 @@ def validate_redacted_message_span_attributes(span): "metadata.applied_guardrails", "metadata.mcp_tool_call_metadata", "metadata.vector_store_request_metadata", + "metadata.requester_custom_headers", ] _all_attributes = set(