Skip to content

Commit e78c15d

Browse files
author
echoqlwang
committed
feat(opentelemetry-instrumentation-http): support adding custom attributes before a span is started
1 parent 3a327dd commit e78c15d

File tree

5 files changed

+57
-9
lines changed

5 files changed

+57
-9
lines changed

packages/opentelemetry-instrumentation-http/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opentelemetry/instrumentation-http",
3-
"version": "0.23.0",
3+
"version": "0.23.1",
44
"description": "OpenTelemetry http/https automatic instrumentation package.",
55
"main": "build/src/index.js",
66
"types": "build/src/index.d.ts",

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

+18-3
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,8 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
408408
const span = instrumentation._startHttpSpan(
409409
`${component.toLocaleUpperCase()} ${method}`,
410410
spanOptions,
411-
ctx
411+
ctx,
412+
request
412413
);
413414
const rpcMetadata: RPCMetadata = {
414415
type: RPCType.HTTP,
@@ -538,7 +539,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
538539
const spanOptions: SpanOptions = {
539540
kind: SpanKind.CLIENT,
540541
};
541-
const span = instrumentation._startHttpSpan(operationName, spanOptions);
542+
const span = instrumentation._startHttpSpan(operationName, spanOptions, undefined, optionsParsed);
542543

543544
const parentContext = context.active();
544545
const requestContext = trace.setSpan(parentContext, span);
@@ -584,7 +585,8 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
584585
private _startHttpSpan(
585586
name: string,
586587
options: SpanOptions,
587-
ctx = context.active()
588+
ctx = context.active(),
589+
request: http.IncomingMessage | http.RequestOptions = {}
588590
) {
589591
/*
590592
* If a parent is required but not present, we use a `NoopSpan` to still
@@ -603,6 +605,9 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
603605
} else if (requireParent === true && currentSpan?.spanContext().isRemote) {
604606
span = currentSpan;
605607
} else {
608+
if (this._getConfig().startSpanHook) {
609+
this._callStartSpanHook(options, request);
610+
}
606611
span = this.tracer.startSpan(name, options, ctx);
607612
}
608613
this._spanNotEnded.add(span);
@@ -639,4 +644,14 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
639644
true
640645
);
641646
}
647+
private _callStartSpanHook(
648+
options: SpanOptions,
649+
request: http.RequestOptions | http.IncomingMessage
650+
) {
651+
safeExecuteInTheMiddle(
652+
() => this._getConfig().startSpanHook!(options, request),
653+
() => {},
654+
true
655+
);
656+
}
642657
}

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

+11-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+
SpanOptions,
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,10 @@ export interface HttpResponseCustomAttributeFunction {
6771
(span: Span, response: IncomingMessage | ServerResponse): void;
6872
}
6973

74+
export interface StartSpanCustomAttributeFunction {
75+
(options: SpanOptions, request: IncomingMessage | RequestOptions): void;
76+
}
77+
7078
/**
7179
* Options available for the HTTP instrumentation (see [documentation](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-instrumentation-http#http-instrumentation-options))
7280
*/
@@ -81,6 +89,8 @@ export interface HttpInstrumentationConfig extends InstrumentationConfig {
8189
requestHook?: HttpRequestCustomAttributeFunction;
8290
/** Function for adding custom attributes before response is handled */
8391
responseHook?: HttpResponseCustomAttributeFunction;
92+
/** Function for adding custom attributes before a span is started */
93+
startSpanHook?: StartSpanCustomAttributeFunction;
8494
/** The primary server name of the matched virtual host. */
8595
serverName?: string;
8696
/** Require parent to create span for outgoing requests */

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
*/
1616

1717
// this is autogenerated file, see scripts/version-update.js
18-
export const VERSION = '0.23.0';
18+
export const VERSION = '0.23.1';

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

+26-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+
SpanOptions,
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,13 @@ export const responseHookFunction = (
9597
span.setAttribute('custom response hook attribute', 'response');
9698
};
9799

100+
export const startSpanHookFunction = (
101+
options: SpanOptions,
102+
request: IncomingMessage | RequestOptions
103+
): void => {
104+
options.attributes = Object.assign({},options.attributes,{guid: request.headers?.guid});
105+
};
106+
98107
describe('HttpInstrumentation', () => {
99108
let contextManager: ContextManager;
100109

@@ -201,6 +210,7 @@ describe('HttpInstrumentation', () => {
201210
applyCustomAttributesOnSpan: customAttributeFunction,
202211
requestHook: requestHookFunction,
203212
responseHook: responseHookFunction,
213+
startSpanHook: startSpanHookFunction,
204214
serverName,
205215
});
206216
instrumentation.enable();
@@ -672,7 +682,8 @@ describe('HttpInstrumentation', () => {
672682

673683
it('custom attributes should show up on client and server spans', async () => {
674684
await httpRequest.get(
675-
`${protocol}://${hostname}:${serverPort}${pathname}`
685+
`${protocol}://${hostname}:${serverPort}${pathname}`,
686+
{headers: {guid: 'user_guid'}}
676687
);
677688
const spans = memoryExporter.getFinishedSpans();
678689
const [incomingSpan, outgoingSpan] = spans;
@@ -685,6 +696,14 @@ describe('HttpInstrumentation', () => {
685696
incomingSpan.attributes['custom response hook attribute'],
686697
'response'
687698
);
699+
assert.strictEqual(
700+
incomingSpan.attributes['custom response hook attribute'],
701+
'response'
702+
);
703+
assert.strictEqual(
704+
incomingSpan.attributes['guid'],
705+
'user_guid'
706+
);
688707
assert.strictEqual(
689708
incomingSpan.attributes['span kind'],
690709
SpanKind.CLIENT
@@ -698,6 +717,10 @@ describe('HttpInstrumentation', () => {
698717
outgoingSpan.attributes['custom response hook attribute'],
699718
'response'
700719
);
720+
assert.strictEqual(
721+
outgoingSpan.attributes['guid'],
722+
'user_guid'
723+
);
701724
assert.strictEqual(
702725
outgoingSpan.attributes['span kind'],
703726
SpanKind.CLIENT

0 commit comments

Comments
 (0)