Skip to content

Commit bbf9dac

Browse files
fix(instrumentation-http): report error.type metrics attribute (#5647)
1 parent 336aff9 commit bbf9dac

3 files changed

Lines changed: 141 additions & 37 deletions

File tree

experimental/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
6868
* fix(otlp-transformer): do not throw when deserializing empty JSON response [#5551](https://github.com/open-telemetry/opentelemetry-js/pull/5551) @pichlermarc
6969
* fix(instrumentation-http): report stable client metrics response code [#9586](https://github.com/open-telemetry/opentelemetry-js/pull/9586) @jtescher
7070
* fix(sdk-node): instantiate baggage processor when env var is set [#5634](https://github.com/open-telemetry/opentelemetry-js/pull/5634) @pichlermarc
71+
* fix(instrumentation-http): report `error.type` metrics attribute [#5647](https://github.com/open-telemetry/opentelemetry-js/pull/5647)
7172

7273
### :house: Internal
7374

experimental/packages/opentelemetry-instrumentation-http/src/http.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
} from '@opentelemetry/instrumentation';
5454
import { errorMonitor } from 'events';
5555
import {
56+
ATTR_ERROR_TYPE,
5657
ATTR_HTTP_REQUEST_METHOD,
5758
ATTR_HTTP_RESPONSE_STATUS_CODE,
5859
ATTR_NETWORK_PROTOCOL_VERSION,
@@ -516,17 +517,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
516517
return;
517518
}
518519
responseFinished = true;
519-
setSpanWithError(span, error, this._semconvStability);
520-
span.setStatus({
521-
code: SpanStatusCode.ERROR,
522-
message: error.message,
523-
});
524-
this._closeHttpSpan(
520+
this._onOutgoingRequestError(
525521
span,
526-
SpanKind.CLIENT,
527-
startTime,
528522
oldMetricAttributes,
529-
stableMetricAttributes
523+
stableMetricAttributes,
524+
startTime,
525+
error
530526
);
531527
});
532528
}
@@ -551,14 +547,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
551547
return;
552548
}
553549
responseFinished = true;
554-
setSpanWithError(span, error, this._semconvStability);
555-
556-
this._closeHttpSpan(
550+
this._onOutgoingRequestError(
557551
span,
558-
SpanKind.CLIENT,
559-
startTime,
560552
oldMetricAttributes,
561-
stableMetricAttributes
553+
stableMetricAttributes,
554+
startTime,
555+
error
562556
);
563557
});
564558

