feat(instrumentation-xml-http-request): support migration to stable HTTP semconv, v1.23.1 (#5662)

This commit is contained in:
Trent Mick 2025-05-09 15:20:47 -07:00 committed by GitHub
parent 0665c854c8
commit 0c21db4621
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1317 additions and 630 deletions

View File

@ -12,12 +12,14 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
### :rocket: Features
* feat(instrumentation-xml-http-request): support migration to stable HTTP semconv, v1.23.1 [#5662](https://github.com/open-telemetry/opentelemetry-js/pull/5662) @trentm
* Configure the instrumentation with `semconvStabilityOptIn: 'http'` to use the new, stable semconv v1.23.1 semantics or `'http/dup'` for both old (v1.7.0) and stable semantics. When `semconvStabilityOptIn` is not specified (or does not contain these values), it uses the old semconv v1.7.0. I.e. the default behavior is unchanged.
* feat(instrumentation-fetch): support migration to stable HTTP semconv, v1.23.1 [#5651](https://github.com/open-telemetry/opentelemetry-js/pull/5651) @trentm
* Configure the instrumentation with `semconvStabilityOptIn: 'http'` to use the new, stable semconv v1.23.1 semantics or `'http/dup'` for both old (v1.7.0) and stable semantics. When `semconvStabilityOptIn` is not specified (or does not contain these values), it uses the old semconv v1.7.0. I.e. the default behavior is unchanged.
* feat(instrumentation): New utilities for semconv stability migration for instrumentations that produce 'http' and 'db' telemetry. [#5659](https://github.com/open-telemetry/opentelemetry-js/pull/5659) @trentm
* See [semconv stability usage guide](./packages/opentelemetry-instrumentation/src/semconvStability.ts).
* feat(instrumentation-http): capture synthetic source type on requests [#5488](https://github.com/open-telemetry/opentelemetry-js/pull/5488) @JacksonWeber
* feat(instrumentation-grpc): support migration to stable HTTP semconv [#5653](https://github.com/open-telemetry/opentelemetry-js/pull/5653) @JamieDanielson
* feat(instrumentation-http): capture synthetic source type on requests [#5488](https://github.com/open-telemetry/opentelemetry-js/pull/5488) @JacksonWeber
### :bug: Bug Fixes

View File

@ -3,9 +3,9 @@
[![NPM Published Version][npm-img]][npm-url]
[![Apache License][license-image]][license-image]
**Note: This is an experimental package under active development. New releases may include breaking changes.**
**Note: This is an experimental package. New releases may include breaking changes.**
This module provides auto instrumentation for web using XMLHttpRequest .
This module provides auto instrumentation for web using XMLHttpRequest.
## Installation
@ -62,34 +62,48 @@ xmlHttpRequestInstrumentation.setTracerProvider(providerWithZone);
const req = new XMLHttpRequest();
req.open('GET', 'http://localhost:8090/xml-http-request.js', true);
req.send();
```
### XHR Instrumentation options
XHR instrumentation 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/main/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts#L85) | `XHRCustomAttributeFunction` | Function for adding custom attributes |
| [`ignoreNetworkEvents`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts#L87) | `boolean` | Disable network events being added as span events (network events are added by default) |
| [`measureRequestSize`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts#L89) | `boolean` | Measure outgoing request length (outgoing request length is not measured by default) |
| Options | Type | Description |
| ----------------------------- | ---------------------------- | ----------- |
| `applyCustomAttributesOnSpan` | `XHRCustomAttributeFunction` | Function for adding custom attributes |
| `ignoreNetworkEvents` | boolean | Disable network events being added as span events (network events are added by default) |
| `measureRequestSize` | boolean | Measure outgoing request length (outgoing request length is not measured by default) |
| `semconvStabilityOptIn` | string | A comma-separated string of tokens as described for `OTEL_SEMCONV_STABILITY_OPT_IN` in the [HTTP semantic convention stability migration](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md) guide. See the "Semantic Conventions" section below. |
## Semantic Conventions
This package uses `@opentelemetry/semantic-conventions` version `1.22+`, which implements Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md)
Up to and including v0.200.0, `instrumentation-xml-http-request` generates telemetry using [Semantic Conventions v1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
Attributes collected:
HTTP semantic conventions (semconv) were stabilized in semconv v1.23.0, and a [migration process](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md#http-semantic-convention-stability-migration) was defined. `instrumentation-xml-http-request` versions 0.201.0 and later include support for migrating to stable HTTP semantic conventions, as described below. The intent is to provide an approximate 6 month time window for users of this instrumentation to migrate to the new HTTP semconv, after which a new minor version will change to use the *new* semconv by default and drop support for the old semconv. See the [HTTP semconv migration plan for OpenTelemetry JS instrumentations](https://github.com/open-telemetry/opentelemetry-js/issues/5646).
| Attribute | Short Description |
| ------------------------------------------- | ------------------------------------------------------------------------------ |
| `http.status_code` | HTTP response status code |
| `http.host` | The value of the HTTP host header |
| `http.user_agent` | Value of the HTTP User-Agent header sent by the client |
| `http.scheme` | The URI scheme identifying the used protocol |
| `http.url` | Full HTTP request URL |
| `http.method` | HTTP request method |
| `http.request_content_length_uncompressed` | Uncompressed size of the request body, if any body exists |
To select which semconv version(s) is emitted from this instrumentation, use the `semconvStabilityOptIn` configuration option. This option works [as described for `OTEL_SEMCONV_STABILITY_OPT_IN`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md):
- `http`: emit the new (stable) v1.23.0 semantics
- `http/dup`: emit **both** the old v1.7.0 and the new (stable) v1.23.0 semantics
- By default, if `semconvStabilityOptIn` includes neither of the above tokens, the old v1.7.0 semconv is used.
**Span status:** When the stable semconv is selected, the [span status](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#status) is set to ERROR when the response status code is `>=400` or when the response fails with an 'error' or 'timeout' XHR event. When just the old semconv is select, the span status is not set.
**Span attributes:**
| v1.7.0 semconv | v1.23.0 semconv | Notes |
| ---------------------- | ---------------------------------- | ----- |
| `http.method` | `http.request.method` | HTTP request method. With v1.23.0 semconv [`http.request.method_original` may also be included](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/http/http-spans.md#common-attributes). |
| `http.url` | `url.full` | Full HTTP request URL |
| `http.host` | `server.address` and `server.port` | The hostname and port of the request URL |
| `http.status_code` | `http.response.status_code` | HTTP response status code |
| `http.request_content_length_uncompressed` | `http.request.body.size` | This is only added if `measureRequestSize` is `true`. |
| `http.response_content_length_uncompressed` | (not included) | Stable HTTP semconv would use `http.response.body.size`, but this is an [`Opt-In` attribute](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/http/http-spans.md#http-client), so would require adding a configuration option to this instrumentation to enable. |
| `http.response_content_length` | (not included) | Stable HTTP semconv would use `http.response.header.<key>`, but this is an [`Opt-In` attribute](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/http/http-spans.md#http-client), so would require adding a configuration option to this instrumentation to enable. |
| (no equivalent) | `error.type` | The response status (as a string), if the response status was `>=400`, or one of these possible request errors: 'timeout' and 'error'.|
| `http.user_agent` | (not included) | Stable HTTP semconv would use `user_agent.original`, but this is an [`Opt-In` attribute](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/http/http-spans.md#http-client), so would require adding a configuration option to this instrumentation to enable. |
| `http.scheme` | (not included) | Stable HTTP semconv would use `url.scheme`, but this is an [`Opt-In` attribute](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/http/http-spans.md#http-client), so would require adding a configuration option to this instrumentation to enable. |
| `http.status_text` | (not included) | This is no longer a documented semantic conventions attribute. |
## Example Screenshots

View File

@ -0,0 +1,124 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This file contains a copy of unstable semantic convention definitions
* used by this package.
* @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv
*/
/**
* Deprecated, use one of `server.address`, `client.address` or `http.request.header.host` instead, depending on the usage.
*
* @example www.example.org
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by one of `server.address`, `client.address` or `http.request.header.host`, depending on the usage.
*/
export const ATTR_HTTP_HOST = 'http.host' as const;
/**
* Deprecated, use `http.request.method` instead.
*
* @example GET
* @example POST
* @example HEAD
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by `http.request.method`.
*/
export const ATTR_HTTP_METHOD = 'http.method' as const;
/**
* The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size.
*
* @example 3495
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*/
export const ATTR_HTTP_REQUEST_BODY_SIZE = 'http.request.body.size' as const;
/**
* Deprecated, use `http.request.body.size` instead.
*
* @example 5493
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by `http.request.body.size`.
*/
export const ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED =
'http.request_content_length_uncompressed' as const;
/**
* Deprecated, use `http.response.header.<key>` instead.
*
* @example 3495
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by `http.response.header.<key>`.
*/
export const ATTR_HTTP_RESPONSE_CONTENT_LENGTH =
'http.response_content_length' as const;
/**
* Deprecated, use `url.scheme` instead.
*
* @example http
* @example https
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by `url.scheme` instead.
*/
export const ATTR_HTTP_SCHEME = 'http.scheme' as const;
/**
* Deprecated, use `http.response.status_code` instead.
*
* @example 200
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by `http.response.status_code`.
*/
export const ATTR_HTTP_STATUS_CODE = 'http.status_code' as const;
/**
* Deprecated, use `url.full` instead.
*
* @example https://www.foo.bar/search?q=OpenTelemetry#SemConv
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by `url.full`.
*/
export const ATTR_HTTP_URL = 'http.url' as const;
/**
* Deprecated, use `user_agent.original` instead.
*
* @example CERN-LineMode/2.15 libwww/2.17b3
* @example Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1
*
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*
* @deprecated Replaced by `user_agent.original`.
*/
export const ATTR_HTTP_USER_AGENT = 'http.user_agent' as const;

View File

@ -18,6 +18,8 @@
// These may be unified in the future.
import * as api from '@opentelemetry/api';
import { getStringListFromEnv } from '@opentelemetry/core';
import { URLLike } from '@opentelemetry/sdk-trace-web';
const DIAG_LOGGER = api.diag.createComponentLogger({
namespace:
@ -83,3 +85,60 @@ function getFormDataSize(formData: FormData): number {
}
return size;
}
/**
* Normalize an HTTP request method string per `http.request.method` spec
* https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client-span
*/
export function normalizeHttpRequestMethod(method: string): string {
const knownMethods = getKnownMethods();
const methUpper = method.toUpperCase();
if (methUpper in knownMethods) {
return methUpper;
} else {
return '_OTHER';
}
}
const DEFAULT_KNOWN_METHODS = {
CONNECT: true,
DELETE: true,
GET: true,
HEAD: true,
OPTIONS: true,
PATCH: true,
POST: true,
PUT: true,
TRACE: true,
};
let knownMethods: { [key: string]: boolean };
function getKnownMethods() {
if (knownMethods === undefined) {
const cfgMethods = getStringListFromEnv(
'OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS'
);
if (cfgMethods && cfgMethods.length > 0) {
knownMethods = {};
cfgMethods.forEach(m => {
knownMethods[m] = true;
});
} else {
knownMethods = DEFAULT_KNOWN_METHODS;
}
}
return knownMethods;
}
const HTTP_PORT_FROM_PROTOCOL: { [key: string]: string } = {
'https:': '443',
'http:': '80',
};
export function serverPortFromUrl(url: URLLike): number | undefined {
const serverPort = Number(url.port || HTTP_PORT_FROM_PROTOCOL[url.protocol]);
// Guard with `if (serverPort)` because `Number('') === 0`.
if (serverPort && !isNaN(serverPort)) {
return serverPort;
} else {
return undefined;
}
}

View File

@ -16,21 +16,14 @@
import * as api from '@opentelemetry/api';
import {
SemconvStability,
httpSemconvStabilityFromStr,
isWrapped,
InstrumentationBase,
InstrumentationConfig,
safeExecuteInTheMiddle,
} from '@opentelemetry/instrumentation';
import { hrTime, isUrlIgnored, otperformance } from '@opentelemetry/core';
import {
SEMATTRS_HTTP_HOST,
SEMATTRS_HTTP_METHOD,
SEMATTRS_HTTP_SCHEME,
SEMATTRS_HTTP_STATUS_CODE,
SEMATTRS_HTTP_URL,
SEMATTRS_HTTP_USER_AGENT,
SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
} from '@opentelemetry/semantic-conventions';
import {
addSpanNetworkEvents,
getResource,
@ -38,6 +31,25 @@ import {
shouldPropagateTraceHeaders,
parseUrl,
} from '@opentelemetry/sdk-trace-web';
import {
ATTR_ERROR_TYPE,
ATTR_HTTP_REQUEST_METHOD,
ATTR_HTTP_REQUEST_METHOD_ORIGINAL,
ATTR_HTTP_RESPONSE_STATUS_CODE,
ATTR_SERVER_ADDRESS,
ATTR_SERVER_PORT,
ATTR_URL_FULL,
} from '@opentelemetry/semantic-conventions';
import {
ATTR_HTTP_HOST,
ATTR_HTTP_METHOD,
ATTR_HTTP_SCHEME,
ATTR_HTTP_STATUS_CODE,
ATTR_HTTP_URL,
ATTR_HTTP_USER_AGENT,
ATTR_HTTP_REQUEST_BODY_SIZE,
ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
} from './semconv';
import { EventNames } from './enums/EventNames';
import {
OpenFunction,
@ -45,7 +57,11 @@ import {
SendFunction,
XhrMem,
} from './types';
import { getXHRBodyLength } from './utils';
import {
normalizeHttpRequestMethod,
serverPortFromUrl,
getXHRBodyLength,
} from './utils';
import { VERSION } from './version';
import { AttributeNames } from './enums/AttributeNames';
@ -87,6 +103,8 @@ export interface XMLHttpRequestInstrumentationConfig
ignoreNetworkEvents?: boolean;
/** Measure outgoing request size */
measureRequestSize?: boolean;
/** Select the HTTP semantic conventions version(s) used. */
semconvStabilityOptIn?: string;
}
/**
@ -100,9 +118,13 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
private _tasksCount = 0;
private _xhrMem = new WeakMap<XMLHttpRequest, XhrMem>();
private _usedResources = new WeakSet<PerformanceResourceTiming>();
private _semconvStability: SemconvStability;
constructor(config: XMLHttpRequestInstrumentationConfig = {}) {
super('@opentelemetry/instrumentation-xml-http-request', VERSION, config);
this._semconvStability = httpSemconvStabilityFromStr(
config?.semconvStabilityOptIn
);
}
init() {}
@ -149,10 +171,15 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
const childSpan = this.tracer.startSpan('CORS Preflight', {
startTime: corsPreFlightRequest[PTN.FETCH_START],
});
const skipOldSemconvContentLengthAttrs = !(
this._semconvStability & SemconvStability.OLD
);
addSpanNetworkEvents(
childSpan,
corsPreFlightRequest,
this.getConfig().ignoreNetworkEvents
this.getConfig().ignoreNetworkEvents,
undefined,
skipOldSemconvContentLengthAttrs
);
childSpan.end(corsPreFlightRequest[PTN.RESPONSE_END]);
});
@ -166,23 +193,33 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
* @private
*/
_addFinalSpanAttributes(span: api.Span, xhrMem: XhrMem, spanUrl?: string) {
if (typeof spanUrl === 'string') {
const parsedUrl = parseUrl(spanUrl);
if (this._semconvStability & SemconvStability.OLD) {
if (xhrMem.status !== undefined) {
span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, xhrMem.status);
span.setAttribute(ATTR_HTTP_STATUS_CODE, xhrMem.status);
}
if (xhrMem.statusText !== undefined) {
span.setAttribute(AttributeNames.HTTP_STATUS_TEXT, xhrMem.statusText);
}
span.setAttribute(SEMATTRS_HTTP_HOST, parsedUrl.host);
span.setAttribute(
SEMATTRS_HTTP_SCHEME,
parsedUrl.protocol.replace(':', '')
);
if (typeof spanUrl === 'string') {
const parsedUrl = parseUrl(spanUrl);
span.setAttribute(ATTR_HTTP_HOST, parsedUrl.host);
span.setAttribute(
ATTR_HTTP_SCHEME,
parsedUrl.protocol.replace(':', '')
);
}
// @TODO do we want to collect this or it will be collected earlier once only or
// maybe when parent span is not available ?
span.setAttribute(SEMATTRS_HTTP_USER_AGENT, navigator.userAgent);
span.setAttribute(ATTR_HTTP_USER_AGENT, navigator.userAgent);
}
if (this._semconvStability & SemconvStability.STABLE) {
if (xhrMem.status) {
// Intentionally exclude status=0, because XHR uses 0 for before a
// response is received and semconv says to only add the attribute if
// received a response.
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, xhrMem.status);
}
}
}
@ -302,10 +339,15 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
this._addChildSpan(span, corsPreFlightRequest);
this._markResourceAsUsed(corsPreFlightRequest);
}
const skipOldSemconvContentLengthAttrs = !(
this._semconvStability & SemconvStability.OLD
);
addSpanNetworkEvents(
span,
mainRequest,
this.getConfig().ignoreNetworkEvents
this.getConfig().ignoreNetworkEvents,
undefined,
skipOldSemconvContentLengthAttrs
);
}
}
@ -343,14 +385,38 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
this._diag.debug('ignoring span as url matches ignored url');
return;
}
const spanName = method.toUpperCase();
const currentSpan = this.tracer.startSpan(spanName, {
let name = '';
const parsedUrl = parseUrl(url);
const attributes = {} as api.Attributes;
if (this._semconvStability & SemconvStability.OLD) {
name = method.toUpperCase();
attributes[ATTR_HTTP_METHOD] = method;
attributes[ATTR_HTTP_URL] = parsedUrl.toString();
}
if (this._semconvStability & SemconvStability.STABLE) {
const origMethod = method;
const normMethod = normalizeHttpRequestMethod(method);
if (!name) {
// The "old" span name wins if emitting both old and stable semconv
// ('http/dup').
name = normMethod;
}
attributes[ATTR_HTTP_REQUEST_METHOD] = normMethod;
if (normMethod !== origMethod) {
attributes[ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = origMethod;
}
attributes[ATTR_URL_FULL] = parsedUrl.toString();
attributes[ATTR_SERVER_ADDRESS] = parsedUrl.hostname;
const serverPort = serverPortFromUrl(parsedUrl);
if (serverPort) {
attributes[ATTR_SERVER_PORT] = serverPort;
}
}
const currentSpan = this.tracer.startSpan(name, {
kind: api.SpanKind.CLIENT,
attributes: {
[SEMATTRS_HTTP_METHOD]: method,
[SEMATTRS_HTTP_URL]: parseUrl(url).toString(),
},
attributes,
});
currentSpan.addEvent(EventNames.METHOD_OPEN);
@ -430,7 +496,12 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
plugin._clearResources();
}
function endSpan(eventName: string, xhr: XMLHttpRequest) {
function endSpan(
eventName: string,
xhr: XMLHttpRequest,
isError: boolean,
errorType?: string
) {
const xhrMem = plugin._xhrMem.get(xhr);
if (!xhrMem) {
return;
@ -440,8 +511,25 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
plugin._xhrMem.delete(xhr);
if (xhrMem.span) {
plugin._applyAttributesAfterXHR(xhrMem.span, xhr);
const span = xhrMem.span;
plugin._applyAttributesAfterXHR(span, xhr);
if (plugin._semconvStability & SemconvStability.STABLE) {
if (isError) {
if (errorType) {
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: errorType,
});
span.setAttribute(ATTR_ERROR_TYPE, errorType);
}
} else if (xhrMem.status && xhrMem.status >= 400) {
span.setStatus({ code: api.SpanStatusCode.ERROR });
span.setAttribute(ATTR_ERROR_TYPE, String(xhrMem.status));
}
}
}
const performanceEndTime = hrTime();
const endTime = Date.now();
@ -454,22 +542,22 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
}
function onError(this: XMLHttpRequest) {
endSpan(EventNames.EVENT_ERROR, this);
endSpan(EventNames.EVENT_ERROR, this, true, 'error');
}
function onAbort(this: XMLHttpRequest) {
endSpan(EventNames.EVENT_ABORT, this);
endSpan(EventNames.EVENT_ABORT, this, false);
}
function onTimeout(this: XMLHttpRequest) {
endSpan(EventNames.EVENT_TIMEOUT, this);
endSpan(EventNames.EVENT_TIMEOUT, this, true, 'timeout');
}
function onLoad(this: XMLHttpRequest) {
if (this.status < 299) {
endSpan(EventNames.EVENT_LOAD, this);
endSpan(EventNames.EVENT_LOAD, this, false);
} else {
endSpan(EventNames.EVENT_ERROR, this);
endSpan(EventNames.EVENT_ERROR, this, false);
}
}
@ -498,10 +586,18 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase<XMLHttpRe
const body = args[0];
const bodyLength = getXHRBodyLength(body);
if (bodyLength !== undefined) {
currentSpan.setAttribute(
SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
bodyLength
);
if (plugin._semconvStability & SemconvStability.OLD) {
currentSpan.setAttribute(
ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
bodyLength
);
}
if (plugin._semconvStability & SemconvStability.STABLE) {
currentSpan.setAttribute(
ATTR_HTTP_REQUEST_BODY_SIZE,
bodyLength
);
}
}
}

View File

@ -15,7 +15,6 @@
*/
import { Span } from '@opentelemetry/api';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH } from '@opentelemetry/semantic-conventions';
import { ReadableSpan, SpanProcessor } from '@opentelemetry/sdk-trace-base';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { XMLHttpRequestInstrumentation } from '../src';
@ -46,7 +45,11 @@ describe('unmocked xhr', () => {
spanProcessors: [testSpans],
});
registerInstrumentations({
instrumentations: [new XMLHttpRequestInstrumentation()],
instrumentations: [
new XMLHttpRequestInstrumentation({
semconvStabilityOptIn: 'http',
}),
],
tracerProvider: provider,
});
});
@ -63,11 +66,12 @@ describe('unmocked xhr', () => {
setTimeout(() => {
assert.strictEqual(testSpans.spans.length, 1);
const span = testSpans.spans[0];
// content length comes from the PerformanceTiming resource; this ensures that our
// matching logic found the right one
assert.ok(
(span.attributes[SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH] as any) > 0
);
// Ensure the PerformanceTiming resource was found and used.
// `fetchStart` is one of its events.
const fetchStartEvent = span?.events.filter(
e => e.name === 'fetchStart'
)[0];
assert.ok(fetchStartEvent);
done();
}, 500);
});