Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2

### :bug: Bug Fixes

* fix(instrumentation-fetch): Handling null-body-status responses [#6037](https://github.com/open-telemetry/opentelemetry-js/pull/6037) @m0sa

### :books: Documentation

### :house: Internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,14 @@ export class FetchInstrumentation extends InstrumentationBase<FetchInstrumentati
const body = resClone.body;
if (body) {
const reader = body.getReader();

const wrappedBody = withCancelPropagation(response.body, reader);
const isNullBodyStatus =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I assume these statuses are the ones with null body according to a spec. But my experience tells me some apis may not follow these specs and return body anyways. I wonder if it's okay to not process the body in this scenario

@m0sa m0sa Oct 29, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The new Response({ /* non-null-wrapped-body */ }, { status: 204 /* or 101, 205, 304 */ }) constructor always throws:

Image

The only way of processing it further would mean avoid reconstructing it / preserving a code path that was in place prior to #5894

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

well, backends could be written in different languages and those may let the dev pass a payload. According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status we should not expect payloads for these statuses so I'm fine. If there is interest in reading the body even with these cases we can add it later.

response.status === 101 ||
response.status === 204 ||
response.status === 205 ||
response.status === 304;
const wrappedBody = isNullBodyStatus
? null
: withCancelPropagation(response.body, reader);
Comment thread
m0sa marked this conversation as resolved.

proxiedResponse = new Response(wrappedBody, {
status: response.status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ describe('fetch', () => {
msw.http.get('/boom', () => {
return new msw.HttpResponse(null, { status: 500 });
}),
msw.http.get('/null-body', () => {
return new msw.HttpResponse(null, { status: 204 });
}),
],
callback = () => fetch('/api/status.json'),
config = {},
Expand All @@ -391,6 +394,20 @@ describe('fetch', () => {
return { rootSpan, response };
};

describe('null-bodied response', () => {
// https://chromium.googlesource.com/chromium/src/+/ac85ca2a9cb8c76a37f9d7a6c611c24114f1f05d/third_party/WebKit/Source/core/fetch/Response.cpp#106
let response: Response | undefined;
beforeEach(async () => {
const result = await tracedFetch({
callback: () => fetch('/null-body'),
});
response = result.response;
});
it('should be handled correctly', async () => {
assert.strictEqual(response?.status, 204);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Other tests make assertions that the spans were emitted rather then asserting on the response. Something like:

   assert.strictEqual(exportedSpans.length, 1);
Suggested change
it('should be handled correctly', async () => {
assert.strictEqual(response?.status, 204);
});
it('204 (No Content) will correctly end the span', async () => {
assert.strictEqual(exportedSpans.length, 1);
});

We also have untested functionality for other null-body content. 101, 205, 304 we could test those as well.

@m0sa m0sa Oct 24, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added separate test cases for each status code.

I did find a weird edge-case in 101 that IDK if is already handled correctly. I would expect a span to be exported, with the correct error code, but this either doesn't happen in tests due to how it's set up, or it actually wouldn't happen. @wolfgangcodes any ideas?

The fetch.ts endSpanOnError callback does happen in that case, but somehow the span is not there for inspection.

@wolfgangcodes wolfgangcodes Oct 24, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for adding those!

I don't have any immediate thoughts on what might be happening with 101s, but I like your approach of implementing what we can test easily right now for for common success responses.

We can always follow up with better support for 101s if they crop up or if we have more time to think about it!

I approved to show my support, but we'll need add'l approvals from the JS folks.

});

describe('simple request', () => {
let rootSpan: api.Span | undefined;
let response: Response | undefined;
Expand Down