mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(logging): redact raw_request when turn_off_message_logging is set in the proxy config (#43219)
* fix(logging): redact raw_request when turn_off_message_logging is set in the proxy config The raw request branch bound turn_off_message_logging by name at import, before the proxy config set it, so loggers kept receiving the prompt in metadata.raw_request and raw_request_typed_dict. It now runs the same per-request redaction check messages use, and json_logs is read at call time for the same reason * fix(logging): keep raw_request_typed_dict for the explicit readers and tolerate missing headers in the json debug log * refactor(logging): drop the stale comment above the raw request typed dict --------- Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
parent
e065a2575b
commit
d86c2e1f42
3 changed files with 106 additions and 39 deletions
|
|
@ -20,11 +20,7 @@ from httpx import Response
|
|||
from pydantic import BaseModel, JsonValue
|
||||
|
||||
import litellm
|
||||
from litellm import (
|
||||
_custom_logger_compatible_callbacks_literal,
|
||||
json_logs,
|
||||
turn_off_message_logging,
|
||||
)
|
||||
from litellm import _custom_logger_compatible_callbacks_literal
|
||||
from litellm._logging import (
|
||||
_is_debugging_on,
|
||||
_redact_string,
|
||||
|
|
@ -43,6 +39,7 @@ from litellm.constants import (
|
|||
DEFAULT_MOCK_RESPONSE_PROMPT_TOKEN_COUNT,
|
||||
EMPTY_MAPPING,
|
||||
PROVIDER_REQUEST_ID_HEADERS,
|
||||
REDACTED_BY_LITELLM,
|
||||
)
|
||||
from litellm.cost_calculator import (
|
||||
RealtimeAPITokenUsageProcessor,
|
||||
|
|
@ -1358,10 +1355,19 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
_litellm_params: Final = self.model_call_details.get("litellm_params", {})
|
||||
_metadata: Final = _litellm_params.get("metadata", {}) or {}
|
||||
try:
|
||||
# [Non-blocking Extra Debug Information in metadata]
|
||||
if turn_off_message_logging is True:
|
||||
_metadata["raw_request"] = "redacted by litellm. \
|
||||
'litellm.turn_off_message_logging=True'"
|
||||
self.model_call_details["raw_request_typed_dict"] = RawRequestTypedDict(
|
||||
raw_request_api_base=self._get_masked_api_base(str(additional_args.get("api_base") or "")),
|
||||
raw_request_body=self._get_raw_request_body(additional_args.get("complete_input_dict", {})),
|
||||
# NOTE: setting ignore_sensitive_headers to True will cause
|
||||
# the Authorization header to be leaked when calls to the health
|
||||
# endpoint are made and fail.
|
||||
raw_request_headers=self._get_masked_headers(
|
||||
additional_args.get("headers", {}) or {},
|
||||
),
|
||||
error=None,
|
||||
)
|
||||
if should_redact_message_logging(self.model_call_details):
|
||||
_metadata["raw_request"] = REDACTED_BY_LITELLM
|
||||
else:
|
||||
curl_command: Final = self._get_request_curl_command(
|
||||
api_base=additional_args.get("api_base", ""),
|
||||
|
|
@ -1369,20 +1375,7 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
additional_args=additional_args,
|
||||
data=additional_args.get("complete_input_dict", {}),
|
||||
)
|
||||
|
||||
_metadata["raw_request"] = _redact_string(str(curl_command))
|
||||
# split up, so it's easier to parse in the UI
|
||||
self.model_call_details["raw_request_typed_dict"] = RawRequestTypedDict(
|
||||
raw_request_api_base=self._get_masked_api_base(str(additional_args.get("api_base") or "")),
|
||||
raw_request_body=self._get_raw_request_body(additional_args.get("complete_input_dict", {})),
|
||||
# NOTE: setting ignore_sensitive_headers to True will cause
|
||||
# the Authorization header to be leaked when calls to the health
|
||||
# endpoint are made and fail.
|
||||
raw_request_headers=self._get_masked_headers(
|
||||
additional_args.get("headers", {}) or {},
|
||||
),
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
self.model_call_details["raw_request_typed_dict"] = RawRequestTypedDict(
|
||||
error=str(e),
|
||||
|
|
@ -1476,7 +1469,7 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
def _print_llm_call_debugging_log(
|
||||
self,
|
||||
api_base: str,
|
||||
headers: dict,
|
||||
headers: dict | None,
|
||||
additional_args: dict,
|
||||
):
|
||||
"""
|
||||
|
|
@ -1485,8 +1478,8 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
Prints the RAW curl command sent from LiteLLM
|
||||
"""
|
||||
if _is_debugging_on() or self.litellm_request_debug:
|
||||
if json_logs:
|
||||
masked_headers: Final = self._get_masked_headers(headers)
|
||||
if litellm.json_logs:
|
||||
masked_headers: Final = self._get_masked_headers(headers or {})
|
||||
masked_api_base: Final = self._get_masked_api_base(str(api_base or ""))
|
||||
if self.litellm_request_debug:
|
||||
verbose_logger.warning( # .warning ensures this shows up in all environments
|
||||
|
|
@ -1563,20 +1556,12 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
else:
|
||||
attr = "debug"
|
||||
|
||||
if json_logs:
|
||||
callattr = verbose_logger.warning if attr == "warning" else verbose_logger.debug
|
||||
callattr(
|
||||
"RAW RESPONSE:\n{}\n\n".format(
|
||||
self.model_call_details.get("original_response", self.model_call_details)
|
||||
),
|
||||
)
|
||||
else:
|
||||
callattr = verbose_logger.warning if attr == "warning" else verbose_logger.debug
|
||||
callattr(
|
||||
"RAW RESPONSE:\n{}\n\n".format(
|
||||
self.model_call_details.get("original_response", self.model_call_details)
|
||||
)
|
||||
callattr: Final = verbose_logger.warning if attr == "warning" else verbose_logger.debug
|
||||
callattr(
|
||||
"RAW RESPONSE:\n{}\n\n".format(
|
||||
self.model_call_details.get("original_response", self.model_call_details)
|
||||
)
|
||||
)
|
||||
if getattr(self, "logger_fn", None) and callable(self.logger_fn):
|
||||
try:
|
||||
self.logger_fn(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from openai._legacy_response import HttpxBinaryResponseContent
|
|||
|
||||
import litellm
|
||||
from litellm._logging import session_id_var, trace_id_var
|
||||
from litellm.constants import SENTRY_PII_DENYLIST
|
||||
from litellm.constants import REDACTED_BY_LITELLM, SENTRY_PII_DENYLIST
|
||||
from litellm.cost_calculator import ocr_batch_cost
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LitellmLogging
|
||||
|
|
@ -6615,6 +6615,65 @@ def test_pre_call_redacts_and_masks_raw_request(logging_obj):
|
|||
assert "key=*****" in raw_api_base
|
||||
|
||||
|
||||
_PRIVATE_RAW_REQUEST_ARGS: Final = {
|
||||
"api_base": "https://api.openai.com/v1/chat/completions",
|
||||
"headers": {},
|
||||
"complete_input_dict": {"messages": [{"role": "user", "content": "PRIVATE-PHRASE"}]},
|
||||
}
|
||||
|
||||
|
||||
def _pre_call_with_raw_request_logging(logging_obj) -> dict:
|
||||
metadata: Final = {"user_api_key_alias": "qa-key"}
|
||||
logging_obj.model_call_details["litellm_params"] = {"metadata": metadata}
|
||||
logging_obj.log_raw_request_response = True
|
||||
logging_obj.pre_call(input="hi", api_key="", additional_args=_PRIVATE_RAW_REQUEST_ARGS)
|
||||
return metadata
|
||||
|
||||
|
||||
def _assert_raw_request_redacted_for_callbacks_only(logging_obj, metadata: dict) -> None:
|
||||
assert metadata["raw_request"] == REDACTED_BY_LITELLM
|
||||
typed_dict: Final = logging_obj.model_call_details["raw_request_typed_dict"]
|
||||
assert typed_dict["raw_request_body"] == _PRIVATE_RAW_REQUEST_ARGS["complete_input_dict"]
|
||||
assert typed_dict["error"] is None
|
||||
|
||||
|
||||
def test_pre_call_raw_request_honors_turn_off_message_logging_set_after_import(logging_obj, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "turn_off_message_logging", True)
|
||||
|
||||
metadata = _pre_call_with_raw_request_logging(logging_obj)
|
||||
|
||||
_assert_raw_request_redacted_for_callbacks_only(logging_obj, metadata)
|
||||
|
||||
|
||||
def test_pre_call_raw_request_honors_per_request_turn_off_message_logging(logging_obj, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "turn_off_message_logging", False)
|
||||
logging_obj.model_call_details["standard_callback_dynamic_params"] = {"turn_off_message_logging": True}
|
||||
|
||||
metadata = _pre_call_with_raw_request_logging(logging_obj)
|
||||
|
||||
_assert_raw_request_redacted_for_callbacks_only(logging_obj, metadata)
|
||||
|
||||
|
||||
def test_debugging_log_honors_json_logs_set_after_import(logging_obj, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "json_logs", True)
|
||||
logging_obj.litellm_request_debug = True
|
||||
|
||||
with patch("litellm.litellm_core_utils.litellm_logging.verbose_logger.warning") as warning:
|
||||
logging_obj._print_llm_call_debugging_log(api_base="https://api.openai.com/v1", headers={}, additional_args={})
|
||||
|
||||
assert "https://api.openai.com/v1" in warning.call_args.kwargs["extra"]["api_base"]
|
||||
|
||||
|
||||
def test_debugging_log_with_json_logs_tolerates_missing_headers(logging_obj, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "json_logs", True)
|
||||
logging_obj.litellm_request_debug = True
|
||||
|
||||
with patch("litellm.litellm_core_utils.litellm_logging.verbose_logger.warning") as warning:
|
||||
logging_obj._print_llm_call_debugging_log(api_base="https://api.openai.com/v1", headers=None, additional_args={})
|
||||
|
||||
assert "https://api.openai.com/v1" in warning.call_args.kwargs["extra"]["api_base"]
|
||||
|
||||
|
||||
def _streaming_logging_obj_with_callbacks(callbacks: list[CustomLogger]):
|
||||
import datetime
|
||||
|
||||
|
|
|
|||
|
|
@ -626,6 +626,29 @@ def test_return_raw_request_does_not_call_provider(respx_mock: respx.MockRouter)
|
|||
]
|
||||
|
||||
|
||||
def test_return_raw_request_ignores_turn_off_message_logging(
|
||||
respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
from litellm.types.utils import CallTypes
|
||||
from litellm.utils import return_raw_request
|
||||
|
||||
model: Final = "gpt-4o"
|
||||
messages: Final = [{"role": "user", "content": "PRIVATE-PHRASE"}]
|
||||
route: Final = respx_mock.post("https://api.openai.com/v1/chat/completions").mock(
|
||||
return_value=_mocked_openai_chat_response(model)
|
||||
)
|
||||
monkeypatch.setattr(litellm, "turn_off_message_logging", True)
|
||||
|
||||
request: Final = return_raw_request(
|
||||
endpoint=CallTypes.completion,
|
||||
kwargs={"model": model, "messages": messages},
|
||||
)
|
||||
|
||||
assert route.call_count == 0
|
||||
assert request.get("error") is None
|
||||
assert request["raw_request_body"]["messages"] == messages
|
||||
|
||||
|
||||
def test_completion_forwards_verbosity_in_raw_request(respx_mock: respx.MockRouter):
|
||||
"""Regression test: completion() must forward the verbosity param to the provider request body."""
|
||||
from litellm.types.utils import CallTypes
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue