mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Merge 15cfe744d7 into b03e913ccf
This commit is contained in:
commit
581d58d478
2 changed files with 63 additions and 0 deletions
|
|
@ -37,6 +37,18 @@ class GenAIEventRecorder:
|
|||
self.event_logger.emit(
|
||||
Event(
|
||||
name=GenAIEvent.OPERATION_EXCEPTION,
|
||||
# The body MUST be set. ``Event.body`` defaults to ``None``, and
|
||||
# OTLP's ``AnyValue`` has no representation for ``None``: the
|
||||
# exporter's ``_encode_value`` raises ``Invalid type <class
|
||||
# 'NoneType'>``, and ``BatchLogRecordProcessor._export_batch``
|
||||
# swallows that and discards the WHOLE batch — so one such event
|
||||
# silently destroys every log record batched with it. The
|
||||
# encoder grew a ``None`` branch in opentelemetry-exporter-otlp-
|
||||
# proto-common 1.43.0, but litellm pins 1.28.0, so this event is
|
||||
# unexportable as shipped. The message is the natural body for a
|
||||
# WARN record and adds no data the attributes don't already
|
||||
# carry.
|
||||
body=message,
|
||||
timestamp=timestamp_ns,
|
||||
trace_id=span_context.trace_id,
|
||||
span_id=span_context.span_id,
|
||||
|
|
|
|||
|
|
@ -940,6 +940,57 @@ def test_operation_exception_log_event_always_carries_required_pair():
|
|||
assert ExceptionEvent.STACKTRACE not in attributes
|
||||
|
||||
|
||||
def test_operation_exception_log_event_is_otlp_encodable():
|
||||
"""The event must survive the real OTLP encoder, not just an in-memory exporter.
|
||||
|
||||
``Event.body`` defaults to ``None`` and OTLP's ``AnyValue`` cannot represent
|
||||
it, so an event emitted without a body raises ``Invalid type <class
|
||||
'NoneType'>`` inside ``encode_logs``. That happens on the exporter's batch
|
||||
thread, where ``BatchLogRecordProcessor._export_batch`` catches it, logs it
|
||||
and drops the WHOLE batch — every record batched alongside is lost silently.
|
||||
Every other test here uses ``InMemoryLogExporter``, which never encodes, so
|
||||
only an explicit encode step can catch this.
|
||||
"""
|
||||
encode_logs = pytest.importorskip(
|
||||
"opentelemetry.exporter.otlp.proto.common._internal._log_encoder"
|
||||
).encode_logs
|
||||
|
||||
engine, _, log_exporter = _engine_with_event_recorder()
|
||||
engine.emit(
|
||||
SpanRole.LLM_CALL,
|
||||
_llm_call_data(SpanError(error_type="RateLimitError", message="rate limited")),
|
||||
)
|
||||
logs = log_exporter.get_finished_logs()
|
||||
|
||||
# Raises "Invalid type <class 'NoneType'> of value None" when body is unset.
|
||||
request = encode_logs(logs)
|
||||
assert request.SerializeToString()
|
||||
|
||||
(resource_logs,) = request.resource_logs
|
||||
(scope_logs,) = resource_logs.scope_logs
|
||||
(log_record,) = scope_logs.log_records
|
||||
assert log_record.body.string_value == "rate limited"
|
||||
|
||||
|
||||
def test_operation_exception_log_event_body_is_never_none():
|
||||
"""A batch is dropped whole on an unencodable body, so this is asserted for
|
||||
both event shapes: with and without a stacktrace."""
|
||||
engine, _, log_exporter = _engine_with_event_recorder()
|
||||
engine.emit(
|
||||
SpanRole.LLM_CALL,
|
||||
_llm_call_data(SpanError(error_type="APIError", message="boom")),
|
||||
)
|
||||
engine.emit(
|
||||
SpanRole.LLM_CALL,
|
||||
_llm_call_data(
|
||||
SpanError(error_type="APIError", message="boom", stack_trace="Traceback ...")
|
||||
),
|
||||
)
|
||||
|
||||
bodies = [log.log_record.body for log in log_exporter.get_finished_logs()]
|
||||
assert bodies == ["boom", "boom"]
|
||||
|
||||
|
||||
def test_operation_exception_log_event_not_emitted_on_success():
|
||||
engine, span_exporter, log_exporter = _engine_with_event_recorder()
|
||||
engine.emit(SpanRole.LLM_CALL, _llm_call_data(None))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue