diff --git a/packages/opentelemetry-instrumentation-http/src/http.ts b/packages/opentelemetry-instrumentation-http/src/http.ts index 4b52635eef..017da15e4d 100644 --- a/packages/opentelemetry-instrumentation-http/src/http.ts +++ b/packages/opentelemetry-instrumentation-http/src/http.ts @@ -24,6 +24,7 @@ import { SpanStatus, SpanStatusCode, trace, + SpanAttributes, } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; import type * as http from 'http'; @@ -396,11 +397,17 @@ export class HttpInstrumentation extends InstrumentationBase { const headers = request.headers; + let hookAttributes: SpanAttributes | undefined; + if (instrumentation._getConfig().startIncomingSpanHook) { + hookAttributes = instrumentation._callStartIncomingSpanHook(request); + } + const spanOptions: SpanOptions = { kind: SpanKind.SERVER, attributes: utils.getIncomingRequestAttributes(request, { component: component, serverName: instrumentation._getConfig().serverName, + hookAttributes: hookAttributes, }), }; @@ -534,9 +541,15 @@ export class HttpInstrumentation extends InstrumentationBase { return original.apply(this, [optionsParsed, ...args]); } + let hookAttributes: SpanAttributes | undefined; + if (instrumentation._getConfig().startOutgoingSpanHook) { + hookAttributes = instrumentation._callStartOutgoingSpanHook(optionsParsed); + } + const operationName = `${component.toUpperCase()} ${method}`; const spanOptions: SpanOptions = { kind: SpanKind.CLIENT, + attributes: hookAttributes, }; const span = instrumentation._startHttpSpan(operationName, spanOptions); @@ -639,4 +652,20 @@ export class HttpInstrumentation extends InstrumentationBase { true ); } + + private _callStartIncomingSpanHook(request: http.IncomingMessage): SpanAttributes { + return safeExecuteInTheMiddle( + () => this._getConfig().startIncomingSpanHook!(request), + () => { }, + true + ); + } + + private _callStartOutgoingSpanHook(request: http.RequestOptions): SpanAttributes { + return safeExecuteInTheMiddle( + () => this._getConfig().startOutgoingSpanHook!(request), + () => {}, + true + ); + } } diff --git a/packages/opentelemetry-instrumentation-http/src/types.ts b/packages/opentelemetry-instrumentation-http/src/types.ts index a9927b35d7..7be9999d8b 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, + SpanAttributes, + } 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,14 @@ export interface HttpResponseCustomAttributeFunction { (span: Span, response: IncomingMessage | ServerResponse): void; } +export interface StartIncomingSpanCustomAttributeFunction { + (request: IncomingMessage ): SpanAttributes; +} + +export interface StartOutgoingSpanCustomAttributeFunction { + (request: RequestOptions ): SpanAttributes; +} + /** * 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 +93,10 @@ 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 in incomingRequest */ + startIncomingSpanHook?: StartIncomingSpanCustomAttributeFunction; + /** Function for adding custom attributes before a span is started in outgoingRequest */ + startOutgoingSpanHook?: StartOutgoingSpanCustomAttributeFunction; /** 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/utils.ts b/packages/opentelemetry-instrumentation-http/src/utils.ts index fa1c55362a..41422742d7 100644 --- a/packages/opentelemetry-instrumentation-http/src/utils.ts +++ b/packages/opentelemetry-instrumentation-http/src/utils.ts @@ -410,11 +410,11 @@ export const getOutgoingRequestAttributesOnResponse = ( /** * Returns incoming request attributes scoped to the request data * @param {IncomingMessage} request the request object - * @param {{ component: string, serverName?: string }} options used to pass data needed to create attributes + * @param {{ component: string, serverName?: string, hookAttributes?: SpanAttributes }} options used to pass data needed to create attributes */ export const getIncomingRequestAttributes = ( request: IncomingMessage, - options: { component: string; serverName?: string } + options: { component: string; serverName?: string; hookAttributes?: SpanAttributes } ): SpanAttributes => { const headers = request.headers; const userAgent = headers['user-agent']; @@ -458,7 +458,7 @@ export const getIncomingRequestAttributes = ( setRequestContentLengthAttribute(request, attributes); const httpKindAttributes = getAttributesFromHttpKind(httpVersion); - return Object.assign(attributes, httpKindAttributes); + return Object.assign(attributes, httpKindAttributes, options.hookAttributes); }; /** 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 218dbf2f4d..40a581a6f2 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, + SpanAttributes, } 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,18 @@ export const responseHookFunction = ( span.setAttribute('custom response hook attribute', 'response'); }; +export const startIncomingSpanHookFunction = ( + request: IncomingMessage +): SpanAttributes => { + return {guid: request.headers?.guid} +}; + +export const startOutgoingSpanHookFunction = ( + request: RequestOptions +): SpanAttributes => { + return {guid: request.headers?.guid} +}; + describe('HttpInstrumentation', () => { let contextManager: ContextManager; @@ -201,6 +215,8 @@ describe('HttpInstrumentation', () => { applyCustomAttributesOnSpan: customAttributeFunction, requestHook: requestHookFunction, responseHook: responseHookFunction, + startIncomingSpanHook: startIncomingSpanHookFunction, + startOutgoingSpanHook: startOutgoingSpanHookFunction, serverName, }); instrumentation.enable(); @@ -672,7 +688,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 +702,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 +723,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