Skip to content

Commit 5dbe53a

Browse files
niekertvmarchaud
andauthored
feat(instrumentation-fetch): Add applyCustomAttributesOnSpan option (#2071)
Co-authored-by: Valentin Marchaud <[email protected]>
1 parent 90a4b4d commit 5dbe53a

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed

packages/opentelemetry-instrumentation-fetch/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ fetch('http://localhost:8090/fetch.js');
6161

6262
See [examples/tracer-web/fetch](https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/tracer-web) for a short example.
6363

64+
### Fetch Instrumentation options
65+
66+
Fetch instrumentation plugin has few options available to choose from. You can set the following:
67+
68+
| Options | Type | Description |
69+
| ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ------------------------------------- |
70+
| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-fetch/src/fetch.ts#L47) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
71+
6472
## Useful links
6573

6674
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>

packages/opentelemetry-instrumentation-fetch/src/fetch.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
isWrapped,
2020
InstrumentationBase,
2121
InstrumentationConfig,
22+
safeExecuteInTheMiddle,
2223
} from '@opentelemetry/instrumentation';
2324
import * as core from '@opentelemetry/core';
2425
import * as web from '@opentelemetry/web';
@@ -43,6 +44,14 @@ const getUrlNormalizingAnchor = () => {
4344
return a;
4445
};
4546

47+
export interface FetchCustomAttributeFunction {
48+
(
49+
span: api.Span,
50+
request: Request | RequestInit,
51+
result: Response | FetchError
52+
): void;
53+
}
54+
4655
/**
4756
* FetchPlugin Config
4857
*/
@@ -61,6 +70,8 @@ export interface FetchInstrumentationConfig extends InstrumentationConfig {
6170
* also not be traced.
6271
*/
6372
ignoreUrls?: Array<string | RegExp>;
73+
/** Function for adding custom attributes on the span */
74+
applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;
6475
}
6576

6677
/**
@@ -311,6 +322,7 @@ export class FetchInstrumentation extends InstrumentationBase<
311322
response: Response
312323
) {
313324
try {
325+
plugin._applyAttributesAfterFetch(span, options, response);
314326
if (response.status >= 200 && response.status < 400) {
315327
plugin._endSpan(span, spanData, response);
316328
} else {
@@ -331,6 +343,7 @@ export class FetchInstrumentation extends InstrumentationBase<
331343
error: FetchError
332344
) {
333345
try {
346+
plugin._applyAttributesAfterFetch(span, options, error);
334347
plugin._endSpan(span, spanData, {
335348
status: error.status || 0,
336349
statusText: error.message,
@@ -360,6 +373,28 @@ export class FetchInstrumentation extends InstrumentationBase<
360373
};
361374
}
362375

376+
private _applyAttributesAfterFetch(
377+
span: api.Span,
378+
request: Request | RequestInit,
379+
result: Response | FetchError
380+
) {
381+
const applyCustomAttributesOnSpan = this._getConfig()
382+
.applyCustomAttributesOnSpan;
383+
if (applyCustomAttributesOnSpan) {
384+
safeExecuteInTheMiddle(
385+
() => applyCustomAttributesOnSpan(span, request, result),
386+
error => {
387+
if (!error) {
388+
return;
389+
}
390+
391+
api.diag.error('applyCustomAttributesOnSpan', error);
392+
},
393+
true
394+
);
395+
}
396+
}
397+
363398
/**
364399
* Prepares a span data - needed later for matching appropriate network
365400
* resources

packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ import {
3535
} from '@opentelemetry/web';
3636
import * as assert from 'assert';
3737
import * as sinon from 'sinon';
38-
import { FetchInstrumentation, FetchInstrumentationConfig } from '../src';
38+
import {
39+
FetchInstrumentation,
40+
FetchInstrumentationConfig,
41+
FetchCustomAttributeFunction,
42+
} from '../src';
3943
import { AttributeNames } from '../src/enums/AttributeNames';
4044
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
4145

@@ -57,6 +61,7 @@ const getData = (url: string, method?: string) =>
5761
},
5862
});
5963

64+
const CUSTOM_ATTRIBUTE_KEY = 'span kind';
6065
const defaultResource = {
6166
connectEnd: 15,
6267
connectStart: 13,
@@ -576,6 +581,73 @@ describe('fetch', () => {
576581
});
577582
});
578583

584+
describe('applyCustomAttributesOnSpan option', () => {
585+
const noop = () => {};
586+
const prepare = (
587+
url: string,
588+
applyCustomAttributesOnSpan: FetchCustomAttributeFunction,
589+
cb: VoidFunction = noop
590+
) => {
591+
const propagateTraceHeaderCorsUrls = [url];
592+
593+
prepareData(cb, url, {
594+
propagateTraceHeaderCorsUrls,
595+
applyCustomAttributesOnSpan,
596+
});
597+
};
598+
599+
afterEach(() => {
600+
clearData();
601+
});
602+
603+
it('applies attributes when the request is succesful', done => {
604+
prepare(
605+
url,
606+
span => {
607+
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
608+
},
609+
() => {
610+
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
611+
const attributes = span.attributes;
612+
613+
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
614+
done();
615+
}
616+
);
617+
});
618+
619+
it('applies custom attributes when the request fails', done => {
620+
prepare(
621+
badUrl,
622+
span => {
623+
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
624+
},
625+
() => {
626+
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
627+
const attributes = span.attributes;
628+
629+
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
630+
done();
631+
}
632+
);
633+
});
634+
635+
it('has request and response objects in callback arguments', done => {
636+
const applyCustomAttributes: FetchCustomAttributeFunction = (
637+
span,
638+
request,
639+
response
640+
) => {
641+
assert.ok(request.method === 'GET');
642+
assert.ok(response.status === 200);
643+
644+
done();
645+
};
646+
647+
prepare(url, applyCustomAttributes);
648+
});
649+
});
650+
579651
describe('when url is ignored', () => {
580652
beforeEach(done => {
581653
const propagateTraceHeaderCorsUrls = url;

0 commit comments

Comments
 (0)