feat(instrumentation-xml-http-request): support migration to stable HTTP semconv, v1.23.1 (#5662)
This commit is contained in:
parent
0665c854c8
commit
0c21db4621
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue