Skip to content

Commit

Permalink
Port applyCustomAttributesOnSpan tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chancancode committed Jan 13, 2025
1 parent 4b35fc2 commit 01c07b9
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ describe('fetch', () => {
});
});

describe('applyCustomAttributesOnSpan option', () => {
xdescribe('applyCustomAttributesOnSpan option', () => {
const prepare = async (
url: string,
applyCustomAttributesOnSpan: FetchCustomAttributeFunction
Expand All @@ -898,7 +898,7 @@ describe('fetch', () => {
clearData();
});

it('applies attributes when the request is successful', async () => {
xit('applies attributes when the request is successful', async () => {
await prepare(url, span => {
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
});
Expand All @@ -908,7 +908,7 @@ describe('fetch', () => {
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
});

it('applies custom attributes when the request fails', async () => {
xit('applies custom attributes when the request fails', async () => {
await prepare(badUrl, span => {
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
});
Expand All @@ -918,7 +918,7 @@ describe('fetch', () => {
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
});

it('has request and response objects in callback arguments', async () => {
xit('has request and response objects in callback arguments', async () => {
let request: any;
let response: any;
const applyCustomAttributes: FetchCustomAttributeFunction = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ import {
} from '@opentelemetry/sdk-trace-web';
import * as assert from 'assert';
import * as sinon from 'sinon';
import { FetchInstrumentation, FetchInstrumentationConfig } from '../src';
import {
FetchCustomAttributeFunction,
FetchInstrumentation,
FetchInstrumentationConfig,
} from '../src';
import { AttributeNames } from '../src/enums/AttributeNames';
import {
SEMATTRS_HTTP_HOST,
Expand Down Expand Up @@ -1102,5 +1106,164 @@ describe('fetch', () => {
]);
});
});

describe('`applyCustomAttributesOnSpan` hook', () => {
const tracedFetch = async ({
handlers = [
msw.http.get('/api/project-headers.json', ({ request }) => {
const headers = new Headers();

for (const [key, value] of request.headers) {
headers.set(`x-request-${key}`, value);
}

return msw.HttpResponse.json({ ok: true }, { headers });
}),
msw.http.get('/api/fail.json', () => {
return msw.HttpResponse.json({ fail: true }, { status: 500 });
}),
],
callback = () => fetch('/api/project-headers.json'),
config,
}: {
handlers?: msw.RequestHandler[];
callback?: () => Promise<Response>;
config: FetchInstrumentationConfig &
Required<
Pick<FetchInstrumentationConfig, 'applyCustomAttributesOnSpan'>
>;
}): Promise<{ rootSpan: api.Span; response: Response }> => {
let response: Response | undefined;

await startWorker(...handlers);

// The current implementation doesn't call this hook until the body has
// been fully read, this ensures that timing is met before returning to
// the test so we don't have to deal with it in every test. Plus it
// checks that the hook is definitely called which is important here.
const appliedCustomAttributes = new Promise<void>(resolve => {
const originalHook = config.applyCustomAttributesOnSpan;

const applyCustomAttributesOnSpan = (
...args: Parameters<FetchCustomAttributeFunction>
) => {
resolve();
originalHook(...args);
};

config = { ...config, applyCustomAttributesOnSpan };
});

const rootSpan = await trace(async () => {
response = await callback();
}, config);

await appliedCustomAttributes;

assert.ok(response instanceof Response);
assert.strictEqual(exportedSpans.length, 1);

return { rootSpan, response };
};

it('can apply arbitrary attributes to the span indiscriminantly', async () => {
await tracedFetch({
config: {
applyCustomAttributesOnSpan: span => {
span.setAttribute('custom.foo', 'bar');
},
},
});

const span: tracing.ReadableSpan = exportedSpans[0];
assert.strictEqual(span.attributes['custom.foo'], 'bar');
});

describe('successful request', () => {
it('has access to the request and response objects', async () => {
await tracedFetch({
callback: () =>
fetch(
new Request('/api/project-headers.json', {
headers: new Headers({
foo: 'bar',
}),
})
),
config: {
applyCustomAttributesOnSpan: (span, request, response) => {
assert.ok(request.headers instanceof Headers);
assert.ok(response instanceof Response);
assert.ok(response.headers instanceof Headers);

assert.strictEqual(
request.headers.get('foo'),
response.headers.get('x-request-foo')
);

span.setAttribute(
'custom.foo',
response.headers.get('x-request-foo')!
);

/*
Note: this confirms that nothing *in the instrumentation code*
consumed the response body; it doesn't guarantee that the response
object passed to the `applyCustomAttributes` hook will always have
a consumable body – in fact, this is typically *not* the case:
```js
// user code:
let response = await fetch("foo");
let json = await response.json(); // <- user code consumes the body on `response`
// ...
{
// ...this is called sometime later...
applyCustomAttributes(span, request, response) {
// too late!
response.bodyUsed // => true
}
}
```
See https://github.com/open-telemetry/opentelemetry-js/pull/5281
*/
assert.strictEqual(response.bodyUsed, false);
},
},
});

const span: tracing.ReadableSpan = exportedSpans[0];
assert.strictEqual(span.attributes['custom.foo'], 'bar');
});

// https://github.com/open-telemetry/opentelemetry-js/pull/5281
it('will not be able to access the response body if already consumed by the application', async () => {
await tracedFetch({
callback: async () => {
const response = await fetch(
new Request('/api/project-headers.json')
);

// body consumed here by the application
await response.json();

return response;
},
config: {
applyCustomAttributesOnSpan: (span, _request, response) => {
assert.ok(response instanceof Response);

span.setAttribute('custom.body-used', response.bodyUsed);
},
},
});

const span: tracing.ReadableSpan = exportedSpans[0];
assert.strictEqual(span.attributes['custom.body-used'], true);
});
});
});
});
});

0 comments on commit 01c07b9

Please sign in to comment.