Skip to content

Commit 60a99c9

Browse files
authored
feat(instrumentation-undici): Add responseHook (#2356)
1 parent 167dced commit 60a99c9

File tree

5 files changed

+71
-7
lines changed

5 files changed

+71
-7
lines changed

plugins/node/instrumentation-undici/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ Undici instrumentation has few options available to choose from. You can set the
5151

5252
| Options | Type | Description |
5353
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
54-
| [`ignoreRequestHook`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L63) | `IgnoreRequestFunction` | Undici instrumentation will not trace all incoming requests that matched with custom function. |
55-
| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L65) | `RequestHookFunction` | Function for adding custom attributes before request is handled. |
56-
| [`startSpanHook`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L67) | `StartSpanHookFunction` | Function for adding custom attributes before a span is started. |
57-
| [`requireParentforSpans`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L69) | `Boolean` | Require a parent span is present to create new span for outgoing requests. |
58-
| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L71) | `Object` | List of case insensitive HTTP headers to convert to span attributes. Headers will be converted to span attributes in the form of `http.{request\|response}.header.header-name` where the name is only lowercased, e.g. `http.response.header.content-length` |
54+
| [`ignoreRequestHook`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L73) | `IgnoreRequestFunction` | Undici instrumentation will not trace all incoming requests that matched with custom function. |
55+
| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L75) | `RequestHookFunction` | Function for adding custom attributes before request is handled. |
56+
| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L77) | `ResponseHookFunction` | Function for adding custom attributes after the response headers are received. |
57+
| [`startSpanHook`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L79) | `StartSpanHookFunction` | Function for adding custom attributes before a span is started. |
58+
| [`requireParentforSpans`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L81) | `Boolean` | Require a parent span is present to create new span for outgoing requests. |
59+
| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/instrumentation-undici/src/types.ts#L83) | `Object` | List of case insensitive HTTP headers to convert to span attributes. Headers will be converted to span attributes in the form of `http.{request\|response}.header.header-name` where the name is only lowercased, e.g. `http.response.header.content-length` |
5960

6061
### Observations
6162

