Skip to content

Commit

Permalink
Add HTTP Server and Client duration Metrics in HTTP Node.js Instrumen…
Browse files Browse the repository at this point in the history
…tation (#3149)
  • Loading branch information
hectorhdzg authored Sep 6, 2022
1 parent a8047ba commit 597ea98
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 30 deletions.
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ All notable changes to experimental packages in this project will be documented

### :rocket: (Enhancement)

* feature(instrumentation-http): Add HTTP Server and Client duration Metrics in HTTP Node.js Instrumentation [#3149](https://github.com/open-telemetry/opentelemetry-js/pull/3149) @hectorhdzg
* fix(add-views-to-node-sdk): added the ability to define meter views in `NodeSDK` [#3066](https://github.com/open-telemetry/opentelemetry-js/pull/3124) @weyert
* feature(add-console-metrics-exporter): add ConsoleMetricExporter [#3120](https://github.com/open-telemetry/opentelemetry-js/pull/3120) @weyert
* feature(prometheus-serialiser): export the unit block when unit is set in metric descriptor [#3066](https://github.com/open-telemetry/opentelemetry-js/pull/3041) @weyert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@
"@opentelemetry/api": "^1.0.0"
},
"dependencies": {
"@opentelemetry/api-metrics": "0.32.0",
"@opentelemetry/core": "1.6.0",
"@opentelemetry/instrumentation": "0.32.0",
"@opentelemetry/sdk-metrics": "0.32.0",
"@opentelemetry/semantic-conventions": "1.6.0",
"semver": "^7.3.5"
},
Expand Down
101 changes: 74 additions & 27 deletions experimental/packages/opentelemetry-instrumentation-http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
import {
context,
HrTime,
INVALID_SPAN_CONTEXT,
propagation,
ROOT_CONTEXT,
Expand All @@ -25,7 +26,8 @@ import {
SpanStatusCode,
trace,
} from '@opentelemetry/api';
import { suppressTracing } from '@opentelemetry/core';
import { Histogram, MeterProvider, MetricAttributes, ValueType } from '@opentelemetry/api-metrics';
import { hrTime, hrTimeDuration, hrTimeToMilliseconds, suppressTracing } from '@opentelemetry/core';
import type * as http from 'http';
import type * as https from 'https';
import { Socket } from 'net';
Expand Down Expand Up @@ -58,15 +60,35 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
private readonly _spanNotEnded: WeakSet<Span> = new WeakSet<Span>();
private readonly _version = process.versions.node;
private _headerCapture;
private _httpServerDurationHistogram!: Histogram;
private _httpClientDurationHistogram!: Histogram;

constructor(config?: HttpInstrumentationConfig) {
super(
'@opentelemetry/instrumentation-http',
VERSION,
config
);

this._headerCapture = this._createHeaderCapture();
this._updateMetricInstruments();
}

override setMeterProvider(meterProvider: MeterProvider) {
super.setMeterProvider(meterProvider);
this._updateMetricInstruments();
}

private _updateMetricInstruments() {
this._httpServerDurationHistogram = this.meter.createHistogram('http.server.duration', {
description: 'measures the duration of the inbound HTTP requests',
unit: 'ms',
valueType: ValueType.DOUBLE
});
this._httpClientDurationHistogram = this.meter.createHistogram('http.client.duration', {
description: 'measures the duration of the outbound HTTP requests',
unit: 'ms',
valueType: ValueType.DOUBLE
});
}

private _getConfig(): HttpInstrumentationConfig {
Expand Down Expand Up @@ -272,11 +294,15 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
* @param request The original request object.
* @param options The arguments to the original function.
* @param span representing the current operation
* @param startTime representing the start time of the request to calculate duration in Metric
* @param metricAttributes metric attributes
*/
private _traceClientRequest(
request: http.ClientRequest,
hostname: string,
span: Span
span: Span,
startTime: HrTime,
metricAttributes: MetricAttributes
): http.ClientRequest {
if (this._getConfig().requestHook) {
this._callRequestHook(span, request);
Expand All @@ -294,6 +320,8 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
response,
);
span.setAttributes(responseAttributes);
metricAttributes = Object.assign(metricAttributes, utils.getOutgoingRequestMetricAttributesOnResponse(responseAttributes));

if (this._getConfig().responseHook) {
this._callResponseHook(span, response);
}
Expand Down Expand Up @@ -323,32 +351,32 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
request,
response
),
() => {},
() => { },
true
);
}

this._closeHttpSpan(span);
this._closeHttpSpan(span, SpanKind.CLIENT, startTime, metricAttributes);
});
response.on('error', (error: Err) => {
this._diag.debug('outgoingRequest on error()', error);
utils.setSpanWithError(span, error);
const code = utils.parseResponseStatus(SpanKind.CLIENT, response.statusCode);
span.setStatus({ code, message: error.message });
this._closeHttpSpan(span);
this._closeHttpSpan(span, SpanKind.CLIENT, startTime, metricAttributes);
});
}
);
request.on('close', () => {
this._diag.debug('outgoingRequest on request close()');
if (!request.aborted) {
this._closeHttpSpan(span);
this._closeHttpSpan(span, SpanKind.CLIENT, startTime, metricAttributes);
}
});
request.on('error', (error: Err) => {
this._diag.debug('outgoingRequest on request error()', error);
utils.setSpanWithError(span, error);
this._closeHttpSpan(span);
this._closeHttpSpan(span, SpanKind.CLIENT, startTime, metricAttributes);
});

