Skip to content

Commit 1bbd1dc

Browse files
echoonthewaydyladan
andauthoredJul 27, 2021
feat(@opentelemetry-instrumentation-http): support adding custom attributes before a span is started (#2332)
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
1 parent 9c10bd7 commit 1bbd1dc

File tree

5 files changed

+82
-18
lines changed

5 files changed

+82
-18
lines changed
 

‎packages/opentelemetry-instrumentation-http/README.md

+10-8
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,16 @@ Http instrumentation has few options available to choose from. You can set the f
4747

4848
| Options | Type | Description |
4949
| ------- | ---- | ----------- |
50-
| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L79) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
51-
| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#81) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
52-
| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L83) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
53-
| [`ignoreIncomingPaths`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L75) | `IgnoreMatcher[]` | Http instrumentation will not trace all incoming requests that match paths |
54-
| [`ignoreOutgoingUrls`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L77) | `IgnoreMatcher[]` | Http instrumentation will not trace all outgoing requests that match urls |
55-
| [`serverName`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L85) | `string` | The primary server name of the matched virtual host. |
56-
| `requireParentforOutgoingSpans` | Boolean | Require that is a parent span to create new span for outgoing requests. |
57-
| `requireParentforIncomingSpans` | Boolean | Require that is a parent span to create new span for incoming requests. |
50+
| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
51+
| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#93) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
52+
| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L95) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
53+
| [`startIncomingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L97) | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest |
54+
| [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
55+
| [`ignoreIncomingPaths`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L87) | `IgnoreMatcher[]` | Http instrumentation will not trace all incoming requests that match paths |
56+
| [`ignoreOutgoingUrls`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L89) | `IgnoreMatcher[]` | Http instrumentation will not trace all outgoing requests that match urls |
57+
| [`serverName`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L101) | `string` | The primary server name of the matched virtual host. |
58+
| [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
59+
| [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
5860

5961
## Useful links
6062

‎packages/opentelemetry-instrumentation-http/src/http.ts

+21
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,10 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
390390
attributes: utils.getIncomingRequestAttributes(request, {
391391
component: component,
392392
serverName: instrumentation._getConfig().serverName,
393+
hookAttributes: instrumentation._callStartSpanHook(
394+
request,
395+
instrumentation._getConfig().startIncomingSpanHook
396+
),
393397
}),
394398
};
395399

@@ -532,6 +536,10 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
532536
const attributes = utils.getOutgoingRequestAttributes(optionsParsed, {
533537
component,
534538
hostname,
539+
hookAttributes: instrumentation._callStartSpanHook(
540+
optionsParsed,
541+
instrumentation._getConfig().startOutgoingSpanHook
542+
),
535543
});
536544

537545
const spanOptions: SpanOptions = {
@@ -638,4 +646,17 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
638646
true
639647
);
640648
}
649+
650+
private _callStartSpanHook(
651+
request: http.IncomingMessage | http.RequestOptions,
652+
hookFunc: Function | undefined,
653+
) {
654+
if(typeof hookFunc === 'function'){
655+
return safeExecuteInTheMiddle(
656+
() => hookFunc(request),
657+
() => { },
658+
true
659+
);
660+
}
661+
}
641662
}

‎packages/opentelemetry-instrumentation-http/src/types.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { Span } from '@opentelemetry/api';
16+
import {
17+
Span,
18+
SpanAttributes,
19+
} from '@opentelemetry/api';
1720
import type * as http from 'http';
1821
import type * as https from 'https';
1922
import {
@@ -22,6 +25,7 @@ import {
2225
IncomingMessage,
2326
request,
2427
ServerResponse,
28+
RequestOptions,
2529
} from 'http';
2630
import * as url from 'url';
2731
import { InstrumentationConfig } from '@opentelemetry/instrumentation';
@@ -67,6 +71,14 @@ export interface HttpResponseCustomAttributeFunction {
6771
(span: Span, response: IncomingMessage | ServerResponse): void;
6872
}
6973

74+
export interface StartIncomingSpanCustomAttributeFunction {
75+
(request: IncomingMessage ): SpanAttributes;
76+
}
77+
78+
export interface StartOutgoingSpanCustomAttributeFunction {
79+
(request: RequestOptions ): SpanAttributes;
80+
}
81+
7082
/**
7183
* Options available for the HTTP instrumentation (see [documentation](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-instrumentation-http#http-instrumentation-options))
7284
*/
@@ -81,6 +93,10 @@ export interface HttpInstrumentationConfig extends InstrumentationConfig {
8193
requestHook?: HttpRequestCustomAttributeFunction;
8294
/** Function for adding custom attributes before response is handled */
8395
responseHook?: HttpResponseCustomAttributeFunction;
96+
/** Function for adding custom attributes before a span is started in incomingRequest */
97+
startIncomingSpanHook?: StartIncomingSpanCustomAttributeFunction;
98+
/** Function for adding custom attributes before a span is started in outgoingRequest */
99+
startOutgoingSpanHook?: StartOutgoingSpanCustomAttributeFunction;
84100
/** The primary server name of the matched virtual host. */
85101
serverName?: string;
86102
/** Require parent to create span for outgoing requests */

‎packages/opentelemetry-instrumentation-http/src/utils.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,11 @@ export const isValidOptionsType = (options: unknown): boolean => {
334334
/**
335335
* Returns outgoing request attributes scoped to the options passed to the request
336336
* @param {ParsedRequestOptions} requestOptions the same options used to make the request
337-
* @param {{ component: string, hostname: string }} options used to pass data needed to create attributes
337+
* @param {{ component: string, hostname: string, hookAttributes?: SpanAttributes }} options used to pass data needed to create attributes
338338
*/
339339
export const getOutgoingRequestAttributes = (
340340
requestOptions: ParsedRequestOptions,
341-
options: { component: string; hostname: string }
341+
options: { component: string; hostname: string; hookAttributes?: SpanAttributes }
342342
): SpanAttributes => {
343343
const host = requestOptions.host;
344344
const hostname =
@@ -363,7 +363,7 @@ export const getOutgoingRequestAttributes = (
363363
if (userAgent !== undefined) {
364364
attributes[SemanticAttributes.HTTP_USER_AGENT] = userAgent;
365365
}
366-
return attributes;
366+
return Object.assign(attributes, options.hookAttributes);
367367
};
368368

369369
/**
@@ -415,11 +415,11 @@ export const getOutgoingRequestAttributesOnResponse = (
415415
/**
416416
* Returns incoming request attributes scoped to the request data
417417
* @param {IncomingMessage} request the request object
418-
* @param {{ component: string, serverName?: string }} options used to pass data needed to create attributes
418+
* @param {{ component: string, serverName?: string, hookAttributes?: SpanAttributes }} options used to pass data needed to create attributes
419419
*/
420420
export const getIncomingRequestAttributes = (
421421
request: IncomingMessage,
422-
options: { component: string; serverName?: string }
422+
options: { component: string; serverName?: string; hookAttributes?: SpanAttributes }
423423
): SpanAttributes => {
424424
const headers = request.headers;
425425
const userAgent = headers['user-agent'];
@@ -463,7 +463,7 @@ export const getIncomingRequestAttributes = (
463463
setRequestContentLengthAttribute(request, attributes);
464464

465465
const httpKindAttributes = getAttributesFromHttpKind(httpVersion);
466-
return Object.assign(attributes, httpKindAttributes);
466+
return Object.assign(attributes, httpKindAttributes, options.hookAttributes);
467467
};
468468

469469
/**

‎packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts

+28-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import {
1818
context,
1919
propagation,
2020
Span as ISpan,
21-
SpanKind, trace,
21+
SpanKind,
22+
trace,
23+
SpanAttributes,
2224
} from '@opentelemetry/api';
2325
import { NodeTracerProvider } from '@opentelemetry/node';
2426
import {
@@ -39,7 +41,7 @@ import { DummyPropagation } from '../utils/DummyPropagation';
3941
import { httpRequest } from '../utils/httpRequest';
4042
import { ContextManager } from '@opentelemetry/api';
4143
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
42-
import type { ClientRequest, IncomingMessage, ServerResponse } from 'http';
44+
import type { ClientRequest, IncomingMessage, ServerResponse, RequestOptions } from 'http';
4345
import { isWrapped } from '@opentelemetry/instrumentation';
4446
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
4547

@@ -95,6 +97,18 @@ export const responseHookFunction = (
9597
span.setAttribute('custom response hook attribute', 'response');
9698
};
9799

100+
export const startIncomingSpanHookFunction = (
101+
request: IncomingMessage
102+
): SpanAttributes => {
103+
return {guid: request.headers?.guid}
104+
};
105+
106+
export const startOutgoingSpanHookFunction = (
107+
request: RequestOptions
108+
): SpanAttributes => {
109+
return {guid: request.headers?.guid}
110+
};
111+
98112
describe('HttpInstrumentation', () => {
99113
let contextManager: ContextManager;
100114

@@ -201,6 +215,8 @@ describe('HttpInstrumentation', () => {
201215
applyCustomAttributesOnSpan: customAttributeFunction,
202216
requestHook: requestHookFunction,
203217
responseHook: responseHookFunction,
218+
startIncomingSpanHook: startIncomingSpanHookFunction,
219+
startOutgoingSpanHook: startOutgoingSpanHookFunction,
204220
serverName,
205221
});
206222
instrumentation.enable();
@@ -686,7 +702,8 @@ describe('HttpInstrumentation', () => {
686702

687703
it('custom attributes should show up on client and server spans', async () => {
688704
await httpRequest.get(
689-
`${protocol}://${hostname}:${serverPort}${pathname}`
705+
`${protocol}://${hostname}:${serverPort}${pathname}`,
706+
{headers: {guid: 'user_guid'}}
690707
);
691708
const spans = memoryExporter.getFinishedSpans();
692709
const [incomingSpan, outgoingSpan] = spans;
@@ -699,6 +716,10 @@ describe('HttpInstrumentation', () => {
699716
incomingSpan.attributes['custom response hook attribute'],
700717
'response'
701718
);
719+
assert.strictEqual(
720+
incomingSpan.attributes['guid'],
721+
'user_guid'
722+
);
702723
assert.strictEqual(
703724
incomingSpan.attributes['span kind'],
704725
SpanKind.CLIENT
@@ -712,6 +733,10 @@ describe('HttpInstrumentation', () => {
712733
outgoingSpan.attributes['custom response hook attribute'],
713734
'response'
714735
);
736+
assert.strictEqual(
737+
outgoingSpan.attributes['guid'],
738+
'user_guid'
739+
);
715740
assert.strictEqual(
716741
outgoingSpan.attributes['span kind'],
717742
SpanKind.CLIENT

0 commit comments

Comments
 (0)
Please sign in to comment.