fix(instrumentation-http): report error.type metrics attribute (#5647)

This commit is contained in:
Raphaël Thériault 2025-08-06 11:46:29 -04:00 committed by GitHub
parent 336aff9586
commit bbf9dacdfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 141 additions and 37 deletions

View File

@ -68,6 +68,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
* fix(otlp-transformer): do not throw when deserializing empty JSON response [#5551](https://github.com/open-telemetry/opentelemetry-js/pull/5551) @pichlermarc
* fix(instrumentation-http): report stable client metrics response code [#9586](https://github.com/open-telemetry/opentelemetry-js/pull/9586) @jtescher
* fix(sdk-node): instantiate baggage processor when env var is set [#5634](https://github.com/open-telemetry/opentelemetry-js/pull/5634) @pichlermarc
* fix(instrumentation-http): report `error.type` metrics attribute [#5647](https://github.com/open-telemetry/opentelemetry-js/pull/5647)
### :house: Internal

View File

@ -53,6 +53,7 @@ import {
} from '@opentelemetry/instrumentation';
import { errorMonitor } from 'events';
import {
ATTR_ERROR_TYPE,
ATTR_HTTP_REQUEST_METHOD,
ATTR_HTTP_RESPONSE_STATUS_CODE,
ATTR_NETWORK_PROTOCOL_VERSION,
@ -516,17 +517,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
return;
}
responseFinished = true;
setSpanWithError(span, error, this._semconvStability);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
this._closeHttpSpan(
this._onOutgoingRequestError(
span,
SpanKind.CLIENT,
startTime,
oldMetricAttributes,
stableMetricAttributes
stableMetricAttributes,
startTime,
error
);
});
}
@ -551,14 +547,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
return;
}
responseFinished = true;
setSpanWithError(span, error, this._semconvStability);
this._closeHttpSpan(
this._onOutgoingRequestError(
span,
SpanKind.CLIENT,
startTime,
oldMetricAttributes,
stableMetricAttributes
stableMetricAttributes,
startTime,
error
);
});
@ -705,17 +699,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
() => original.apply(this, [event, ...args]),
error => {
if (error) {
setSpanWithError(
instrumentation._onServerResponseError(
span,
error,
instrumentation._semconvStability
);
instrumentation._closeHttpSpan(
span,
SpanKind.SERVER,
startTime,
oldMetricAttributes,
stableMetricAttributes
stableMetricAttributes,
startTime,
error
);
throw error;
}
@ -851,14 +840,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
},
error => {
if (error) {
setSpanWithError(span, error, instrumentation._semconvStability);
instrumentation._closeHttpSpan(
instrumentation._onOutgoingRequestError(
span,
SpanKind.CLIENT,
startTime,
oldMetricAttributes,
stableMetricAttributes
stableMetricAttributes,
startTime,
error
);
throw error;
}
@ -937,6 +924,25 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
);
}
private _onOutgoingRequestError(
span: Span,
oldMetricAttributes: Attributes,
stableMetricAttributes: Attributes,
startTime: HrTime,
error: Err
) {
setSpanWithError(span, error, this._semconvStability);
stableMetricAttributes[ATTR_ERROR_TYPE] = error.name;
this._closeHttpSpan(
span,
SpanKind.CLIENT,
startTime,
oldMetricAttributes,
stableMetricAttributes
);
}
private _onServerResponseError(
span: Span,
oldMetricAttributes: Attributes,
@ -945,7 +951,8 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
error: Err
) {
setSpanWithError(span, error, this._semconvStability);
// TODO get error attributes for metrics
stableMetricAttributes[ATTR_ERROR_TYPE] = error.name;
this._closeHttpSpan(
span,
SpanKind.SERVER,

View File

@ -21,6 +21,7 @@ import {
} from '@opentelemetry/sdk-metrics';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import {
ATTR_ERROR_TYPE,
ATTR_HTTP_REQUEST_METHOD,
ATTR_HTTP_RESPONSE_STATUS_CODE,
ATTR_HTTP_ROUTE,
@ -196,10 +197,10 @@ describe('metrics', () => {
);
}
await metricReader.collectAndExport();
const resourceMetrics = metricsMemoryExporter.getMetrics();
const scopeMetrics = resourceMetrics[0].scopeMetrics;
let resourceMetrics = metricsMemoryExporter.getMetrics();
let scopeMetrics = resourceMetrics[0].scopeMetrics;
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
const metrics = scopeMetrics[0].metrics;
let metrics = scopeMetrics[0].metrics;
assert.strictEqual(metrics.length, 2, 'metrics count');
assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
assert.strictEqual(
@ -247,6 +248,43 @@ describe('metrics', () => {
[ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
[ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
});
metricsMemoryExporter.reset();
assert.throws(() =>
http.request({
hostname,
port: serverPort,
pathname,
headers: { cookie: undefined },
})
);
await metricReader.collectAndExport();
resourceMetrics = metricsMemoryExporter.getMetrics();
scopeMetrics = resourceMetrics[0].scopeMetrics;
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
metrics = scopeMetrics[0].metrics;
assert.strictEqual(metrics.length, 1, 'metrics count');
assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
assert.strictEqual(
metrics[0].descriptor.description,
'Duration of HTTP client requests.'
);
assert.strictEqual(
metrics[0].descriptor.name,
'http.client.request.duration'
);
assert.strictEqual(metrics[0].descriptor.unit, 's');
assert.strictEqual(metrics[0].dataPoints.length, 1);
assert.strictEqual((metrics[0].dataPoints[0].value as any).count, 1);
assert.deepStrictEqual(metrics[0].dataPoints[0].attributes, {
[ATTR_HTTP_REQUEST_METHOD]: 'GET',
[ATTR_SERVER_ADDRESS]: 'localhost',
[ATTR_SERVER_PORT]: 22346,
[ATTR_ERROR_TYPE]: 'TypeError',
});
});
});
@ -263,10 +301,10 @@ describe('metrics', () => {
);
}
await metricReader.collectAndExport();
const resourceMetrics = metricsMemoryExporter.getMetrics();
const scopeMetrics = resourceMetrics[0].scopeMetrics;
let resourceMetrics = metricsMemoryExporter.getMetrics();
let scopeMetrics = resourceMetrics[0].scopeMetrics;
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
const metrics = scopeMetrics[0].metrics;
let metrics = scopeMetrics[0].metrics;
assert.strictEqual(metrics.length, 4, 'metrics count');
// old metrics
@ -387,6 +425,64 @@ describe('metrics', () => {
[ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
[ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
});
metricsMemoryExporter.reset();
assert.throws(() =>
http.request({
hostname,
port: serverPort,
pathname,
headers: { cookie: undefined },
})
);
await metricReader.collectAndExport();
resourceMetrics = metricsMemoryExporter.getMetrics();
scopeMetrics = resourceMetrics[0].scopeMetrics;
assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
metrics = scopeMetrics[0].metrics;
assert.strictEqual(metrics.length, 2, 'metrics count');
// Old metrics
assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
assert.strictEqual(
metrics[0].descriptor.description,
'Measures the duration of outbound HTTP requests.'
);
assert.strictEqual(metrics[0].descriptor.name, 'http.client.duration');
assert.strictEqual(metrics[0].descriptor.unit, 'ms');
assert.strictEqual(metrics[0].dataPoints.length, 1);
assert.strictEqual((metrics[0].dataPoints[0].value as any).count, 1);
assert.strictEqual(
metrics[0].dataPoints[0].attributes[ATTR_HTTP_METHOD],
'GET'
);
assert.strictEqual(
metrics[0].dataPoints[0].attributes[ATTR_NET_PEER_NAME],
'localhost'
);
// Stable metrics
assert.strictEqual(metrics[1].dataPointType, DataPointType.HISTOGRAM);
assert.strictEqual(
metrics[1].descriptor.description,
'Duration of HTTP client requests.'
);
assert.strictEqual(
metrics[1].descriptor.name,
'http.client.request.duration'
);
assert.strictEqual(metrics[1].descriptor.unit, 's');
assert.strictEqual(metrics[1].dataPoints.length, 1);
assert.strictEqual((metrics[1].dataPoints[0].value as any).count, 1);
assert.deepStrictEqual(metrics[1].dataPoints[0].attributes, {
[ATTR_HTTP_REQUEST_METHOD]: 'GET',
[ATTR_SERVER_ADDRESS]: 'localhost',
[ATTR_SERVER_PORT]: 22346,
[ATTR_ERROR_TYPE]: 'TypeError',
});
});
});
});