@@ -705,17 +699,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
705699
() => original.apply(this, [event, ...args]),
706700
error => {
707701
if (error) {
708-
setSpanWithError(
709-
span,
710-
error,
711-
instrumentation._semconvStability
712-
);
713-
instrumentation._closeHttpSpan(
702+
instrumentation._onServerResponseError(
714703
span,
715-
SpanKind.SERVER,
716-
startTime,
717704
oldMetricAttributes,
718-
stableMetricAttributes
705+
stableMetricAttributes,
706+
startTime,
707+
error
719708
);
720709
throw error;
721710
}
@@ -851,14 +840,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
851840
},
852841
error => {
853842
if (error) {
854-
setSpanWithError(span, error, instrumentation._semconvStability);
855-
856-
instrumentation._closeHttpSpan(
843+
instrumentation._onOutgoingRequestError(
857844
span,
858-
SpanKind.CLIENT,
859-
startTime,
860845
oldMetricAttributes,
861-
stableMetricAttributes
846+
stableMetricAttributes,
847+
startTime,
848+
error
862849
);
863850
throw error;
864851
}
@@ -937,6 +924,25 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
937924
);
938925
}
939926

927+
private _onOutgoingRequestError(
928+
span: Span,
929+
oldMetricAttributes: Attributes,
930+
stableMetricAttributes: Attributes,
931+
startTime: HrTime,
932+
error: Err
933+
) {
934+
setSpanWithError(span, error, this._semconvStability);
935+
stableMetricAttributes[ATTR_ERROR_TYPE] = error.name;
936+
937+
this._closeHttpSpan(
938+
span,
939+
SpanKind.CLIENT,
940+
startTime,
941+
oldMetricAttributes,
942+
stableMetricAttributes
943+
);
944+
}
945+
940946
private _onServerResponseError(
941947
span: Span,
942948
oldMetricAttributes: Attributes,
@@ -945,7 +951,8 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
945951
error: Err
946952
) {
947953
setSpanWithError(span, error, this._semconvStability);
948-
// TODO get error attributes for metrics
954+
stableMetricAttributes[ATTR_ERROR_TYPE] = error.name;
955+
949956
this._closeHttpSpan(
950957
span,
951958
SpanKind.SERVER,

experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-metrics.test.ts

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from '@opentelemetry/sdk-metrics';
2222
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
2323
import {
24+
ATTR_ERROR_TYPE,
2425
ATTR_HTTP_REQUEST_METHOD,
2526
ATTR_HTTP_RESPONSE_STATUS_CODE,
2627
ATTR_HTTP_ROUTE,
@@ -196,10 +197,10 @@ describe('metrics', () => {
196197
);
197198
}
198199
await metricReader.collectAndExport();
199-
const resourceMetrics = metricsMemoryExporter.getMetrics();
200-
const scopeMetrics = resourceMetrics[0].scopeMetrics;
200+
let resourceMetrics = metricsMemoryExporter.getMetrics();
201+
let scopeMetrics = resourceMetrics[0].scopeMetrics;
201202
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
202-
const metrics = scopeMetrics[0].metrics;
203+
let metrics = scopeMetrics[0].metrics;
203204
assert.strictEqual(metrics.length, 2, 'metrics count');
204205
assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
205206
assert.strictEqual(
@@ -247,6 +248,43 @@ describe('metrics', () => {
247248
[ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
248249
[ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
249250
});
251+
252+
metricsMemoryExporter.reset();
253+
254+
assert.throws(() =>
255+
http.request({
256+
hostname,
257+
port: serverPort,
258+
pathname,
259+
headers: { cookie: undefined },
260+
})
261+
);
262+
263+
await metricReader.collectAndExport();
264+
resourceMetrics = metricsMemoryExporter.getMetrics();
265+
scopeMetrics = resourceMetrics[0].scopeMetrics;
266+
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
267+
metrics = scopeMetrics[0].metrics;
268+
assert.strictEqual(metrics.length, 1, 'metrics count');
269+
assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
270+
assert.strictEqual(
271+
metrics[0].descriptor.description,
272+
'Duration of HTTP client requests.'
273+
);
274+
assert.strictEqual(
275+
metrics[0].descriptor.name,
276+
'http.client.request.duration'
277+
);
278+
assert.strictEqual(metrics[0].descriptor.unit, 's');
279+
assert.strictEqual(metrics[0].dataPoints.length, 1);
280+
assert.strictEqual((metrics[0].dataPoints[0].value as any).count, 1);
281+
282+
assert.deepStrictEqual(metrics[0].dataPoints[0].attributes, {
283+
[ATTR_HTTP_REQUEST_METHOD]: 'GET',
284+
[ATTR_SERVER_ADDRESS]: 'localhost',
285+
[ATTR_SERVER_PORT]: 22346,
286+
[ATTR_ERROR_TYPE]: 'TypeError',
287+
});
250288
});
251289
});
252290

@@ -263,10 +301,10 @@ describe('metrics', () => {
263301
);
264302
}
265303
await metricReader.collectAndExport();
266-
const resourceMetrics = metricsMemoryExporter.getMetrics();
267-
const scopeMetrics = resourceMetrics[0].scopeMetrics;
304+
let resourceMetrics = metricsMemoryExporter.getMetrics();
305+
let scopeMetrics = resourceMetrics[0].scopeMetrics;
268306
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
269-
const metrics = scopeMetrics[0].metrics;
307+
let metrics = scopeMetrics[0].metrics;
270308
assert.strictEqual(metrics.length, 4, 'metrics count');
271309

272310
// old metrics
@@ -387,6 +425,64 @@ describe('metrics', () => {
387425
[ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
388426
[ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
389427
});
428+
429+
metricsMemoryExporter.reset();
430+
431+
assert.throws(() =>
432+
http.request({
433+
hostname,
434+
port: serverPort,
435+
pathname,
436+
headers: { cookie: undefined },
437+
})
438+
);
439+
440+
await metricReader.collectAndExport();
441+
resourceMetrics = metricsMemoryExporter.getMetrics();
442+
scopeMetrics = resourceMetrics[0].scopeMetrics;
443+
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
444+
metrics = scopeMetrics[0].metrics;
445+
assert.strictEqual(metrics.length, 2, 'metrics count');
446+
447+
// Old metrics
448+
assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
449+
assert.strictEqual(
450+
metrics[0].descriptor.description,
451+
'Measures the duration of outbound HTTP requests.'
452+
);
453+
assert.strictEqual(metrics[0].descriptor.name, 'http.client.duration');
454+
assert.strictEqual(metrics[0].descriptor.unit, 'ms');
455+
assert.strictEqual(metrics[0].dataPoints.length, 1);
456+
assert.strictEqual((metrics[0].dataPoints[0].value as any).count, 1);
457+
assert.strictEqual(
458+
metrics[0].dataPoints[0].attributes[ATTR_HTTP_METHOD],
459+
'GET'
460+
);
461+
assert.strictEqual(
462+
metrics[0].dataPoints[0].attributes[ATTR_NET_PEER_NAME],
463+
'localhost'
464+
);
465+
466+
// Stable metrics
467+
assert.strictEqual(metrics[1].dataPointType, DataPointType.HISTOGRAM);
468+
assert.strictEqual(
469+
metrics[1].descriptor.description,
470+
'Duration of HTTP client requests.'
471+
);
472+
assert.strictEqual(
473+
metrics[1].descriptor.name,
474+
'http.client.request.duration'
475+
);
476+
assert.strictEqual(metrics[1].descriptor.unit, 's');
477+
assert.strictEqual(metrics[1].dataPoints.length, 1);
478+
assert.strictEqual((metrics[1].dataPoints[0].value as any).count, 1);
479+
480+
assert.deepStrictEqual(metrics[1].dataPoints[0].attributes, {
481+
[ATTR_HTTP_REQUEST_METHOD]: 'GET',
482+
[ATTR_SERVER_ADDRESS]: 'localhost',
483+
[ATTR_SERVER_PORT]: 22346,
484+
[ATTR_ERROR_TYPE]: 'TypeError',
485+
});
390486
});
391487
});
392488
});

0 commit comments

Comments
 (0)