plugins/node/instrumentation-undici/src/types.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface UndiciRequest {
4141
export interface UndiciResponse {
4242
headers: Buffer[];
4343
statusCode: number;
44+
statusText: string;
4445
}
4546

4647
export interface IgnoreRequestFunction<T = UndiciRequest> {
@@ -51,18 +52,29 @@ export interface RequestHookFunction<T = UndiciRequest> {
5152
(span: Span, request: T): void;
5253
}
5354

55+
export interface ResponseHookFunction<
56+
RequestType = UndiciResponse,
57+
ResponseType = UndiciResponse
58+
> {
59+
(span: Span, info: { request: RequestType; response: ResponseType }): void;
60+
}
61+
5462
export interface StartSpanHookFunction<T = UndiciRequest> {
5563
(request: T): Attributes;
5664
}
5765

5866
// This package will instrument HTTP requests made through `undici` or `fetch` global API
5967
// so it seems logical to have similar options than the HTTP instrumentation
60-
export interface UndiciInstrumentationConfig<RequestType = UndiciRequest>
61-
extends InstrumentationConfig {
68+
export interface UndiciInstrumentationConfig<
69+
RequestType = UndiciRequest,
70+
ResponseType = UndiciResponse
71+
> extends InstrumentationConfig {
6272
/** Not trace all outgoing requests that matched with custom function */
6373
ignoreRequestHook?: IgnoreRequestFunction<RequestType>;
6474
/** Function for adding custom attributes before request is handled */
6575
requestHook?: RequestHookFunction<RequestType>;
76+
/** Function called once response headers have been received */
77+
responseHook?: ResponseHookFunction<RequestType, ResponseType>;
6678
/** Function for adding custom attributes before a span is started */
6779
startSpanHook?: StartSpanHookFunction<RequestType>;
6880
/** Require parent to create span for outgoing requests */

plugins/node/instrumentation-undici/src/undici.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,14 @@ export class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumenta
354354
};
355355

356356
const config = this.getConfig();
357+
358+
// Execute the response hook if defined
359+
safeExecuteInTheMiddle(
360+
() => config.responseHook?.(span, { request, response }),
361+
e => e && this._diag.error('caught responseHook error: ', e),
362+
true
363+
);
364+
357365
const headersToAttribs = new Set();
358366

359367
if (config.headersToSpanAttributes?.responseHeaders) {

plugins/node/instrumentation-undici/test/fetch.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ describe('UndiciInstrumentation `fetch` tests', function () {
142142
requestHook: () => {
143143
throw new Error('requestHook error');
144144
},
145+
responseHook: () => {
146+
throw new Error('responseHook error');
147+
},
145148
startSpanHook: () => {
146149
throw new Error('startSpanHook error');
147150
},
@@ -213,6 +216,12 @@ describe('UndiciInstrumentation `fetch` tests', function () {
213216
req.headers.push('x-requested-with', 'undici');
214217
}
215218
},
219+
responseHook: (span, { response }) => {
220+
span.setAttribute(
221+
'test.response-hook.attribute',
222+
response.statusText
223+
);
224+
},
216225
startSpanHook: request => {
217226
return {
218227
'test.hook.attribute': 'hook-value',
@@ -281,6 +290,11 @@ describe('UndiciInstrumentation `fetch` tests', function () {
281290
'hook-value',
282291
'startSpanHook is called'
283292
);
293+
assert.strictEqual(
294+
span.attributes['test.response-hook.attribute'],
295+
'OK',
296+
'responseHook is called'
297+
);
284298
});
285299

286300
it('should not create spans without parent if required in configuration', async function () {

plugins/node/instrumentation-undici/test/undici.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ describe('UndiciInstrumentation `undici` tests', function () {
169169
req.headers.push('x-requested-with', 'undici');
170170
}
171171
},
172+
responseHook: (span, { response }) => {
173+
span.setAttribute(
174+
'test.response-hook.attribute',
175+
response.statusText
176+
);
177+
},
172178
startSpanHook: request => {
173179
return {
174180
'test.hook.attribute': 'hook-value',
@@ -357,6 +363,11 @@ describe('UndiciInstrumentation `undici` tests', function () {
357363
'hook-value',
358364
'startSpanHook is called'
359365
);
366+
assert.strictEqual(
367+
span.attributes['test.response-hook.attribute'],
368+
'OK',
369+
'responseHook is called'
370+
);
360371
});
361372

362373
it('should create valid spans for "fetch" method', async function () {
@@ -417,6 +428,11 @@ describe('UndiciInstrumentation `undici` tests', function () {
417428
'hook-value',
418429
'startSpanHook is called'
419430
);
431+
assert.strictEqual(
432+
span.attributes['test.response-hook.attribute'],
433+
'OK',
434+
'responseHook is called'
435+
);
420436
});
421437

422438
it('should create valid spans for "stream" method', async function () {
@@ -485,6 +501,11 @@ describe('UndiciInstrumentation `undici` tests', function () {
485501
'hook-value',
486502
'startSpanHook is called'
487503
);
504+
assert.strictEqual(
505+
span.attributes['test.response-hook.attribute'],
506+
'OK',
507+
'responseHook is called'
508+
);
488509
});
489510

490511
it('should create valid spans for "dispatch" method', async function () {
@@ -561,6 +582,11 @@ describe('UndiciInstrumentation `undici` tests', function () {
561582
'hook-value',
562583
'startSpanHook is called'
563584
);
585+
assert.strictEqual(
586+
span.attributes['test.response-hook.attribute'],
587+
'OK',
588+
'responseHook is called'
589+
);
564590
});
565591

566592
it('should create valid spans even if the configuration hooks fail', async function () {
@@ -576,6 +602,9 @@ describe('UndiciInstrumentation `undici` tests', function () {
576602
requestHook: () => {
577603
throw new Error('requestHook error');
578604
},
605+
responseHook: () => {
606+
throw new Error('responseHook error');
607+
},
579608
startSpanHook: () => {
580609
throw new Error('startSpanHook error');
581610
},

0 commit comments

Comments
 (0)