this._diag.debug('http.ClientRequest return request');
Expand Down Expand Up @@ -404,18 +432,23 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {

const headers = request.headers;

const spanAttributes = utils.getIncomingRequestAttributes(request, {
component: component,
serverName: instrumentation._getConfig().serverName,
hookAttributes: instrumentation._callStartSpanHook(
request,
instrumentation._getConfig().startIncomingSpanHook
),
});

const spanOptions: SpanOptions = {
kind: SpanKind.SERVER,
attributes: utils.getIncomingRequestAttributes(request, {
component: component,
serverName: instrumentation._getConfig().serverName,
hookAttributes: instrumentation._callStartSpanHook(
request,
instrumentation._getConfig().startIncomingSpanHook
),
}),
attributes: spanAttributes,
};

const startTime = hrTime();
let metricAttributes: MetricAttributes = utils.getIncomingRequestMetricAttributes(spanAttributes);

const ctx = propagation.extract(ROOT_CONTEXT, headers);
const span = instrumentation._startHttpSpan(
`${component.toLocaleUpperCase()} ${method}`,
Expand Down Expand Up @@ -456,7 +489,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
error => {
if (error) {
utils.setSpanWithError(span, error);
instrumentation._closeHttpSpan(span);
instrumentation._closeHttpSpan(span, SpanKind.SERVER, startTime, metricAttributes);
throw error;
}
}
Expand All @@ -466,6 +499,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
request,
response
);
metricAttributes = Object.assign(metricAttributes, utils.getIncomingRequestMetricAttributesOnResponse(attributes));

instrumentation._headerCapture.server.captureResponseHeaders(span, header => response.getHeader(header));

Expand All @@ -481,12 +515,12 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
request,
response
),
() => {},
() => { },
true
);
}

instrumentation._closeHttpSpan(span);
instrumentation._closeHttpSpan(span, SpanKind.SERVER, startTime, metricAttributes);
return returned;
};

Expand All @@ -495,7 +529,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
error => {
if (error) {
utils.setSpanWithError(span, error);
instrumentation._closeHttpSpan(span);
instrumentation._closeHttpSpan(span, SpanKind.SERVER, startTime, metricAttributes);
throw error;
}
}
Expand All @@ -520,7 +554,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
}
const extraOptions =
typeof args[0] === 'object' &&
(typeof options === 'string' || options instanceof url.URL)
(typeof options === 'string' || options instanceof url.URL)
? (args.shift() as http.RequestOptions)
: undefined;
const { origin, pathname, method, optionsParsed } = utils.getRequestInfo(
Expand Down Expand Up @@ -572,6 +606,9 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
),
});

const startTime = hrTime();
const metricAttributes: MetricAttributes = utils.getOutgoingRequestMetricAttributes(attributes);

const spanOptions: SpanOptions = {
kind: SpanKind.CLIENT,
attributes,
Expand Down Expand Up @@ -601,7 +638,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
error => {
if (error) {
utils.setSpanWithError(span, error);
instrumentation._closeHttpSpan(span);
instrumentation._closeHttpSpan(span, SpanKind.CLIENT, startTime, metricAttributes);
throw error;
}
}
Expand All @@ -612,7 +649,9 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
return instrumentation._traceClientRequest(
request,
hostname,
span
span,
startTime,
metricAttributes
);
});
};
Expand Down Expand Up @@ -646,13 +685,21 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
return span;
}

private _closeHttpSpan(span: Span) {
private _closeHttpSpan(span: Span, spanKind: SpanKind, startTime: HrTime, metricAttributes: MetricAttributes) {
if (!this._spanNotEnded.has(span)) {
return;
}

span.end();
this._spanNotEnded.delete(span);

// Record metrics
const duration = hrTimeToMilliseconds(hrTimeDuration(startTime, hrTime()));
if (spanKind === SpanKind.SERVER) {
this._httpServerDurationHistogram.record(duration, metricAttributes);
} else if (spanKind === SpanKind.CLIENT) {
this._httpClientDurationHistogram.record(duration, metricAttributes);
}
}

private _callResponseHook(
Expand All @@ -661,7 +708,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
) {
safeExecuteInTheMiddle(
() => this._getConfig().responseHook!(span, response),
() => {},
() => { },
true
);
}
Expand All @@ -672,7 +719,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
) {
safeExecuteInTheMiddle(
() => this._getConfig().requestHook!(span, request),
() => {},
() => { },
true
);
}
Expand All @@ -681,7 +728,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
request: http.IncomingMessage | http.RequestOptions,
hookFunc: Function | undefined,
) {
if(typeof hookFunc === 'function'){
if (typeof hookFunc === 'function') {
return safeExecuteInTheMiddle(
() => hookFunc(request),
() => { },
Expand Down
Loading

0 comments on commit 597ea98

Please sign in to comment.