Skip to content

Commit

Permalink
feat(@opentelemetry-instrumentation-http): support adding custom attr…
Browse files Browse the repository at this point in the history
…ibutes before a span is started (#2332)

Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
  • Loading branch information
echoontheway and dyladan committed Jul 27, 2021
1 parent 9c10bd7 commit 1bbd1dc
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 18 deletions.
18 changes: 10 additions & 8 deletions packages/opentelemetry-instrumentation-http/README.md
Expand Up @@ -47,14 +47,16 @@ Http instrumentation has few options available to choose from. You can set the f

| Options | Type | Description |
| ------- | ---- | ----------- |
| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L79) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
| [`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 |
| [`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 |
| [`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 |
| [`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 |
| [`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. |
| `requireParentforOutgoingSpans` | Boolean | Require that is a parent span to create new span for outgoing requests. |
| `requireParentforIncomingSpans` | Boolean | Require that is a parent span to create new span for incoming requests. |
| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
| [`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 |
| [`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 |
| [`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 |
| [`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 |
| [`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 |
| [`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 |
| [`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. |
| [`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. |
| [`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. |

## Useful links

Expand Down
21 changes: 21 additions & 0 deletions packages/opentelemetry-instrumentation-http/src/http.ts
Expand Up @@ -390,6 +390,10 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
attributes: utils.getIncomingRequestAttributes(request, {
component: component,
serverName: instrumentation._getConfig().serverName,
hookAttributes: instrumentation._callStartSpanHook(
request,
instrumentation._getConfig().startIncomingSpanHook
),
}),
};

Expand Down Expand Up @@ -532,6 +536,10 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
const attributes = utils.getOutgoingRequestAttributes(optionsParsed, {
component,
hostname,
hookAttributes: instrumentation._callStartSpanHook(
optionsParsed,
instrumentation._getConfig().startOutgoingSpanHook
),
});

const spanOptions: SpanOptions = {
Expand Down Expand Up @@ -638,4 +646,17 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
true
);
}

private _callStartSpanHook(
request: http.IncomingMessage | http.RequestOptions,
hookFunc: Function | undefined,
) {
if(typeof hookFunc === 'function'){
return safeExecuteInTheMiddle(
() => hookFunc(request),
() => { },
true
);
}
}
}
18 changes: 17 additions & 1 deletion packages/opentelemetry-instrumentation-http/src/types.ts
Expand Up @@ -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 {
Expand All @@ -22,6 +25,7 @@ import {
IncomingMessage,
request,
ServerResponse,
RequestOptions,
} from 'http';
import * as url from 'url';
import { InstrumentationConfig } from '@opentelemetry/instrumentation';
Expand Down Expand Up @@ -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))
*/
Expand All @@ -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 */
Expand Down
12 changes: 6 additions & 6 deletions packages/opentelemetry-instrumentation-http/src/utils.ts
Expand Up @@ -334,11 +334,11 @@ export const isValidOptionsType = (options: unknown): boolean => {
/**
* Returns outgoing request attributes scoped to the options passed to the request
* @param {ParsedRequestOptions} requestOptions the same options used to make the request
* @param {{ component: string, hostname: string }} options used to pass data needed to create attributes
* @param {{ component: string, hostname: string, hookAttributes?: SpanAttributes }} options used to pass data needed to create attributes
*/
export const getOutgoingRequestAttributes = (
requestOptions: ParsedRequestOptions,
options: { component: string; hostname: string }
options: { component: string; hostname: string; hookAttributes?: SpanAttributes }
): SpanAttributes => {
const host = requestOptions.host;
const hostname =
Expand All @@ -363,7 +363,7 @@ export const getOutgoingRequestAttributes = (
if (userAgent !== undefined) {
attributes[SemanticAttributes.HTTP_USER_AGENT] = userAgent;
}
return attributes;
return Object.assign(attributes, options.hookAttributes);
};

/**
Expand Down Expand Up @@ -415,11 +415,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'];
Expand Down Expand Up @@ -463,7 +463,7 @@ export const getIncomingRequestAttributes = (
setRequestContentLengthAttribute(request, attributes);

const httpKindAttributes = getAttributesFromHttpKind(httpVersion);
return Object.assign(attributes, httpKindAttributes);
return Object.assign(attributes, httpKindAttributes, options.hookAttributes);
};

/**
Expand Down
Expand Up @@ -18,7 +18,9 @@ import {
context,
propagation,
Span as ISpan,
SpanKind, trace,
SpanKind,
trace,
SpanAttributes,
} from '@opentelemetry/api';
import { NodeTracerProvider } from '@opentelemetry/node';
import {
Expand All @@ -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';

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -201,6 +215,8 @@ describe('HttpInstrumentation', () => {
applyCustomAttributesOnSpan: customAttributeFunction,
requestHook: requestHookFunction,
responseHook: responseHookFunction,
startIncomingSpanHook: startIncomingSpanHookFunction,
startOutgoingSpanHook: startOutgoingSpanHookFunction,
serverName,
});
instrumentation.enable();
Expand Down Expand Up @@ -686,7 +702,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;
Expand All @@ -699,6 +716,10 @@ describe('HttpInstrumentation', () => {
incomingSpan.attributes['custom response hook attribute'],
'response'
);
assert.strictEqual(
incomingSpan.attributes['guid'],
'user_guid'
);
assert.strictEqual(
incomingSpan.attributes['span kind'],
SpanKind.CLIENT
Expand All @@ -712,6 +733,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
Expand Down

0 comments on commit 1bbd1dc

Please sign in to comment.