Skip to content

fix(exporter/otlp/http): log error details from response on export failure#5155

Open
grvmishra788 wants to merge 5 commits intoopen-telemetry:mainfrom
grvmishra788:worktree-issue-4526
Open

fix(exporter/otlp/http): log error details from response on export failure#5155
grvmishra788 wants to merge 5 commits intoopen-telemetry:mainfrom
grvmishra788:worktree-issue-4526

Conversation

@grvmishra788
Copy link
Copy Markdown

@grvmishra788 grvmishra788 commented Apr 27, 2026

Description

Fixes #4526.

Before: When an OTLP HTTP export fails (non-2xx response), all three exporters (trace, metrics, logs) log only resp.reason — the HTTP reason phrase (e.g. "Bad Request"). The response body is never read, so server-provided error details (e.g. a protobuf-encoded error from telemetry.googleapis.com) are silently discarded.

After: On a non-2xx response, exporters parse the body using the Content-Type header before logging:

Content-Type Behaviour
application/x-protobuf Deserialise as Export*ServiceResponse; extract partial_success.error_message
application/json Parse JSON; extract partialSuccess.errorMessage or message (google.rpc.Status)
Unknown or missing Return resp.text
Empty body or parse failure Return resp.reason (previous behaviour, unchanged)

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

The fix was verified end-to-end by constructing a 400 response with a protobuf-encoded ExportTraceServiceResponse body (containing partial_success.error_message = "Request contains too many spans (limit: 10000)") and a mocked OTLP endpoint (via the responses library). Before the fix, the logged reason was "Bad Request"; after, it is the server-provided message. The same was confirmed for a JSON google.rpc.Status body and for an empty body (fallback to resp.reason, no regression).

Run from the repo root:

pytest exporter/opentelemetry-exporter-otlp-proto-http/tests/test_response_body_parsing.py \
       exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py \
       exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py
  • 11 unit tests in test_response_body_parsing.py — cover every branch of _parse_response_body: protobuf with/without error message, JSON partialSuccess and message fields, ; charset=utf-8 content-type variants, unknown content-type, empty body, malformed protobuf, malformed JSON.
  • 3 integration tests in test_proto_span_exporter.py and test_proto_log_exporter.py — verify the parsed message appears in the error log on a real export call.
  • All 41 tests pass; pylint 10.00/10.

Does This PR Require a Contrib Repo Change?

  • No.

Checklist:

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

Comment thread exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py Outdated
@grvmishra788 grvmishra788 marked this pull request as ready for review April 29, 2026 19:53
@grvmishra788 grvmishra788 requested a review from a team as a code owner April 29, 2026 19:53
status_code = None
else:
reason = resp.reason
reason = _parse_response_body(resp, ExportLogsServiceResponse)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this is not a successful resopnse should not resp being parsed as google.rpc.Status rather than ExportMetricsServiceResponse.

According to https://opentelemetry.io/docs/specs/otlp/#failures-1
"The response body for all HTTP 4xx and HTTP 5xx responses MUST be a Protobuf-encoded Status message that describes the problem."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that! Updated _parse_response_body to parse protobuf as google.rpc.Status and extract its message field. Removed the response_class parameter since it's no longer needed.

if not resp.content:
return resp.reason

content_type = resp.headers.get("Content-Type", "")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
content_type = resp.headers.get("Content-Type", "")
content_type = (
resp.headers.get("Content-Type", "")
.split(";", 1)[0]
.strip()
.lower()
)

return resp.text or resp.reason
if isinstance(body, dict):
# OTLP partial_success uses camelCase in JSON
if error_message := body.get("partialSuccess", {}).get(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will crash on

     {"partialSuccess": null}

or

     {"partialSuccess": "x"}

Better to cover edge cases

if rpc_message := body.get("message", ""):
return rpc_message

return resp.text or resp.reason
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return resp.text or resp.reason
return resp.text.strip() or resp.reason

@github-project-automation github-project-automation Bot moved this to Reviewed PRs that need fixes in Python PR digest Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Reviewed PRs that need fixes

Development

Successfully merging this pull request may close these issues.

OTLP HTTP Exporter Should Parse response based on content-type header

3 participants