Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-http): add plugin hooks before processing req and res #963

Merged
merged 8 commits into from
May 13, 2020
2 changes: 2 additions & 0 deletions packages/opentelemetry-plugin-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Http plugin has few options available to choose from. You can set the following:
| Options | Type | Description |
| ------- | ---- | ----------- |
| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L52) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L60) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L67) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
| [`ignoreIncomingPaths`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L28) | `IgnoreMatcher[]` | Http plugin will not trace all incoming requests that match paths |
| [`ignoreOutgoingUrls`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L28) | `IgnoreMatcher[]` | Http plugin will not trace all outgoing requests that match urls |
| [`serverName`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L28) | `string` | The primary server name of the matched virtual host. |
Expand Down
35 changes: 35 additions & 0 deletions packages/opentelemetry-plugin-http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ export class HttpPlugin extends BasePlugin<Http> {
hostname,
});
span.setAttributes(attributes);
if (this._config.requestHook) {
this._callRequestHook(span, request);
}

request.on(
'response',
Expand All @@ -212,6 +215,9 @@ export class HttpPlugin extends BasePlugin<Http> {
{ hostname }
);
span.setAttributes(attributes);
if (this._config.responseHook) {
this._callResponseHook(span, response);
}

this._tracer.bind(response);
this._logger.debug('outgoingRequest on response()');
Expand Down Expand Up @@ -316,6 +322,13 @@ export class HttpPlugin extends BasePlugin<Http> {
context.bind(request);
context.bind(response);

if (plugin._config.requestHook) {
plugin._callRequestHook(span, request);
}
if (plugin._config.responseHook) {
plugin._callResponseHook(span, response);
}

// Wraps end (inspired by:
// https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/master/src/plugins/plugin-connect.ts#L75)
const originalEnd = response.end;
Expand Down Expand Up @@ -465,6 +478,28 @@ export class HttpPlugin extends BasePlugin<Http> {
this._spanNotEnded.delete(span);
}

private _callResponseHook(
span: Span,
response: IncomingMessage | ServerResponse
) {
this._safeExecute(
span,
() => this._config.responseHook!(span, response),
false
);
}

private _callRequestHook(
span: Span,
request: ClientRequest | IncomingMessage
) {
this._safeExecute(
span,
() => this._config.requestHook!(span, request),
false
);
}

private _safeExecute<
T extends (...args: unknown[]) => ReturnType<T>,
K extends boolean
Expand Down
14 changes: 13 additions & 1 deletion packages/opentelemetry-plugin-http/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ export interface HttpCustomAttributeFunction {
): void;
}

export interface HttpRequestCustomAttributeFunction {
(span: Span, request: ClientRequest | IncomingMessage): void;
}

export interface HttpResponseCustomAttributeFunction {
(span: Span, response: IncomingMessage | ServerResponse): void;
}

/**
* Options available for the HTTP Plugin (see [documentation](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-http#http-plugin-options))
*/
Expand All @@ -65,8 +73,12 @@ export interface HttpPluginConfig extends PluginConfig {
ignoreIncomingPaths?: IgnoreMatcher[];
/** Not trace all outgoing requests that match urls */
ignoreOutgoingUrls?: IgnoreMatcher[];
/** Function for adding custom attributes */
/** Function for adding custom attributes after response is handled */
applyCustomAttributesOnSpan?: HttpCustomAttributeFunction;
/** Function for adding custom attributes before request is handled */
requestHook?: HttpRequestCustomAttributeFunction;
/** Function for adding custom attributes before response is handled */
responseHook?: HttpResponseCustomAttributeFunction;
/** The primary server name of the matched virtual host. */
serverName?: string;
/** Require parent to create span for outgoing requests */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { DummyPropagation } from '../utils/DummyPropagation';
import { httpRequest } from '../utils/httpRequest';
import { ContextManager } from '@opentelemetry/context-base';
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
import { ClientRequest, IncomingMessage, ServerResponse } from 'http';

const applyCustomAttributesOnSpanErrorMessage =
'bad applyCustomAttributesOnSpan function';
Expand Down Expand Up @@ -76,6 +77,20 @@ export const customAttributeFunction = (span: ISpan): void => {
span.setAttribute('span kind', SpanKind.CLIENT);
};

export const requestHookFunction = (
span: ISpan,
request: ClientRequest | IncomingMessage
): void => {
span.setAttribute('custom request hook attribute', 'request');
};

export const responseHookFunction = (
span: ISpan,
response: IncomingMessage | ServerResponse
): void => {
span.setAttribute('custom response hook attribute', 'response');
};

describe('HttpPlugin', () => {
let contextManager: ContextManager;

Expand Down Expand Up @@ -207,6 +222,8 @@ describe('HttpPlugin', () => {
(url: string) => url.endsWith(`/ignored/function`),
],
applyCustomAttributesOnSpan: customAttributeFunction,
requestHook: requestHookFunction,
responseHook: responseHookFunction,
serverName,
};
plugin.enable(http, provider, provider.logger, config);
Expand Down Expand Up @@ -703,6 +720,40 @@ describe('HttpPlugin', () => {
});
req.end();
});

it('custom attributes should show up on client and server spans', async () => {
await httpRequest.get(
`${protocol}://${hostname}:${serverPort}${pathname}`
);
const spans = memoryExporter.getFinishedSpans();
const [incomingSpan, outgoingSpan] = spans;

assert.strictEqual(
incomingSpan.attributes['custom request hook attribute'],
'request'
);
assert.strictEqual(
incomingSpan.attributes['custom response hook attribute'],
'response'
);
assert.strictEqual(
incomingSpan.attributes['span kind'],
SpanKind.CLIENT
);

assert.strictEqual(
outgoingSpan.attributes['custom request hook attribute'],
'request'
);
assert.strictEqual(
outgoingSpan.attributes['custom response hook attribute'],
'response'
);
assert.strictEqual(
outgoingSpan.attributes['span kind'],
SpanKind.CLIENT
);
});
});

describe('with require parent span', () => {
Expand Down