From e78c15d74b8e1285fc5eb83cdf352d82a914908e Mon Sep 17 00:00:00 2001 From: echoqlwang Date: Wed, 7 Jul 2021 19:33:20 +0800 Subject: [PATCH] feat(opentelemetry-instrumentation-http): support adding custom attributes before a span is started --- .../package.json | 2 +- .../src/http.ts | 21 ++++++++++++-- .../src/types.ts | 12 +++++++- .../src/version.ts | 2 +- .../test/functionals/http-enable.test.ts | 29 +++++++++++++++++-- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-instrumentation-http/package.json b/packages/opentelemetry-instrumentation-http/package.json index 9c678840c57..b93f0ca31b5 100644 --- a/packages/opentelemetry-instrumentation-http/package.json +++ b/packages/opentelemetry-instrumentation-http/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/instrumentation-http", - "version": "0.23.0", + "version": "0.23.1", "description": "OpenTelemetry http/https automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", diff --git a/packages/opentelemetry-instrumentation-http/src/http.ts b/packages/opentelemetry-instrumentation-http/src/http.ts index 4b52635eef9..878899d0773 100644 --- a/packages/opentelemetry-instrumentation-http/src/http.ts +++ b/packages/opentelemetry-instrumentation-http/src/http.ts @@ -408,7 +408,8 @@ export class HttpInstrumentation extends InstrumentationBase { const span = instrumentation._startHttpSpan( `${component.toLocaleUpperCase()} ${method}`, spanOptions, - ctx + ctx, + request ); const rpcMetadata: RPCMetadata = { type: RPCType.HTTP, @@ -538,7 +539,7 @@ export class HttpInstrumentation extends InstrumentationBase { const spanOptions: SpanOptions = { kind: SpanKind.CLIENT, }; - const span = instrumentation._startHttpSpan(operationName, spanOptions); + const span = instrumentation._startHttpSpan(operationName, spanOptions, undefined, optionsParsed); const parentContext = context.active(); const requestContext = trace.setSpan(parentContext, span); @@ -584,7 +585,8 @@ export class HttpInstrumentation extends InstrumentationBase { private _startHttpSpan( name: string, options: SpanOptions, - ctx = context.active() + ctx = context.active(), + request: http.IncomingMessage | http.RequestOptions = {} ) { /* * If a parent is required but not present, we use a `NoopSpan` to still @@ -603,6 +605,9 @@ export class HttpInstrumentation extends InstrumentationBase { } else if (requireParent === true && currentSpan?.spanContext().isRemote) { span = currentSpan; } else { + if (this._getConfig().startSpanHook) { + this._callStartSpanHook(options, request); + } span = this.tracer.startSpan(name, options, ctx); } this._spanNotEnded.add(span); @@ -639,4 +644,14 @@ export class HttpInstrumentation extends InstrumentationBase { true ); } + private _callStartSpanHook( + options: SpanOptions, + request: http.RequestOptions | http.IncomingMessage + ) { + safeExecuteInTheMiddle( + () => this._getConfig().startSpanHook!(options, request), + () => {}, + true + ); + } } diff --git a/packages/opentelemetry-instrumentation-http/src/types.ts b/packages/opentelemetry-instrumentation-http/src/types.ts index a9927b35d7d..a0fd407e50f 100644 --- a/packages/opentelemetry-instrumentation-http/src/types.ts +++ b/packages/opentelemetry-instrumentation-http/src/types.ts @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Span } from '@opentelemetry/api'; +import { + Span, + SpanOptions, + } from '@opentelemetry/api'; import type * as http from 'http'; import type * as https from 'https'; import { @@ -22,6 +25,7 @@ import { IncomingMessage, request, ServerResponse, + RequestOptions, } from 'http'; import * as url from 'url'; import { InstrumentationConfig } from '@opentelemetry/instrumentation'; @@ -67,6 +71,10 @@ export interface HttpResponseCustomAttributeFunction { (span: Span, response: IncomingMessage | ServerResponse): void; } +export interface StartSpanCustomAttributeFunction { + (options: SpanOptions, request: IncomingMessage | RequestOptions): void; +} + /** * Options available for the HTTP instrumentation (see [documentation](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-instrumentation-http#http-instrumentation-options)) */ @@ -81,6 +89,8 @@ export interface HttpInstrumentationConfig extends InstrumentationConfig { requestHook?: HttpRequestCustomAttributeFunction; /** Function for adding custom attributes before response is handled */ responseHook?: HttpResponseCustomAttributeFunction; + /** Function for adding custom attributes before a span is started */ + startSpanHook?: StartSpanCustomAttributeFunction; /** The primary server name of the matched virtual host. */ serverName?: string; /** Require parent to create span for outgoing requests */ diff --git a/packages/opentelemetry-instrumentation-http/src/version.ts b/packages/opentelemetry-instrumentation-http/src/version.ts index 113a67d8b1b..ce5521b3fc3 100644 --- a/packages/opentelemetry-instrumentation-http/src/version.ts +++ b/packages/opentelemetry-instrumentation-http/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.23.0'; +export const VERSION = '0.23.1'; diff --git a/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts b/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts index 218dbf2f4de..859b122b237 100644 --- a/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts +++ b/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts @@ -18,7 +18,9 @@ import { context, propagation, Span as ISpan, - SpanKind, trace, + SpanKind, + trace, + SpanOptions, } from '@opentelemetry/api'; import { NodeTracerProvider } from '@opentelemetry/node'; import { @@ -39,7 +41,7 @@ import { DummyPropagation } from '../utils/DummyPropagation'; import { httpRequest } from '../utils/httpRequest'; import { ContextManager } from '@opentelemetry/api'; import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; -import type { ClientRequest, IncomingMessage, ServerResponse } from 'http'; +import type { ClientRequest, IncomingMessage, ServerResponse, RequestOptions } from 'http'; import { isWrapped } from '@opentelemetry/instrumentation'; import { getRPCMetadata, RPCType } from '@opentelemetry/core'; @@ -95,6 +97,13 @@ export const responseHookFunction = ( span.setAttribute('custom response hook attribute', 'response'); }; +export const startSpanHookFunction = ( + options: SpanOptions, + request: IncomingMessage | RequestOptions +): void => { + options.attributes = Object.assign({},options.attributes,{guid: request.headers?.guid}); +}; + describe('HttpInstrumentation', () => { let contextManager: ContextManager; @@ -201,6 +210,7 @@ describe('HttpInstrumentation', () => { applyCustomAttributesOnSpan: customAttributeFunction, requestHook: requestHookFunction, responseHook: responseHookFunction, + startSpanHook: startSpanHookFunction, serverName, }); instrumentation.enable(); @@ -672,7 +682,8 @@ describe('HttpInstrumentation', () => { it('custom attributes should show up on client and server spans', async () => { await httpRequest.get( - `${protocol}://${hostname}:${serverPort}${pathname}` + `${protocol}://${hostname}:${serverPort}${pathname}`, + {headers: {guid: 'user_guid'}} ); const spans = memoryExporter.getFinishedSpans(); const [incomingSpan, outgoingSpan] = spans; @@ -685,6 +696,14 @@ describe('HttpInstrumentation', () => { incomingSpan.attributes['custom response hook attribute'], 'response' ); + assert.strictEqual( + incomingSpan.attributes['custom response hook attribute'], + 'response' + ); + assert.strictEqual( + incomingSpan.attributes['guid'], + 'user_guid' + ); assert.strictEqual( incomingSpan.attributes['span kind'], SpanKind.CLIENT @@ -698,6 +717,10 @@ describe('HttpInstrumentation', () => { outgoingSpan.attributes['custom response hook attribute'], 'response' ); + assert.strictEqual( + outgoingSpan.attributes['guid'], + 'user_guid' + ); assert.strictEqual( outgoingSpan.attributes['span kind'], SpanKind.CLIENT