This commit is contained in:
dingdangmao 2026-11-07 03:39:46 +00:00 • committed by GitHub
commit 8c2fa78110
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 64 additions and 0 deletions

View file

@ -1535,6 +1535,18 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger):
if not self._capture_in_span():
return
# Per-request message redaction must apply to the raw payload span too:
# set_raw_request_attributes dumps llm.<provider>.messages / choices
# verbatim, so without this guard the very content redaction is meant
# to keep out of traces still leaks through the raw span. This uses the
# same precedence as the main span (dynamic param > redaction headers >
# global turn_off_message_logging).
from litellm.litellm_core_utils.redact_messages import (
should_redact_message_logging, # pyright: ignore[reportUnknownVariableType] # callee is untyped upstream
)
if should_redact_message_logging(kwargs): # pyright: ignore[reportUnknownArgumentType] # untyped by design
return
litellm_params: Final = kwargs.get("litellm_params", {})
metadata: Final = litellm_params.get("metadata") or {}
generation_name: Final = metadata.get("generation_name")

View file

@ -7793,3 +7793,55 @@ class TestOpentelemetryUnitTests:
# Assert: error.message should be set from error_str using ErrorAttributes constant
mock_span.set_attribute.assert_called_with(ErrorAttributes.ERROR_MESSAGE, "Fallback error message")
class TestOpenTelemetryRawSpanRespectsRedaction(unittest.TestCase):
"""#44504: per-request message redaction must also skip the raw payload span.
``set_raw_request_attributes`` dumps ``llm.<provider>.messages`` / ``choices``
verbatim from the un-redacted ``complete_input_dict`` and ``original_response``,
so without this guard a request that asked for redaction (the
``x-litellm-enable-message-redaction`` header, a dynamic param, or the global
``turn_off_message_logging`` switch) still leaks its content through the
``raw_gen_ai_request`` span.
"""
@patch.dict(os.environ, {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": ""})
def test_skips_raw_span_when_redaction_header_set(self): # test-quality-ok: negative-path test; the observable behavior is precisely that no raw span is created, which is only assertable on the tracer mock
"""Redaction requested via the per-request header -> raw span is not created."""
from litellm.integrations.opentelemetry import OpenTelemetry
otel = OpenTelemetry()
otel.message_logging = True
mock_tracer = MagicMock()
mock_span = MagicMock()
mock_tracer.start_span.return_value = mock_span
otel.get_tracer_to_use_for_request = MagicMock(return_value=mock_tracer)
otel.set_raw_request_attributes = MagicMock()
otel._to_ns = MagicMock(return_value=1234567890)
kwargs = {"litellm_params": {"metadata": {"headers": {"x-litellm-enable-message-redaction": "true"}}}}
otel._maybe_log_raw_request(kwargs, {}, datetime.now(), datetime.now(), MagicMock())
mock_tracer.start_span.assert_not_called()
otel.set_raw_request_attributes.assert_not_called()
@patch.dict(os.environ, {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": ""})
def test_creates_raw_span_when_no_redaction_requested(self): # test-quality-ok: positive-path twin of the test above; asserts the span name and raw-attribute hook via the tracer mock, matching the existing file style
"""No redaction requested (empty headers dict) -> raw span is still created."""
from litellm.integrations.opentelemetry import RAW_REQUEST_SPAN_NAME, OpenTelemetry
otel = OpenTelemetry()
otel.message_logging = True
mock_tracer = MagicMock()
mock_span = MagicMock()
mock_tracer.start_span.return_value = mock_span
otel.get_tracer_to_use_for_request = MagicMock(return_value=mock_tracer)
otel.set_raw_request_attributes = MagicMock()
otel._to_ns = MagicMock(return_value=1234567890)
kwargs = {"litellm_params": {"metadata": {"headers": {}}}}
otel._maybe_log_raw_request(kwargs, {}, datetime.now(), datetime.now(), MagicMock())
mock_tracer.start_span.assert_called_once()
self.assertEqual(mock_tracer.start_span.call_args[1]["name"], RAW_REQUEST_SPAN_NAME)
otel.set_raw_request_attributes.assert_called_once()