Files
klz-cables.com/.pnpm-store/v10/files/00/8f4028eb82bbbfb9ca29b0cf00c49406a382a2e5b5736edba6452b78930511153b361b00fa7a6655c88a1dc9ba50d4dd9cdacdd7e21e80d1592a54a02d692b
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

5296 lines
188 KiB
Plaintext

{
if (globalThis.performance === undefined) {
globalThis.performance = {
timeOrigin: 0,
now: () => Date.now()
};
}
}
import { ServerRuntimeClient, applySdkMetadata, setAsyncContextStrategy, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, hasSpansEnabled, spanToTraceContext, getRootSpan, getDynamicSamplingContextFromSpan, getCurrentScope, getCapturedScopesOnSpan, dynamicSamplingContextToSentryBaggageHeader, generateSentryTraceHeader, generateTraceparentHeader, baggageHeaderToDynamicSamplingContext, parseSampleRate, _INTERNAL_safeMathRandom, sampleSpan, debug, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, LRUMap, getClient, shouldPropagateTraceForUrl, parseBaggageHeader, SENTRY_BAGGAGE_KEY_PREFIX, getDynamicSamplingContextFromScope, SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, handleCallbackErrors, getDefaultIsolationScope, getDefaultCurrentScope, _INTERNAL_safeDateNow, debounce, timedEventsToMeasurements, captureEvent, addChildSpanToSpan, setCapturedScopesOnSpan, logSpanStart, logSpanEnd, getIsolationScope, propagationContextFromHeaders, shouldContinueTrace, convertSpanLinksForEnvelope, getStatusMessage, spanTimeInputToSeconds, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME, SPAN_STATUS_OK, SPAN_STATUS_ERROR, parseUrl, getSanitizedUrlString, stripUrlQueryAndFragment, addNonEnumerableProperty, getSpanStatusFromHttpCode, defineIntegration, addFetchInstrumentationHandler, isSentryRequestUrl, instrumentFetchRequest, stringMatchesSomePattern, addBreadcrumb, getBreadcrumbLogLevelFromHttpStatusCode, createTransport, suppressTracing as suppressTracing$2, SENTRY_BUFFER_FULL_ERROR, GLOBAL_OBJ, createStackParser, nodeStackLineParser, dedupeIntegration, inboundFiltersIntegration, functionToStringIntegration, conversationIdIntegration, linkedErrorsIntegration, consoleIntegration, requestDataIntegration, getIntegrationsToSetup, stackParserFromStackParserOptions, addVercelAiProcessors } from '@sentry/core';
export { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, addBreadcrumb, addEventProcessor, addIntegration, captureCheckIn, captureConsoleIntegration, captureEvent, captureException, captureFeedback, captureMessage, close, consoleIntegration, consoleLoggingIntegration, continueTrace, createConsolaReporter, createLangChainCallbackHandler, createTransport, dedupeIntegration, eventFiltersIntegration, extraErrorDataIntegration, featureFlagsIntegration, flush, functionToStringIntegration, getActiveSpan, getClient, getCurrentScope, getGlobalScope, getIsolationScope, getRootSpan, getSpanDescendants, getSpanStatusFromHttpCode, getTraceData, getTraceMetaTags, inboundFiltersIntegration, instrumentAnthropicAiClient, instrumentGoogleGenAIClient, instrumentLangGraph, instrumentOpenAiClient, instrumentSupabaseClient, isEnabled, isInitialized, lastEventId, linkedErrorsIntegration, logger, metrics, moduleMetadataIntegration, requestDataIntegration, rewriteFramesIntegration, setContext, setCurrentClient, setExtra, setExtras, setHttpStatus, setMeasurement, setTag, setTags, setUser, spanToBaggageHeader, spanToJSON, spanToTraceHeader, startInactiveSpan, startNewTrace, startSpan, startSpanManual, supabaseIntegration, suppressTracing, trpcMiddleware, withActiveSpan, withIsolationScope, withMonitor, withScope, wrapMcpServerWithSentry, zodErrorsIntegration } from '@sentry/core';
import * as api from '@opentelemetry/api';
import { createContextKey, baggageEntryMetadataFromString, propagation, diag, SpanStatusCode, trace, isSpanContextValid, TraceFlags, isValidTraceId, context, SpanKind, INVALID_TRACEID, ROOT_CONTEXT, DiagLogLevel } from '@opentelemetry/api';
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
/**
* The Sentry Vercel Edge Runtime SDK Client.
*
* @see VercelEdgeClientOptions for documentation on configuration options.
* @see ServerRuntimeClient for usage documentation.
*/
class VercelEdgeClient extends ServerRuntimeClient {
/**
* Creates a new Vercel Edge Runtime SDK instance.
* @param options Configuration options for this SDK.
*/
constructor(options) {
applySdkMetadata(options, 'vercel-edge');
options._metadata = options._metadata || {};
const clientOptions = {
...options,
platform: 'javascript',
// Use provided runtime or default to 'vercel-edge'
runtime: options.runtime || { name: 'vercel-edge' },
serverName: options.serverName || process.env.SENTRY_NAME,
};
super(clientOptions);
}
// Eslint ignore explanation: This is already documented in super.
// eslint-disable-next-line jsdoc/require-jsdoc
async flush(timeout) {
const provider = this.traceProvider;
await provider?.forceFlush();
if (this.getOptions().sendClientReports) {
this._flushOutcomes();
}
return super.flush(timeout);
}
}
/*
* 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.
*/
const SUPPRESS_TRACING_KEY = createContextKey('OpenTelemetry SDK Context Key SUPPRESS_TRACING');
function suppressTracing$1(context) {
return context.setValue(SUPPRESS_TRACING_KEY, true);
}
function isTracingSuppressed(context) {
return context.getValue(SUPPRESS_TRACING_KEY) === true;
}
/*
* 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.
*/
const BAGGAGE_KEY_PAIR_SEPARATOR = '=';
const BAGGAGE_PROPERTIES_SEPARATOR = ';';
const BAGGAGE_ITEMS_SEPARATOR = ',';
// Name of the http header used to propagate the baggage
const BAGGAGE_HEADER = 'baggage';
// Maximum number of name-value pairs allowed by w3c spec
const BAGGAGE_MAX_NAME_VALUE_PAIRS = 180;
// Maximum number of bytes per a single name-value pair allowed by w3c spec
const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096;
// Maximum total length of all name-value pairs allowed by w3c spec
const BAGGAGE_MAX_TOTAL_LENGTH = 8192;
/*
* 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.
*/
function serializeKeyPairs(keyPairs) {
return keyPairs.reduce((hValue, current) => {
const value = `${hValue}${hValue !== '' ? BAGGAGE_ITEMS_SEPARATOR : ''}${current}`;
return value.length > BAGGAGE_MAX_TOTAL_LENGTH ? hValue : value;
}, '');
}
function getKeyPairs(baggage) {
return baggage.getAllEntries().map(([key, value]) => {
let entry = `${encodeURIComponent(key)}=${encodeURIComponent(value.value)}`;
// include opaque metadata if provided
// NOTE: we intentionally don't URI-encode the metadata - that responsibility falls on the metadata implementation
if (value.metadata !== undefined) {
entry += BAGGAGE_PROPERTIES_SEPARATOR + value.metadata.toString();
}
return entry;
});
}
function parsePairKeyValue(entry) {
if (!entry)
return;
const metadataSeparatorIndex = entry.indexOf(BAGGAGE_PROPERTIES_SEPARATOR);
const keyPairPart = metadataSeparatorIndex === -1
? entry
: entry.substring(0, metadataSeparatorIndex);
const separatorIndex = keyPairPart.indexOf(BAGGAGE_KEY_PAIR_SEPARATOR);
if (separatorIndex <= 0)
return;
const rawKey = keyPairPart.substring(0, separatorIndex).trim();
const rawValue = keyPairPart.substring(separatorIndex + 1).trim();
if (!rawKey || !rawValue)
return;
let key;
let value;
try {
key = decodeURIComponent(rawKey);
value = decodeURIComponent(rawValue);
}
catch {
return;
}
let metadata;
if (metadataSeparatorIndex !== -1 &&
metadataSeparatorIndex < entry.length - 1) {
const metadataString = entry.substring(metadataSeparatorIndex + 1);
metadata = baggageEntryMetadataFromString(metadataString);
}
return { key, value, metadata };
}
/*
* 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.
*/
/**
* Propagates {@link Baggage} through Context format propagation.
*
* Based on the Baggage specification:
* https://w3c.github.io/baggage/
*/
class W3CBaggagePropagator {
inject(context, carrier, setter) {
const baggage = propagation.getBaggage(context);
if (!baggage || isTracingSuppressed(context))
return;
const keyPairs = getKeyPairs(baggage)
.filter((pair) => {
return pair.length <= BAGGAGE_MAX_PER_NAME_VALUE_PAIRS;
})
.slice(0, BAGGAGE_MAX_NAME_VALUE_PAIRS);
const headerValue = serializeKeyPairs(keyPairs);
if (headerValue.length > 0) {
setter.set(carrier, BAGGAGE_HEADER, headerValue);
}
}
extract(context, carrier, getter) {
const headerValue = getter.get(carrier, BAGGAGE_HEADER);
const baggageString = Array.isArray(headerValue)
? headerValue.join(BAGGAGE_ITEMS_SEPARATOR)
: headerValue;
if (!baggageString)
return context;
const baggage = {};
if (baggageString.length === 0) {
return context;
}
const pairs = baggageString.split(BAGGAGE_ITEMS_SEPARATOR);
pairs.forEach(entry => {
const keyPair = parsePairKeyValue(entry);
if (keyPair) {
const baggageEntry = { value: keyPair.value };
if (keyPair.metadata) {
baggageEntry.metadata = keyPair.metadata;
}
baggage[keyPair.key] = baggageEntry;
}
});
if (Object.entries(baggage).length === 0) {
return context;
}
return propagation.setBaggage(context, propagation.createBaggage(baggage));
}
fields() {
return [BAGGAGE_HEADER];
}
}
/*
* 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.
*/
function sanitizeAttributes(attributes) {
const out = {};
if (typeof attributes !== 'object' || attributes == null) {
return out;
}
for (const key in attributes) {
if (!Object.prototype.hasOwnProperty.call(attributes, key)) {
continue;
}
if (!isAttributeKey(key)) {
diag.warn(`Invalid attribute key: ${key}`);
continue;
}
const val = attributes[key];
if (!isAttributeValue(val)) {
diag.warn(`Invalid attribute value set for key: ${key}`);
continue;
}
if (Array.isArray(val)) {
out[key] = val.slice();
}
else {
out[key] = val;
}
}
return out;
}
function isAttributeKey(key) {
return typeof key === 'string' && key !== '';
}
function isAttributeValue(val) {
if (val == null) {
return true;
}
if (Array.isArray(val)) {
return isHomogeneousAttributeValueArray(val);
}
return isValidPrimitiveAttributeValueType(typeof val);
}
function isHomogeneousAttributeValueArray(arr) {
let type;
for (const element of arr) {
// null/undefined elements are allowed
if (element == null)
continue;
const elementType = typeof element;
if (elementType === type) {
continue;
}
if (!type) {
if (isValidPrimitiveAttributeValueType(elementType)) {
type = elementType;
continue;
}
// encountered an invalid primitive
return false;
}
return false;
}
return true;
}
function isValidPrimitiveAttributeValueType(valType) {
switch (valType) {
case 'number':
case 'boolean':
case 'string':
return true;
}
return false;
}
/*
* 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.
*/
/**
* Returns a function that logs an error using the provided logger, or a
* console logger if one was not provided.
*/
function loggingErrorHandler() {
return (ex) => {
diag.error(stringifyException(ex));
};
}
/**
* Converts an exception into a string representation
* @param {Exception} ex
*/
function stringifyException(ex) {
if (typeof ex === 'string') {
return ex;
}
else {
return JSON.stringify(flattenException(ex));
}
}
/**
* Flattens an exception into key-value pairs by traversing the prototype chain
* and coercing values to strings. Duplicate properties will not be overwritten;
* the first insert wins.
*/
function flattenException(ex) {
const result = {};
let current = ex;
while (current !== null) {
Object.getOwnPropertyNames(current).forEach(propertyName => {
if (result[propertyName])
return;
const value = current[propertyName];
if (value) {
result[propertyName] = String(value);
}
});
current = Object.getPrototypeOf(current);
}
return result;
}
/*
* 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.
*/
/** The global error handler delegate */
let delegateHandler = loggingErrorHandler();
/**
* Return the global error handler
* @param {Exception} ex
*/
function globalErrorHandler(ex) {
try {
delegateHandler(ex);
}
catch { } // eslint-disable-line no-empty
}
const inspect = (object) =>
JSON.stringify(object, null, 2);
/*
* 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.
*/
/**
* Retrieves a number from an environment variable.
* - Returns `undefined` if the environment variable is empty, unset, contains only whitespace, or is not a number.
* - Returns a number in all other cases.
*
* @param {string} key - The name of the environment variable to retrieve.
* @returns {number | undefined} - The number value or `undefined`.
*/
function getNumberFromEnv(key) {
const raw = process.env[key];
if (raw == null || raw.trim() === '') {
return undefined;
}
const value = Number(raw);
if (isNaN(value)) {
diag.warn(`Unknown value ${inspect(raw)} for ${key}, expected a number, using defaults`);
return undefined;
}
return value;
}
/**
* Retrieves a string from an environment variable.
* - Returns `undefined` if the environment variable is empty, unset, or contains only whitespace.
*
* @param {string} key - The name of the environment variable to retrieve.
* @returns {string | undefined} - The string value or `undefined`.
*/
function getStringFromEnv(key) {
const raw = process.env[key];
if (raw == null || raw.trim() === '') {
return undefined;
}
return raw;
}
/*
* 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.
*/
const TMP_DB_SYSTEM = 'db.system';
const TMP_DB_STATEMENT = 'db.statement';
const TMP_FAAS_TRIGGER = 'faas.trigger';
const TMP_HTTP_METHOD = 'http.method';
const TMP_HTTP_URL = 'http.url';
const TMP_HTTP_TARGET = 'http.target';
const TMP_HTTP_STATUS_CODE = 'http.status_code';
const TMP_MESSAGING_SYSTEM = 'messaging.system';
const TMP_RPC_SERVICE = 'rpc.service';
const TMP_RPC_GRPC_STATUS_CODE = 'rpc.grpc.status_code';
/**
* An identifier for the database management system (DBMS) product being used. See below for a list of well-known identifiers.
*
* @deprecated Use ATTR_DB_SYSTEM in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_DB_SYSTEM = TMP_DB_SYSTEM;
/**
* The database statement being executed.
*
* Note: The value may be sanitized to exclude sensitive information.
*
* @deprecated Use ATTR_DB_STATEMENT in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_DB_STATEMENT = TMP_DB_STATEMENT;
/**
* Type of the trigger on which the function is executed.
*
* @deprecated Use ATTR_FAAS_TRIGGER in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_FAAS_TRIGGER = TMP_FAAS_TRIGGER;
/**
* HTTP request method.
*
* @deprecated Use ATTR_HTTP_METHOD in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_HTTP_METHOD = TMP_HTTP_METHOD;
/**
* Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`. Usually the fragment is not transmitted over HTTP, but if it is known, it should be included nevertheless.
*
* Note: `http.url` MUST NOT contain credentials passed via URL in form of `https://username:password@www.example.com/`. In such case the attribute&#39;s value should be `https://www.example.com/`.
*
* @deprecated Use ATTR_HTTP_URL in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_HTTP_URL = TMP_HTTP_URL;
/**
* The full request target as passed in a HTTP request line or equivalent.
*
* @deprecated Use ATTR_HTTP_TARGET in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_HTTP_TARGET = TMP_HTTP_TARGET;
/**
* [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).
*
* @deprecated Use ATTR_HTTP_STATUS_CODE in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_HTTP_STATUS_CODE = TMP_HTTP_STATUS_CODE;
/**
* A string identifying the messaging system.
*
* @deprecated Use ATTR_MESSAGING_SYSTEM in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_MESSAGING_SYSTEM = TMP_MESSAGING_SYSTEM;
/**
* The full (logical) name of the service being called, including its package name, if applicable.
*
* Note: This is the logical name of the service from the RPC interface perspective, which can be different from the name of any implementing class. The `code.namespace` attribute may be used to store the latter (despite the attribute name, it may include a class name; e.g., class with method actually executing the call on the server side, RPC client stub class on the client side).
*
* @deprecated Use ATTR_RPC_SERVICE in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_RPC_SERVICE = TMP_RPC_SERVICE;
/**
* The [numeric status code](https://github.com/grpc/grpc/blob/v1.33.2/doc/statuscodes.md) of the gRPC request.
*
* @deprecated Use ATTR_RPC_GRPC_STATUS_CODE in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMATTRS_RPC_GRPC_STATUS_CODE = TMP_RPC_GRPC_STATUS_CODE;
/*
* 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.
*/
const TMP_SERVICE_NAMESPACE = 'service.namespace';
/**
* A namespace for `service.name`.
*
* Note: A string value having a meaning that helps to distinguish a group of services, for example the team name that owns a group of services. `service.name` is expected to be unique within the same namespace. If `service.namespace` is not specified in the Resource then `service.name` is expected to be unique for all services that have no explicit namespace defined (so the empty/unspecified namespace is simply one more valid namespace). Zero-length namespace string is assumed equal to unspecified namespace.
*
* @deprecated Use ATTR_SERVICE_NAMESPACE in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}).
*/
const SEMRESATTRS_SERVICE_NAMESPACE = TMP_SERVICE_NAMESPACE;
/*
* 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.
*/
//----------------------------------------------------------------------------------------------------------
// DO NOT EDIT, this is an Auto-generated file from scripts/semconv/templates/registry/stable/attributes.ts.j2
//----------------------------------------------------------------------------------------------------------
/**
* ASP.NET Core exception middleware handling result.
*
* @example handled
* @example unhandled
*/
/**
* The database management system (DBMS) product as identified by the client instrumentation.
*
* @note The actual DBMS may differ from the one identified by the client. For example, when using PostgreSQL client libraries to connect to a CockroachDB, the `db.system.name` is set to `postgresql` based on the instrumentation's best knowledge.
*/
const ATTR_DB_SYSTEM_NAME = 'db.system.name';
/**
* The exception message.
*
* @example Division by zero
* @example Can't convert 'int' object to str implicitly
*/
const ATTR_EXCEPTION_MESSAGE = 'exception.message';
/**
* A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG.
*
* @example "Exception in thread "main" java.lang.RuntimeException: Test exception\\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at com.example.GenerateTrace.main(GenerateTrace.java:5)\\n"
*/
const ATTR_EXCEPTION_STACKTRACE = 'exception.stacktrace';
/**
* The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it.
*
* @example java.net.ConnectException
* @example OSError
*/
const ATTR_EXCEPTION_TYPE = 'exception.type';
/**
* HTTP request method.
*
* @example GET
* @example POST
* @example HEAD
*
* @note HTTP request method value **SHOULD** be "known" to the instrumentation.
* By default, this convention defines "known" methods as the ones listed in [RFC9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-methods),
* the PATCH method defined in [RFC5789](https://www.rfc-editor.org/rfc/rfc5789.html)
* and the QUERY method defined in [httpbis-safe-method-w-body](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/?include_text=1).
*
* If the HTTP request method is not known to instrumentation, it **MUST** set the `http.request.method` attribute to `_OTHER`.
*
* If the HTTP instrumentation could end up converting valid HTTP request methods to `_OTHER`, then it **MUST** provide a way to override
* the list of known HTTP methods. If this override is done via environment variable, then the environment variable **MUST** be named
* OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS and support a comma-separated list of case-sensitive known HTTP methods
* (this list **MUST** be a full override of the default known method, it is not a list of known methods in addition to the defaults).
*
* HTTP method names are case-sensitive and `http.request.method` attribute value **MUST** match a known HTTP method name exactly.
* Instrumentations for specific web frameworks that consider HTTP methods to be case insensitive, **SHOULD** populate a canonical equivalent.
* Tracing instrumentations that do so, **MUST** also set `http.request.method_original` to the original value.
*/
const ATTR_HTTP_REQUEST_METHOD = 'http.request.method';
/**
* [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).
*
* @example 200
*/
const ATTR_HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code';
/**
* The matched route template for the request. This **MUST** be low-cardinality and include all static path segments, with dynamic path segments represented with placeholders.
*
* @example /users/:userID?
* @example my-controller/my-action/{id?}
*
* @note **MUST NOT** be populated when this is not supported by the HTTP server framework as the route attribute should have low-cardinality and the URI path can NOT substitute it.
* **SHOULD** include the [application root](/docs/http/http-spans.md#http-server-definitions) if there is one.
*
* A static path segment is a part of the route template with a fixed, low-cardinality value. This includes literal strings like `/users/` and placeholders that
* are constrained to a finite, predefined set of values, e.g. `{controller}` or `{action}`.
*
* A dynamic path segment is a placeholder for a value that can have high cardinality and is not constrained to a predefined list like static path segments.
*
* Instrumentations **SHOULD** use routing information provided by the corresponding web framework. They **SHOULD** pick the most precise source of routing information and **MAY**
* support custom route formatting. Instrumentations **SHOULD** document the format and the API used to obtain the route string.
*/
const ATTR_HTTP_ROUTE = 'http.route';
/**
* Logical name of the service.
*
* @example shoppingcart
*
* @note **MUST** be the same for all instances of horizontally scaled services. If the value was not specified, SDKs **MUST** fallback to `unknown_service:` concatenated with [`process.executable.name`](process.md), e.g. `unknown_service:bash`. If `process.executable.name` is not available, the value **MUST** be set to `unknown_service`.
*/
const ATTR_SERVICE_NAME = 'service.name';
/**
* The version string of the service component. The format is not defined by these conventions.
*
* @example 2.0.0
* @example a01dbef8a
*/
const ATTR_SERVICE_VERSION = 'service.version';
/**
* Absolute URL describing a network resource according to [RFC3986](https://www.rfc-editor.org/rfc/rfc3986)
*
* @example https://www.foo.bar/search?q=OpenTelemetry#SemConv
* @example //localhost
*
* @note For network calls, URL usually has `scheme://host[:port][path][?query][#fragment]` format, where the fragment
* is not transmitted over HTTP, but if it is known, it **SHOULD** be included nevertheless.
*
* `url.full` **MUST NOT** contain credentials passed via URL in form of `https://username:password@www.example.com/`.
* In such case username and password **SHOULD** be redacted and attribute's value **SHOULD** be `https://REDACTED:REDACTED@www.example.com/`.
*
* `url.full` **SHOULD** capture the absolute URL when it is available (or can be reconstructed).
*
* Sensitive content provided in `url.full` **SHOULD** be scrubbed when instrumentations can identify it.
*
*
* Query string values for the following keys **SHOULD** be redacted by default and replaced by the
* value `REDACTED`:
*
* - [`AWSAccessKeyId`](https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html#RESTAuthenticationQueryStringAuth)
* - [`Signature`](https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html#RESTAuthenticationQueryStringAuth)
* - [`sig`](https://learn.microsoft.com/azure/storage/common/storage-sas-overview#sas-token)
* - [`X-Goog-Signature`](https://cloud.google.com/storage/docs/access-control/signed-urls)
*
* This list is subject to change over time.
*
* When a query string value is redacted, the query string key **SHOULD** still be preserved, e.g.
* `https://www.example.com/path?color=blue&sig=REDACTED`.
*/
const ATTR_URL_FULL = 'url.full';
/*
* 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.
*/
/**
* @deprecated Use performance directly.
*/
const otperformance = performance;
/*
* 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.
*/
const NANOSECOND_DIGITS = 9;
const NANOSECOND_DIGITS_IN_MILLIS = 6;
const MILLISECONDS_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS_IN_MILLIS);
const SECOND_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS);
/**
* Converts a number of milliseconds from epoch to HrTime([seconds, remainder in nanoseconds]).
* @param epochMillis
*/
function millisToHrTime(epochMillis) {
const epochSeconds = epochMillis / 1000;
// Decimals only.
const seconds = Math.trunc(epochSeconds);
// Round sub-nanosecond accuracy to nanosecond.
const nanos = Math.round((epochMillis % 1000) * MILLISECONDS_TO_NANOSECONDS);
return [seconds, nanos];
}
/**
* Returns an hrtime calculated via performance component.
* @param performanceNow
*/
function hrTime(performanceNow) {
const timeOrigin = millisToHrTime(otperformance.timeOrigin);
const now = millisToHrTime(typeof performanceNow === 'number' ? performanceNow : otperformance.now());
return addHrTimes(timeOrigin, now);
}
/**
* Returns a duration of two hrTime.
* @param startTime
* @param endTime
*/
function hrTimeDuration(startTime, endTime) {
let seconds = endTime[0] - startTime[0];
let nanos = endTime[1] - startTime[1];
// overflow
if (nanos < 0) {
seconds -= 1;
// negate
nanos += SECOND_TO_NANOSECONDS;
}
return [seconds, nanos];
}
/**
* check if time is HrTime
* @param value
*/
function isTimeInputHrTime(value) {
return (Array.isArray(value) &&
value.length === 2 &&
typeof value[0] === 'number' &&
typeof value[1] === 'number');
}
/**
* check if input value is a correct types.TimeInput
* @param value
*/
function isTimeInput(value) {
return (isTimeInputHrTime(value) ||
typeof value === 'number' ||
value instanceof Date);
}
/**
* Given 2 HrTime formatted times, return their sum as an HrTime.
*/
function addHrTimes(time1, time2) {
const out = [time1[0] + time2[0], time1[1] + time2[1]];
// Nanoseconds
if (out[1] >= SECOND_TO_NANOSECONDS) {
out[1] -= SECOND_TO_NANOSECONDS;
out[0] += 1;
}
return out;
}
/*
* 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.
*/
const VALID_KEY_CHAR_RANGE = '[_0-9a-z-*/]';
const VALID_KEY = `[a-z]${VALID_KEY_CHAR_RANGE}{0,255}`;
const VALID_VENDOR_KEY = `[a-z0-9]${VALID_KEY_CHAR_RANGE}{0,240}@[a-z]${VALID_KEY_CHAR_RANGE}{0,13}`;
const VALID_KEY_REGEX = new RegExp(`^(?:${VALID_KEY}|${VALID_VENDOR_KEY})$`);
const VALID_VALUE_BASE_REGEX = /^[ -~]{0,255}[!-~]$/;
const INVALID_VALUE_COMMA_EQUAL_REGEX = /,|=/;
/**
* Key is opaque string up to 256 characters printable. It MUST begin with a
* lowercase letter, and can only contain lowercase letters a-z, digits 0-9,
* underscores _, dashes -, asterisks *, and forward slashes /.
* For multi-tenant vendor scenarios, an at sign (@) can be used to prefix the
* vendor name. Vendors SHOULD set the tenant ID at the beginning of the key.
* see https://www.w3.org/TR/trace-context/#key
*/
function validateKey(key) {
return VALID_KEY_REGEX.test(key);
}
/**
* Value is opaque string up to 256 characters printable ASCII RFC0020
* characters (i.e., the range 0x20 to 0x7E) except comma , and =.
*/
function validateValue(value) {
return (VALID_VALUE_BASE_REGEX.test(value) &&
!INVALID_VALUE_COMMA_EQUAL_REGEX.test(value));
}
/*
* 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.
*/
const MAX_TRACE_STATE_ITEMS = 32;
const MAX_TRACE_STATE_LEN = 512;
const LIST_MEMBERS_SEPARATOR = ',';
const LIST_MEMBER_KEY_VALUE_SPLITTER = '=';
/**
* TraceState must be a class and not a simple object type because of the spec
* requirement (https://www.w3.org/TR/trace-context/#tracestate-field).
*
* Here is the list of allowed mutations:
* - New key-value pair should be added into the beginning of the list
* - The value of any key can be updated. Modified keys MUST be moved to the
* beginning of the list.
*/
class TraceState {
__init() {this._internalState = new Map();}
constructor(rawTraceState) {TraceState.prototype.__init.call(this);
if (rawTraceState)
this._parse(rawTraceState);
}
set(key, value) {
// TODO: Benchmark the different approaches(map vs list) and
// use the faster one.
const traceState = this._clone();
if (traceState._internalState.has(key)) {
traceState._internalState.delete(key);
}
traceState._internalState.set(key, value);
return traceState;
}
unset(key) {
const traceState = this._clone();
traceState._internalState.delete(key);
return traceState;
}
get(key) {
return this._internalState.get(key);
}
serialize() {
return this._keys()
.reduce((agg, key) => {
agg.push(key + LIST_MEMBER_KEY_VALUE_SPLITTER + this.get(key));
return agg;
}, [])
.join(LIST_MEMBERS_SEPARATOR);
}
_parse(rawTraceState) {
if (rawTraceState.length > MAX_TRACE_STATE_LEN)
return;
this._internalState = rawTraceState
.split(LIST_MEMBERS_SEPARATOR)
.reverse() // Store in reverse so new keys (.set(...)) will be placed at the beginning
.reduce((agg, part) => {
const listMember = part.trim(); // Optional Whitespace (OWS) handling
const i = listMember.indexOf(LIST_MEMBER_KEY_VALUE_SPLITTER);
if (i !== -1) {
const key = listMember.slice(0, i);
const value = listMember.slice(i + 1, part.length);
if (validateKey(key) && validateValue(value)) {
agg.set(key, value);
}
}
return agg;
}, new Map());
// Because of the reverse() requirement, trunc must be done after map is created
if (this._internalState.size > MAX_TRACE_STATE_ITEMS) {
this._internalState = new Map(Array.from(this._internalState.entries())
.reverse() // Use reverse same as original tracestate parse chain
.slice(0, MAX_TRACE_STATE_ITEMS));
}
}
_keys() {
return Array.from(this._internalState.keys()).reverse();
}
_clone() {
const traceState = new TraceState();
traceState._internalState = new Map(this._internalState);
return traceState;
}
}
/*
* 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.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* based on lodash in order to support esm builds without esModuleInterop.
* lodash is using MIT License.
**/
const objectTag = '[object Object]';
const nullTag = '[object Null]';
const undefinedTag = '[object Undefined]';
const funcProto = Function.prototype;
const funcToString = funcProto.toString;
const objectCtorString = funcToString.call(Object);
const getPrototypeOf = Object.getPrototypeOf;
const objectProto = Object.prototype;
const hasOwnProperty = objectProto.hasOwnProperty;
const symToStringTag = Symbol ? Symbol.toStringTag : undefined;
const nativeObjectToString = objectProto.toString;
/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
*
* @static
* @memberOf _
* @since 0.8.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
* @example
*
* function Foo() {
* this.a = 1;
* }
*
* _.isPlainObject(new Foo);
* // => false
*
* _.isPlainObject([1, 2, 3]);
* // => false
*
* _.isPlainObject({ 'x': 0, 'y': 0 });
* // => true
*
* _.isPlainObject(Object.create(null));
* // => true
*/
function isPlainObject(value) {
if (!isObjectLike(value) || baseGetTag(value) !== objectTag) {
return false;
}
const proto = getPrototypeOf(value);
if (proto === null) {
return true;
}
const Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return (typeof Ctor == 'function' &&
Ctor instanceof Ctor &&
funcToString.call(Ctor) === objectCtorString);
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return symToStringTag && symToStringTag in Object(value)
? getRawTag(value)
: objectToString(value);
}
/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/
function getRawTag(value) {
const isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
let unmasked = false;
try {
value[symToStringTag] = undefined;
unmasked = true;
}
catch {
// silence
}
const result = nativeObjectToString.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag] = tag;
}
else {
delete value[symToStringTag];
}
}
return result;
}
/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/
function objectToString(value) {
return nativeObjectToString.call(value);
}
/*
* 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.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
const MAX_LEVEL = 20;
/**
* Merges objects together
* @param args - objects / values to be merged
*/
function merge(...args) {
let result = args.shift();
const objects = new WeakMap();
while (args.length > 0) {
result = mergeTwoObjects(result, args.shift(), 0, objects);
}
return result;
}
function takeValue(value) {
if (isArray(value)) {
return value.slice();
}
return value;
}
/**
* Merges two objects
* @param one - first object
* @param two - second object
* @param level - current deep level
* @param objects - objects holder that has been already referenced - to prevent
* cyclic dependency
*/
function mergeTwoObjects(one, two, level = 0, objects) {
let result;
if (level > MAX_LEVEL) {
return undefined;
}
level++;
if (isPrimitive(one) || isPrimitive(two) || isFunction(two)) {
result = takeValue(two);
}
else if (isArray(one)) {
result = one.slice();
if (isArray(two)) {
for (let i = 0, j = two.length; i < j; i++) {
result.push(takeValue(two[i]));
}
}
else if (isObject(two)) {
const keys = Object.keys(two);
for (let i = 0, j = keys.length; i < j; i++) {
const key = keys[i];
result[key] = takeValue(two[key]);
}
}
}
else if (isObject(one)) {
if (isObject(two)) {
if (!shouldMerge(one, two)) {
return two;
}
result = Object.assign({}, one);
const keys = Object.keys(two);
for (let i = 0, j = keys.length; i < j; i++) {
const key = keys[i];
const twoValue = two[key];
if (isPrimitive(twoValue)) {
if (typeof twoValue === 'undefined') {
delete result[key];
}
else {
// result[key] = takeValue(twoValue);
result[key] = twoValue;
}
}
else {
const obj1 = result[key];
const obj2 = twoValue;
if (wasObjectReferenced(one, key, objects) ||
wasObjectReferenced(two, key, objects)) {
delete result[key];
}
else {
if (isObject(obj1) && isObject(obj2)) {
const arr1 = objects.get(obj1) || [];
const arr2 = objects.get(obj2) || [];
arr1.push({ obj: one, key });
arr2.push({ obj: two, key });
objects.set(obj1, arr1);
objects.set(obj2, arr2);
}
result[key] = mergeTwoObjects(result[key], twoValue, level, objects);
}
}
}
}
else {
result = two;
}
}
return result;
}
/**
* Function to check if object has been already reference
* @param obj
* @param key
* @param objects
*/
function wasObjectReferenced(obj, key, objects) {
const arr = objects.get(obj[key]) || [];
for (let i = 0, j = arr.length; i < j; i++) {
const info = arr[i];
if (info.key === key && info.obj === obj) {
return true;
}
}
return false;
}
function isArray(value) {
return Array.isArray(value);
}
function isFunction(value) {
return typeof value === 'function';
}
function isObject(value) {
return (!isPrimitive(value) &&
!isArray(value) &&
!isFunction(value) &&
typeof value === 'object');
}
function isPrimitive(value) {
return (typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean' ||
typeof value === 'undefined' ||
value instanceof Date ||
value instanceof RegExp ||
value === null);
}
function shouldMerge(one, two) {
if (!isPlainObject(one) || !isPlainObject(two)) {
return false;
}
return true;
}
/*
* 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.
*/
// Event name definitions
const ExceptionEventName = 'exception';
/*
* 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 class represents a span.
*/
class SpanImpl {
// Below properties are included to implement ReadableSpan for export
// purposes but are not intended to be written-to directly.
__init() {this.attributes = {};}
__init2() {this.links = [];}
__init3() {this.events = [];}
__init4() {this._droppedAttributesCount = 0;}
__init5() {this._droppedEventsCount = 0;}
__init6() {this._droppedLinksCount = 0;}
__init7() {this.status = {
code: SpanStatusCode.UNSET,
};}
__init8() {this.endTime = [0, 0];}
__init9() {this._ended = false;}
__init10() {this._duration = [-1, -1];}
/**
* Constructs a new SpanImpl instance.
*/
constructor(opts) {SpanImpl.prototype.__init.call(this);SpanImpl.prototype.__init2.call(this);SpanImpl.prototype.__init3.call(this);SpanImpl.prototype.__init4.call(this);SpanImpl.prototype.__init5.call(this);SpanImpl.prototype.__init6.call(this);SpanImpl.prototype.__init7.call(this);SpanImpl.prototype.__init8.call(this);SpanImpl.prototype.__init9.call(this);SpanImpl.prototype.__init10.call(this);
const now = Date.now();
this._spanContext = opts.spanContext;
this._performanceStartTime = otperformance.now();
this._performanceOffset =
now - (this._performanceStartTime + otperformance.timeOrigin);
this._startTimeProvided = opts.startTime != null;
this._spanLimits = opts.spanLimits;
this._attributeValueLengthLimit =
this._spanLimits.attributeValueLengthLimit || 0;
this._spanProcessor = opts.spanProcessor;
this.name = opts.name;
this.parentSpanContext = opts.parentSpanContext;
this.kind = opts.kind;
this.links = opts.links || [];
this.startTime = this._getTime(opts.startTime ?? now);
this.resource = opts.resource;
this.instrumentationScope = opts.scope;
if (opts.attributes != null) {
this.setAttributes(opts.attributes);
}
this._spanProcessor.onStart(this, opts.context);
}
spanContext() {
return this._spanContext;
}
setAttribute(key, value) {
if (value == null || this._isSpanEnded())
return this;
if (key.length === 0) {
diag.warn(`Invalid attribute key: ${key}`);
return this;
}
if (!isAttributeValue(value)) {
diag.warn(`Invalid attribute value set for key: ${key}`);
return this;
}
const { attributeCountLimit } = this._spanLimits;
if (attributeCountLimit !== undefined &&
Object.keys(this.attributes).length >= attributeCountLimit &&
!Object.prototype.hasOwnProperty.call(this.attributes, key)) {
this._droppedAttributesCount++;
return this;
}
this.attributes[key] = this._truncateToSize(value);
return this;
}
setAttributes(attributes) {
for (const [k, v] of Object.entries(attributes)) {
this.setAttribute(k, v);
}
return this;
}
/**
*
* @param name Span Name
* @param [attributesOrStartTime] Span attributes or start time
* if type is {@type TimeInput} and 3rd param is undefined
* @param [timeStamp] Specified time stamp for the event
*/
addEvent(name, attributesOrStartTime, timeStamp) {
if (this._isSpanEnded())
return this;
const { eventCountLimit } = this._spanLimits;
if (eventCountLimit === 0) {
diag.warn('No events allowed.');
this._droppedEventsCount++;
return this;
}
if (eventCountLimit !== undefined &&
this.events.length >= eventCountLimit) {
if (this._droppedEventsCount === 0) {
diag.debug('Dropping extra events.');
}
this.events.shift();
this._droppedEventsCount++;
}
if (isTimeInput(attributesOrStartTime)) {
if (!isTimeInput(timeStamp)) {
timeStamp = attributesOrStartTime;
}
attributesOrStartTime = undefined;
}
const attributes = sanitizeAttributes(attributesOrStartTime);
this.events.push({
name,
attributes,
time: this._getTime(timeStamp),
droppedAttributesCount: 0,
});
return this;
}
addLink(link) {
this.links.push(link);
return this;
}
addLinks(links) {
this.links.push(...links);
return this;
}
setStatus(status) {
if (this._isSpanEnded())
return this;
this.status = { ...status };
// When using try-catch, the caught "error" is of type `any`. When then assigning `any` to `status.message`,
// TypeScript will not error. While this can happen during use of any API, it is more common on Span#setStatus()
// as it's likely used in a catch-block. Therefore, we validate if `status.message` is actually a string, null, or
// undefined to avoid an incorrect type causing issues downstream.
if (this.status.message != null && typeof status.message !== 'string') {
diag.warn(`Dropping invalid status.message of type '${typeof status.message}', expected 'string'`);
delete this.status.message;
}
return this;
}
updateName(name) {
if (this._isSpanEnded())
return this;
this.name = name;
return this;
}
end(endTime) {
if (this._isSpanEnded()) {
diag.error(`${this.name} ${this._spanContext.traceId}-${this._spanContext.spanId} - You can only call end() on a span once.`);
return;
}
this.endTime = this._getTime(endTime);
this._duration = hrTimeDuration(this.startTime, this.endTime);
if (this._duration[0] < 0) {
diag.warn('Inconsistent start and end time, startTime > endTime. Setting span duration to 0ms.', this.startTime, this.endTime);
this.endTime = this.startTime.slice();
this._duration = [0, 0];
}
if (this._droppedEventsCount > 0) {
diag.warn(`Dropped ${this._droppedEventsCount} events because eventCountLimit reached`);
}
if (this._spanProcessor.onEnding) {
this._spanProcessor.onEnding(this);
}
this._ended = true;
this._spanProcessor.onEnd(this);
}
_getTime(inp) {
if (typeof inp === 'number' && inp <= otperformance.now()) {
// must be a performance timestamp
// apply correction and convert to hrtime
return hrTime(inp + this._performanceOffset);
}
if (typeof inp === 'number') {
return millisToHrTime(inp);
}
if (inp instanceof Date) {
return millisToHrTime(inp.getTime());
}
if (isTimeInputHrTime(inp)) {
return inp;
}
if (this._startTimeProvided) {
// if user provided a time for the start manually
// we can't use duration to calculate event/end times
return millisToHrTime(Date.now());
}
const msDuration = otperformance.now() - this._performanceStartTime;
return addHrTimes(this.startTime, millisToHrTime(msDuration));
}
isRecording() {
return this._ended === false;
}
recordException(exception, time) {
const attributes = {};
if (typeof exception === 'string') {
attributes[ATTR_EXCEPTION_MESSAGE] = exception;
}
else if (exception) {
if (exception.code) {
attributes[ATTR_EXCEPTION_TYPE] = exception.code.toString();
}
else if (exception.name) {
attributes[ATTR_EXCEPTION_TYPE] = exception.name;
}
if (exception.message) {
attributes[ATTR_EXCEPTION_MESSAGE] = exception.message;
}
if (exception.stack) {
attributes[ATTR_EXCEPTION_STACKTRACE] = exception.stack;
}
}
// these are minimum requirements from spec
if (attributes[ATTR_EXCEPTION_TYPE] || attributes[ATTR_EXCEPTION_MESSAGE]) {
this.addEvent(ExceptionEventName, attributes, time);
}
else {
diag.warn(`Failed to record an exception ${exception}`);
}
}
get duration() {
return this._duration;
}
get ended() {
return this._ended;
}
get droppedAttributesCount() {
return this._droppedAttributesCount;
}
get droppedEventsCount() {
return this._droppedEventsCount;
}
get droppedLinksCount() {
return this._droppedLinksCount;
}
_isSpanEnded() {
if (this._ended) {
const error = new Error(`Operation attempted on ended Span {traceId: ${this._spanContext.traceId}, spanId: ${this._spanContext.spanId}}`);
diag.warn(`Cannot execute the operation on ended Span {traceId: ${this._spanContext.traceId}, spanId: ${this._spanContext.spanId}}`, error);
}
return this._ended;
}
// Utility function to truncate given value within size
// for value type of string, will truncate to given limit
// for type of non-string, will return same value
_truncateToLimitUtil(value, limit) {
if (value.length <= limit) {
return value;
}
return value.substring(0, limit);
}
/**
* If the given attribute value is of type string and has more characters than given {@code attributeValueLengthLimit} then
* return string with truncated to {@code attributeValueLengthLimit} characters
*
* If the given attribute value is array of strings then
* return new array of strings with each element truncated to {@code attributeValueLengthLimit} characters
*
* Otherwise return same Attribute {@code value}
*
* @param value Attribute value
* @returns truncated attribute value if required, otherwise same value
*/
_truncateToSize(value) {
const limit = this._attributeValueLengthLimit;
// Check limit
if (limit <= 0) {
// Negative values are invalid, so do not truncate
diag.warn(`Attribute value limit must be positive, got ${limit}`);
return value;
}
// String
if (typeof value === 'string') {
return this._truncateToLimitUtil(value, limit);
}
// Array of strings
if (Array.isArray(value)) {
return value.map(val => typeof val === 'string' ? this._truncateToLimitUtil(val, limit) : val);
}
// Other types, no need to apply value length limit
return value;
}
}
/*
* 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.
*/
/**
* A sampling decision that determines how a {@link Span} will be recorded
* and collected.
*/
var SamplingDecision;
(function (SamplingDecision) {
/**
* `Span.isRecording() === false`, span will not be recorded and all events
* and attributes will be dropped.
*/
SamplingDecision[SamplingDecision["NOT_RECORD"] = 0] = "NOT_RECORD";
/**
* `Span.isRecording() === true`, but `Sampled` flag in {@link TraceFlags}
* MUST NOT be set.
*/
SamplingDecision[SamplingDecision["RECORD"] = 1] = "RECORD";
/**
* `Span.isRecording() === true` AND `Sampled` flag in {@link TraceFlags}
* MUST be set.
*/
SamplingDecision[SamplingDecision["RECORD_AND_SAMPLED"] = 2] = "RECORD_AND_SAMPLED";
})(SamplingDecision || (SamplingDecision = {}));
/*
* 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.
*/
/** Sampler that samples no traces. */
class AlwaysOffSampler {
shouldSample() {
return {
decision: SamplingDecision.NOT_RECORD,
};
}
toString() {
return 'AlwaysOffSampler';
}
}
/*
* 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.
*/
/** Sampler that samples all traces. */
class AlwaysOnSampler {
shouldSample() {
return {
decision: SamplingDecision.RECORD_AND_SAMPLED,
};
}
toString() {
return 'AlwaysOnSampler';
}
}
/*
* 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.
*/
/**
* A composite sampler that either respects the parent span's sampling decision
* or delegates to `delegateSampler` for root spans.
*/
class ParentBasedSampler {
constructor(config) {
this._root = config.root;
if (!this._root) {
globalErrorHandler(new Error('ParentBasedSampler must have a root sampler configured'));
this._root = new AlwaysOnSampler();
}
this._remoteParentSampled =
config.remoteParentSampled ?? new AlwaysOnSampler();
this._remoteParentNotSampled =
config.remoteParentNotSampled ?? new AlwaysOffSampler();
this._localParentSampled =
config.localParentSampled ?? new AlwaysOnSampler();
this._localParentNotSampled =
config.localParentNotSampled ?? new AlwaysOffSampler();
}
shouldSample(context, traceId, spanName, spanKind, attributes, links) {
const parentContext = trace.getSpanContext(context);
if (!parentContext || !isSpanContextValid(parentContext)) {
return this._root.shouldSample(context, traceId, spanName, spanKind, attributes, links);
}
if (parentContext.isRemote) {
if (parentContext.traceFlags & TraceFlags.SAMPLED) {
return this._remoteParentSampled.shouldSample(context, traceId, spanName, spanKind, attributes, links);
}
return this._remoteParentNotSampled.shouldSample(context, traceId, spanName, spanKind, attributes, links);
}
if (parentContext.traceFlags & TraceFlags.SAMPLED) {
return this._localParentSampled.shouldSample(context, traceId, spanName, spanKind, attributes, links);
}
return this._localParentNotSampled.shouldSample(context, traceId, spanName, spanKind, attributes, links);
}
toString() {
return `ParentBased{root=${this._root.toString()}, remoteParentSampled=${this._remoteParentSampled.toString()}, remoteParentNotSampled=${this._remoteParentNotSampled.toString()}, localParentSampled=${this._localParentSampled.toString()}, localParentNotSampled=${this._localParentNotSampled.toString()}}`;
}
}
/*
* 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.
*/
/** Sampler that samples a given fraction of traces based of trace id deterministically. */
class TraceIdRatioBasedSampler {
constructor(ratio = 0) {
this._ratio = this._normalize(ratio);
this._upperBound = Math.floor(this._ratio * 0xffffffff);
}
shouldSample(context, traceId) {
return {
decision: isValidTraceId(traceId) && this._accumulate(traceId) < this._upperBound
? SamplingDecision.RECORD_AND_SAMPLED
: SamplingDecision.NOT_RECORD,
};
}
toString() {
return `TraceIdRatioBased{${this._ratio}}`;
}
_normalize(ratio) {
if (typeof ratio !== 'number' || isNaN(ratio))
return 0;
return ratio >= 1 ? 1 : ratio <= 0 ? 0 : ratio;
}
_accumulate(traceId) {
let accumulation = 0;
for (let i = 0; i < traceId.length / 8; i++) {
const pos = i * 8;
const part = parseInt(traceId.slice(pos, pos + 8), 16);
accumulation = (accumulation ^ part) >>> 0;
}
return accumulation;
}
}
/*
* 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.
*/
var TracesSamplerValues;
(function (TracesSamplerValues) {
TracesSamplerValues["AlwaysOff"] = "always_off";
TracesSamplerValues["AlwaysOn"] = "always_on";
TracesSamplerValues["ParentBasedAlwaysOff"] = "parentbased_always_off";
TracesSamplerValues["ParentBasedAlwaysOn"] = "parentbased_always_on";
TracesSamplerValues["ParentBasedTraceIdRatio"] = "parentbased_traceidratio";
TracesSamplerValues["TraceIdRatio"] = "traceidratio";
})(TracesSamplerValues || (TracesSamplerValues = {}));
const DEFAULT_RATIO = 1;
/**
* Load default configuration. For fields with primitive values, any user-provided
* value will override the corresponding default value. For fields with
* non-primitive values (like `spanLimits`), the user-provided value will be
* used to extend the default value.
*/
// object needs to be wrapped in this function and called when needed otherwise
// envs are parsed before tests are ran - causes tests using these envs to fail
function loadDefaultConfig() {
return {
sampler: buildSamplerFromEnv(),
forceFlushTimeoutMillis: 30000,
generalLimits: {
attributeValueLengthLimit: getNumberFromEnv('OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT') ?? Infinity,
attributeCountLimit: getNumberFromEnv('OTEL_ATTRIBUTE_COUNT_LIMIT') ?? 128,
},
spanLimits: {
attributeValueLengthLimit: getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT') ?? Infinity,
attributeCountLimit: getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT') ?? 128,
linkCountLimit: getNumberFromEnv('OTEL_SPAN_LINK_COUNT_LIMIT') ?? 128,
eventCountLimit: getNumberFromEnv('OTEL_SPAN_EVENT_COUNT_LIMIT') ?? 128,
attributePerEventCountLimit: getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_PER_EVENT_COUNT_LIMIT') ?? 128,
attributePerLinkCountLimit: getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_PER_LINK_COUNT_LIMIT') ?? 128,
},
};
}
/**
* Based on environment, builds a sampler, complies with specification.
*/
function buildSamplerFromEnv() {
const sampler = getStringFromEnv('OTEL_TRACES_SAMPLER') ??
TracesSamplerValues.ParentBasedAlwaysOn;
switch (sampler) {
case TracesSamplerValues.AlwaysOn:
return new AlwaysOnSampler();
case TracesSamplerValues.AlwaysOff:
return new AlwaysOffSampler();
case TracesSamplerValues.ParentBasedAlwaysOn:
return new ParentBasedSampler({
root: new AlwaysOnSampler(),
});
case TracesSamplerValues.ParentBasedAlwaysOff:
return new ParentBasedSampler({
root: new AlwaysOffSampler(),
});
case TracesSamplerValues.TraceIdRatio:
return new TraceIdRatioBasedSampler(getSamplerProbabilityFromEnv());
case TracesSamplerValues.ParentBasedTraceIdRatio:
return new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(getSamplerProbabilityFromEnv()),
});
default:
diag.error(`OTEL_TRACES_SAMPLER value "${sampler}" invalid, defaulting to "${TracesSamplerValues.ParentBasedAlwaysOn}".`);
return new ParentBasedSampler({
root: new AlwaysOnSampler(),
});
}
}
function getSamplerProbabilityFromEnv() {
const probability = getNumberFromEnv('OTEL_TRACES_SAMPLER_ARG');
if (probability == null) {
diag.error(`OTEL_TRACES_SAMPLER_ARG is blank, defaulting to ${DEFAULT_RATIO}.`);
return DEFAULT_RATIO;
}
if (probability < 0 || probability > 1) {
diag.error(`OTEL_TRACES_SAMPLER_ARG=${probability} was given, but it is out of range ([0..1]), defaulting to ${DEFAULT_RATIO}.`);
return DEFAULT_RATIO;
}
return probability;
}
/*
* 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.
*/
const DEFAULT_ATTRIBUTE_COUNT_LIMIT = 128;
const DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT = Infinity;
/**
* Function to merge Default configuration (as specified in './config') with
* user provided configurations.
*/
function mergeConfig(userConfig) {
const perInstanceDefaults = {
sampler: buildSamplerFromEnv(),
};
const DEFAULT_CONFIG = loadDefaultConfig();
const target = Object.assign({}, DEFAULT_CONFIG, perInstanceDefaults, userConfig);
target.generalLimits = Object.assign({}, DEFAULT_CONFIG.generalLimits, userConfig.generalLimits || {});
target.spanLimits = Object.assign({}, DEFAULT_CONFIG.spanLimits, userConfig.spanLimits || {});
return target;
}
/**
* When general limits are provided and model specific limits are not,
* configures the model specific limits by using the values from the general ones.
* @param userConfig User provided tracer configuration
*/
function reconfigureLimits(userConfig) {
const spanLimits = Object.assign({}, userConfig.spanLimits);
/**
* Reassign span attribute count limit to use first non null value defined by user or use default value
*/
spanLimits.attributeCountLimit =
userConfig.spanLimits?.attributeCountLimit ??
userConfig.generalLimits?.attributeCountLimit ??
getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT') ??
getNumberFromEnv('OTEL_ATTRIBUTE_COUNT_LIMIT') ??
DEFAULT_ATTRIBUTE_COUNT_LIMIT;
/**
* Reassign span attribute value length limit to use first non null value defined by user or use default value
*/
spanLimits.attributeValueLengthLimit =
userConfig.spanLimits?.attributeValueLengthLimit ??
userConfig.generalLimits?.attributeValueLengthLimit ??
getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT') ??
getNumberFromEnv('OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT') ??
DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT;
return Object.assign({}, userConfig, { spanLimits });
}
/*
* 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.
*/
const SPAN_ID_BYTES = 8;
const TRACE_ID_BYTES = 16;
class RandomIdGenerator {constructor() { RandomIdGenerator.prototype.__init.call(this);RandomIdGenerator.prototype.__init2.call(this); }
/**
* Returns a random 16-byte trace ID formatted/encoded as a 32 lowercase hex
* characters corresponding to 128 bits.
*/
__init() {this.generateTraceId = getIdGenerator(TRACE_ID_BYTES);}
/**
* Returns a random 8-byte span ID formatted/encoded as a 16 lowercase hex
* characters corresponding to 64 bits.
*/
__init2() {this.generateSpanId = getIdGenerator(SPAN_ID_BYTES);}
}
const SHARED_BUFFER = Buffer.allocUnsafe(TRACE_ID_BYTES);
function getIdGenerator(bytes) {
return function generateId() {
for (let i = 0; i < bytes / 4; i++) {
// unsigned right shift drops decimal part of the number
// it is required because if a number between 2**32 and 2**32 - 1 is generated, an out of range error is thrown by writeUInt32BE
SHARED_BUFFER.writeUInt32BE((Math.random() * 2 ** 32) >>> 0, i * 4);
}
// If buffer is all 0, set the last byte to 1 to guarantee a valid w3c id is generated
for (let i = 0; i < bytes; i++) {
if (SHARED_BUFFER[i] > 0) {
break;
}
else if (i === bytes - 1) {
SHARED_BUFFER[bytes - 1] = 1;
}
}
return SHARED_BUFFER.toString('hex', 0, bytes);
};
}
/*
* 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 class represents a basic tracer.
*/
class Tracer {
/**
* Constructs a new Tracer instance.
*/
constructor(instrumentationScope, config, resource, spanProcessor) {
const localConfig = mergeConfig(config);
this._sampler = localConfig.sampler;
this._generalLimits = localConfig.generalLimits;
this._spanLimits = localConfig.spanLimits;
this._idGenerator = config.idGenerator || new RandomIdGenerator();
this._resource = resource;
this._spanProcessor = spanProcessor;
this.instrumentationScope = instrumentationScope;
}
/**
* Starts a new Span or returns the default NoopSpan based on the sampling
* decision.
*/
startSpan(name, options = {}, context = api.context.active()) {
// remove span from context in case a root span is requested via options
if (options.root) {
context = api.trace.deleteSpan(context);
}
const parentSpan = api.trace.getSpan(context);
if (isTracingSuppressed(context)) {
api.diag.debug('Instrumentation suppressed, returning Noop Span');
const nonRecordingSpan = api.trace.wrapSpanContext(api.INVALID_SPAN_CONTEXT);
return nonRecordingSpan;
}
const parentSpanContext = parentSpan?.spanContext();
const spanId = this._idGenerator.generateSpanId();
let validParentSpanContext;
let traceId;
let traceState;
if (!parentSpanContext ||
!api.trace.isSpanContextValid(parentSpanContext)) {
// New root span.
traceId = this._idGenerator.generateTraceId();
}
else {
// New child span.
traceId = parentSpanContext.traceId;
traceState = parentSpanContext.traceState;
validParentSpanContext = parentSpanContext;
}
const spanKind = options.kind ?? api.SpanKind.INTERNAL;
const links = (options.links ?? []).map(link => {
return {
context: link.context,
attributes: sanitizeAttributes(link.attributes),
};
});
const attributes = sanitizeAttributes(options.attributes);
// make sampling decision
const samplingResult = this._sampler.shouldSample(context, traceId, name, spanKind, attributes, links);
traceState = samplingResult.traceState ?? traceState;
const traceFlags = samplingResult.decision === api.SamplingDecision.RECORD_AND_SAMPLED
? api.TraceFlags.SAMPLED
: api.TraceFlags.NONE;
const spanContext = { traceId, spanId, traceFlags, traceState };
if (samplingResult.decision === api.SamplingDecision.NOT_RECORD) {
api.diag.debug('Recording is off, propagating context in a non-recording span');
const nonRecordingSpan = api.trace.wrapSpanContext(spanContext);
return nonRecordingSpan;
}
// Set initial span attributes. The attributes object may have been mutated
// by the sampler, so we sanitize the merged attributes before setting them.
const initAttributes = sanitizeAttributes(Object.assign(attributes, samplingResult.attributes));
const span = new SpanImpl({
resource: this._resource,
scope: this.instrumentationScope,
context,
spanContext,
name,
kind: spanKind,
links,
parentSpanContext: validParentSpanContext,
attributes: initAttributes,
startTime: options.startTime,
spanProcessor: this._spanProcessor,
spanLimits: this._spanLimits,
});
return span;
}
startActiveSpan(name, arg2, arg3, arg4) {
let opts;
let ctx;
let fn;
if (arguments.length < 2) {
return;
}
else if (arguments.length === 2) {
fn = arg2;
}
else if (arguments.length === 3) {
opts = arg2;
fn = arg3;
}
else {
opts = arg2;
ctx = arg3;
fn = arg4;
}
const parentContext = ctx ?? api.context.active();
const span = this.startSpan(name, opts, parentContext);
const contextWithSpanSet = api.trace.setSpan(parentContext, span);
return api.context.with(contextWithSpanSet, fn, undefined, span);
}
/** Returns the active {@link GeneralLimits}. */
getGeneralLimits() {
return this._generalLimits;
}
/** Returns the active {@link SpanLimits}. */
getSpanLimits() {
return this._spanLimits;
}
}
/*
* 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.
*/
/**
* Implementation of the {@link SpanProcessor} that simply forwards all
* received events to a list of {@link SpanProcessor}s.
*/
class MultiSpanProcessor {
constructor(spanProcessors) {
this._spanProcessors = spanProcessors;
}
forceFlush() {
const promises = [];
for (const spanProcessor of this._spanProcessors) {
promises.push(spanProcessor.forceFlush());
}
return new Promise(resolve => {
Promise.all(promises)
.then(() => {
resolve();
})
.catch(error => {
globalErrorHandler(error || new Error('MultiSpanProcessor: forceFlush failed'));
resolve();
});
});
}
onStart(span, context) {
for (const spanProcessor of this._spanProcessors) {
spanProcessor.onStart(span, context);
}
}
onEnding(span) {
for (const spanProcessor of this._spanProcessors) {
if (spanProcessor.onEnding) {
spanProcessor.onEnding(span);
}
}
}
onEnd(span) {
for (const spanProcessor of this._spanProcessors) {
spanProcessor.onEnd(span);
}
}
shutdown() {
const promises = [];
for (const spanProcessor of this._spanProcessors) {
promises.push(spanProcessor.shutdown());
}
return new Promise((resolve, reject) => {
Promise.all(promises).then(() => {
resolve();
}, reject);
});
}
}
/*
* 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.
*/
var ForceFlushState;
(function (ForceFlushState) {
ForceFlushState[ForceFlushState["resolved"] = 0] = "resolved";
ForceFlushState[ForceFlushState["timeout"] = 1] = "timeout";
ForceFlushState[ForceFlushState["error"] = 2] = "error";
ForceFlushState[ForceFlushState["unresolved"] = 3] = "unresolved";
})(ForceFlushState || (ForceFlushState = {}));
/**
* This class represents a basic tracer provider which platform libraries can extend
*/
class BasicTracerProvider {
__init() {this._tracers = new Map();}
constructor(config = {}) {BasicTracerProvider.prototype.__init.call(this);
const mergedConfig = merge({}, loadDefaultConfig(), reconfigureLimits(config));
this._resource = mergedConfig.resource ?? defaultResource();
this._config = Object.assign({}, mergedConfig, {
resource: this._resource,
});
const spanProcessors = [];
if (config.spanProcessors?.length) {
spanProcessors.push(...config.spanProcessors);
}
this._activeSpanProcessor = new MultiSpanProcessor(spanProcessors);
}
getTracer(name, version, options) {
const key = `${name}@${version || ''}:${options?.schemaUrl || ''}`;
if (!this._tracers.has(key)) {
this._tracers.set(key, new Tracer({ name, version, schemaUrl: options?.schemaUrl }, this._config, this._resource, this._activeSpanProcessor));
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this._tracers.get(key);
}
forceFlush() {
const timeout = this._config.forceFlushTimeoutMillis;
const promises = this._activeSpanProcessor['_spanProcessors'].map((spanProcessor) => {
return new Promise(resolve => {
let state;
const timeoutInterval = setTimeout(() => {
resolve(new Error(`Span processor did not completed within timeout period of ${timeout} ms`));
state = ForceFlushState.timeout;
}, timeout);
spanProcessor
.forceFlush()
.then(() => {
clearTimeout(timeoutInterval);
if (state !== ForceFlushState.timeout) {
state = ForceFlushState.resolved;
resolve(state);
}
})
.catch(error => {
clearTimeout(timeoutInterval);
state = ForceFlushState.error;
resolve(error);
});
});
});
return new Promise((resolve, reject) => {
Promise.all(promises)
.then(results => {
const errors = results.filter(result => result !== ForceFlushState.resolved);
if (errors.length > 0) {
reject(errors);
}
else {
resolve();
}
})
.catch(error => reject([error]));
});
}
shutdown() {
return this._activeSpanProcessor.shutdown();
}
}
/** If this attribute is true, it means that the parent is a remote span. */
const SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE = 'sentry.parentIsRemote';
// These are not standardized yet, but used by the graphql instrumentation
const SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION = 'sentry.graphql.operation';
/**
* Get the parent span id from a span.
* In OTel v1, the parent span id is accessed as `parentSpanId`
* In OTel v2, the parent span id is accessed as `spanId` on the `parentSpanContext`
*/
function getParentSpanId(span) {
if ('parentSpanId' in span) {
return span.parentSpanId ;
} else if ('parentSpanContext' in span) {
return (span.parentSpanContext )?.spanId;
}
return undefined;
}
/**
* Check if a given span has attributes.
* This is necessary because the base `Span` type does not have attributes,
* so in places where we are passed a generic span, we need to check if we want to access them.
*/
function spanHasAttributes(
span,
) {
const castSpan = span ;
return !!castSpan.attributes && typeof castSpan.attributes === 'object';
}
/**
* Check if a given span has a kind.
* This is necessary because the base `Span` type does not have a kind,
* so in places where we are passed a generic span, we need to check if we want to access it.
*/
function spanHasKind(span) {
const castSpan = span ;
return typeof castSpan.kind === 'number';
}
/**
* Check if a given span has a status.
* This is necessary because the base `Span` type does not have a status,
* so in places where we are passed a generic span, we need to check if we want to access it.
*/
function spanHasStatus(
span,
) {
const castSpan = span ;
return !!castSpan.status;
}
/**
* Check if a given span has a name.
* This is necessary because the base `Span` type does not have a name,
* so in places where we are passed a generic span, we need to check if we want to access it.
*/
function spanHasName(span) {
const castSpan = span ;
return !!castSpan.name;
}
/**
* Get sanitizied request data from an OTEL span.
*/
function getRequestSpanData(span) {
// The base `Span` type has no `attributes`, so we need to guard here against that
if (!spanHasAttributes(span)) {
return {};
}
// eslint-disable-next-line deprecation/deprecation
const maybeUrlAttribute = (span.attributes[ATTR_URL_FULL] || span.attributes[SEMATTRS_HTTP_URL])
;
const data = {
url: maybeUrlAttribute,
// eslint-disable-next-line deprecation/deprecation
'http.method': (span.attributes[ATTR_HTTP_REQUEST_METHOD] || span.attributes[SEMATTRS_HTTP_METHOD])
,
};
// Default to GET if URL is set but method is not
if (!data['http.method'] && data.url) {
data['http.method'] = 'GET';
}
try {
if (typeof maybeUrlAttribute === 'string') {
const url = parseUrl(maybeUrlAttribute);
data.url = getSanitizedUrlString(url);
if (url.search) {
data['http.query'] = url.search;
}
if (url.hash) {
data['http.fragment'] = url.hash;
}
}
} catch {
// ignore
}
return data;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
/**
* Get the span kind from a span.
* For whatever reason, this is not public API on the generic "Span" type,
* so we need to check if we actually have a `SDKTraceBaseSpan` where we can fetch this from.
* Otherwise, we fall back to `SpanKind.INTERNAL`.
*/
function getSpanKind(span) {
if (spanHasKind(span)) {
return span.kind;
}
return SpanKind.INTERNAL;
}
const SENTRY_TRACE_HEADER = 'sentry-trace';
const SENTRY_BAGGAGE_HEADER = 'baggage';
const SENTRY_TRACE_STATE_DSC = 'sentry.dsc';
const SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING = 'sentry.sampled_not_recording';
const SENTRY_TRACE_STATE_URL = 'sentry.url';
const SENTRY_TRACE_STATE_SAMPLE_RAND = 'sentry.sample_rand';
const SENTRY_TRACE_STATE_SAMPLE_RATE = 'sentry.sample_rate';
const SENTRY_SCOPES_CONTEXT_KEY = createContextKey('sentry_scopes');
const SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_isolation_scope');
const SENTRY_FORK_SET_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_scope');
const SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_isolation_scope');
const SCOPE_CONTEXT_FIELD = '_scopeContext';
/**
* Try to get the current scopes from the given OTEL context.
* This requires a Context Manager that was wrapped with getWrappedContextManager.
*/
function getScopesFromContext(context) {
return context.getValue(SENTRY_SCOPES_CONTEXT_KEY) ;
}
/**
* Set the current scopes on an OTEL context.
* This will return a forked context with the Propagation Context set.
*/
function setScopesOnContext(context, scopes) {
return context.setValue(SENTRY_SCOPES_CONTEXT_KEY, scopes);
}
/**
* Set the context on the scope so we can later look it up.
* We need this to get the context from the scope in the `trace` functions.
*/
function setContextOnScope(scope, context) {
addNonEnumerableProperty(scope, SCOPE_CONTEXT_FIELD, context);
}
/**
* Get the context related to a scope.
*/
function getContextFromScope(scope) {
return (scope )[SCOPE_CONTEXT_FIELD];
}
/**
* OpenTelemetry only knows about SAMPLED or NONE decision,
* but for us it is important to differentiate between unset and unsampled.
*
* Both of these are identified as `traceFlags === TracegFlags.NONE`,
* but we additionally look at a special trace state to differentiate between them.
*/
function getSamplingDecision(spanContext) {
const { traceFlags, traceState } = spanContext;
const sampledNotRecording = traceState ? traceState.get(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING) === '1' : false;
// If trace flag is `SAMPLED`, we interpret this as sampled
// If it is `NONE`, it could mean either it was sampled to be not recorder, or that it was not sampled at all
// For us this is an important difference, sow e look at the SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING
// to identify which it is
if (traceFlags === TraceFlags.SAMPLED) {
return true;
}
if (sampledNotRecording) {
return false;
}
// Fall back to DSC as a last resort, that may also contain `sampled`...
const dscString = traceState ? traceState.get(SENTRY_TRACE_STATE_DSC) : undefined;
const dsc = dscString ? baggageHeaderToDynamicSamplingContext(dscString) : undefined;
if (dsc?.sampled === 'true') {
return true;
}
if (dsc?.sampled === 'false') {
return false;
}
return undefined;
}
/**
* Infer the op & description for a set of name, attributes and kind of a span.
*/
function inferSpanData(spanName, attributes, kind) {
// if http.method exists, this is an http request span
// eslint-disable-next-line deprecation/deprecation
const httpMethod = attributes[ATTR_HTTP_REQUEST_METHOD] || attributes[SEMATTRS_HTTP_METHOD];
if (httpMethod) {
return descriptionForHttpMethod({ attributes, name: spanName, kind }, httpMethod);
}
// eslint-disable-next-line deprecation/deprecation
const dbSystem = attributes[ATTR_DB_SYSTEM_NAME] || attributes[SEMATTRS_DB_SYSTEM];
const opIsCache =
typeof attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'string' &&
attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP].startsWith('cache.');
// If db.type exists then this is a database call span
// If the Redis DB is used as a cache, the span description should not be changed
if (dbSystem && !opIsCache) {
return descriptionForDbSystem({ attributes, name: spanName });
}
const customSourceOrRoute = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom' ? 'custom' : 'route';
// If rpc.service exists then this is a rpc call span.
// eslint-disable-next-line deprecation/deprecation
const rpcService = attributes[SEMATTRS_RPC_SERVICE];
if (rpcService) {
return {
...getUserUpdatedNameAndSource(spanName, attributes, 'route'),
op: 'rpc',
};
}
// If messaging.system exists then this is a messaging system span.
// eslint-disable-next-line deprecation/deprecation
const messagingSystem = attributes[SEMATTRS_MESSAGING_SYSTEM];
if (messagingSystem) {
return {
...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute),
op: 'message',
};
}
// If faas.trigger exists then this is a function as a service span.
// eslint-disable-next-line deprecation/deprecation
const faasTrigger = attributes[SEMATTRS_FAAS_TRIGGER];
if (faasTrigger) {
return {
...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute),
op: faasTrigger.toString(),
};
}
return { op: undefined, description: spanName, source: 'custom' };
}
/**
* Extract better op/description from an otel span.
*
* Does not overwrite the span name if the source is already set to custom to ensure
* that user-updated span names are preserved. In this case, we only adjust the op but
* leave span description and source unchanged.
*
* Based on https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/7422ce2a06337f68a59b552b8c5a2ac125d6bae5/exporter/sentryexporter/sentry_exporter.go#L306
*/
function parseSpanDescription(span) {
const attributes = spanHasAttributes(span) ? span.attributes : {};
const name = spanHasName(span) ? span.name : '<unknown>';
const kind = getSpanKind(span);
return inferSpanData(name, attributes, kind);
}
function descriptionForDbSystem({ attributes, name }) {
// if we already have a custom name, we don't overwrite it but only set the op
const userDefinedName = attributes[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
if (typeof userDefinedName === 'string') {
return {
op: 'db',
description: userDefinedName,
source: (attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] ) || 'custom',
};
}
// if we already have the source set to custom, we don't overwrite the span description but only set the op
if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom') {
return { op: 'db', description: name, source: 'custom' };
}
// Use DB statement (Ex "SELECT * FROM table") if possible as description.
// eslint-disable-next-line deprecation/deprecation
const statement = attributes[SEMATTRS_DB_STATEMENT];
const description = statement ? statement.toString() : name;
return { op: 'db', description, source: 'task' };
}
/** Only exported for tests. */
function descriptionForHttpMethod(
{ name, kind, attributes },
httpMethod,
) {
const opParts = ['http'];
switch (kind) {
case SpanKind.CLIENT:
opParts.push('client');
break;
case SpanKind.SERVER:
opParts.push('server');
break;
}
// Spans for HTTP requests we have determined to be prefetch requests will have a `.prefetch` postfix in the op
if (attributes['sentry.http.prefetch']) {
opParts.push('prefetch');
}
const { urlPath, url, query, fragment, hasRoute } = getSanitizedUrl(attributes, kind);
if (!urlPath) {
return { ...getUserUpdatedNameAndSource(name, attributes), op: opParts.join('.') };
}
const graphqlOperationsAttribute = attributes[SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION];
// Ex. GET /api/users
const baseDescription = `${httpMethod} ${urlPath}`;
// When the http span has a graphql operation, append it to the description
// We add these in the graphqlIntegration
const inferredDescription = graphqlOperationsAttribute
? `${baseDescription} (${getGraphqlOperationNamesFromAttribute(graphqlOperationsAttribute)})`
: baseDescription;
// If `httpPath` is a root path, then we can categorize the transaction source as route.
const inferredSource = hasRoute || urlPath === '/' ? 'route' : 'url';
const data = {};
if (url) {
data.url = url;
}
if (query) {
data['http.query'] = query;
}
if (fragment) {
data['http.fragment'] = fragment;
}
// If the span kind is neither client nor server, we use the original name
// this infers that somebody manually started this span, in which case we don't want to overwrite the name
const isClientOrServerKind = kind === SpanKind.CLIENT || kind === SpanKind.SERVER;
// If the span is an auto-span (=it comes from one of our instrumentations),
// we always want to infer the name
// this is necessary because some of the auto-instrumentation we use uses kind=INTERNAL
const origin = attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] || 'manual';
const isManualSpan = !`${origin}`.startsWith('auto');
// If users (or in very rare occasions we) set the source to custom, we don't overwrite the name
const alreadyHasCustomSource = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom';
const customSpanName = attributes[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
const useInferredDescription =
!alreadyHasCustomSource && customSpanName == null && (isClientOrServerKind || !isManualSpan);
const { description, source } = useInferredDescription
? { description: inferredDescription, source: inferredSource }
: getUserUpdatedNameAndSource(name, attributes);
return {
op: opParts.join('.'),
description,
source,
data,
};
}
function getGraphqlOperationNamesFromAttribute(attr) {
if (Array.isArray(attr)) {
const sorted = attr.slice().sort();
// Up to 5 items, we just add all of them
if (sorted.length <= 5) {
return sorted.join(', ');
} else {
// Else, we add the first 5 and the diff of other operations
return `${sorted.slice(0, 5).join(', ')}, +${sorted.length - 5}`;
}
}
return `${attr}`;
}
/** Exported for tests only */
function getSanitizedUrl(
attributes,
kind,
)
{
// This is the relative path of the URL, e.g. /sub
// eslint-disable-next-line deprecation/deprecation
const httpTarget = attributes[SEMATTRS_HTTP_TARGET];
// This is the full URL, including host & query params etc., e.g. https://example.com/sub?foo=bar
// eslint-disable-next-line deprecation/deprecation
const httpUrl = attributes[SEMATTRS_HTTP_URL] || attributes[ATTR_URL_FULL];
// This is the normalized route name - may not always be available!
const httpRoute = attributes[ATTR_HTTP_ROUTE];
const parsedUrl = typeof httpUrl === 'string' ? parseUrl(httpUrl) : undefined;
const url = parsedUrl ? getSanitizedUrlString(parsedUrl) : undefined;
const query = parsedUrl?.search || undefined;
const fragment = parsedUrl?.hash || undefined;
if (typeof httpRoute === 'string') {
return { urlPath: httpRoute, url, query, fragment, hasRoute: true };
}
if (kind === SpanKind.SERVER && typeof httpTarget === 'string') {
return { urlPath: stripUrlQueryAndFragment(httpTarget), url, query, fragment, hasRoute: false };
}
if (parsedUrl) {
return { urlPath: url, url, query, fragment, hasRoute: false };
}
// fall back to target even for client spans, if no URL is present
if (typeof httpTarget === 'string') {
return { urlPath: stripUrlQueryAndFragment(httpTarget), url, query, fragment, hasRoute: false };
}
return { urlPath: undefined, url, query, fragment, hasRoute: false };
}
/**
* Because Otel instrumentation sometimes mutates span names via `span.updateName`, the only way
* to ensure that a user-set span name is preserved is to store it as a tmp attribute on the span.
* We delete this attribute once we're done with it when preparing the event envelope.
*
* This temp attribute always takes precedence over the original name.
*
* We also need to take care of setting the correct source. Users can always update the source
* after updating the name, so we need to respect that.
*
* @internal exported only for testing
*/
function getUserUpdatedNameAndSource(
originalName,
attributes,
fallbackSource = 'custom',
)
{
const source = (attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] ) || fallbackSource;
const description = attributes[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
if (description && typeof description === 'string') {
return {
description,
source,
};
}
return { description: originalName, source };
}
/**
* Setup a DSC handler on the passed client,
* ensuring that the transaction name is inferred from the span correctly.
*/
function enhanceDscWithOpenTelemetryRootSpanName(client) {
client.on('createDsc', (dsc, rootSpan) => {
if (!rootSpan) {
return;
}
// We want to overwrite the transaction on the DSC that is created by default in core
// The reason for this is that we want to infer the span name, not use the initial one
// Otherwise, we'll get names like "GET" instead of e.g. "GET /foo"
// `parseSpanDescription` takes the attributes of the span into account for the name
// This mutates the passed-in DSC
const jsonSpan = spanToJSON(rootSpan);
const attributes = jsonSpan.data;
const source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
const { description } = spanHasName(rootSpan) ? parseSpanDescription(rootSpan) : { description: undefined };
if (source !== 'url' && description) {
dsc.transaction = description;
}
// Also ensure sampling decision is correctly inferred
// In core, we use `spanIsSampled`, which just looks at the trace flags
// but in OTEL, we use a slightly more complex logic to be able to differntiate between unsampled and deferred sampling
if (hasSpansEnabled()) {
const sampled = getSamplingDecision(rootSpan.spanContext());
dsc.sampled = sampled == undefined ? undefined : String(sampled);
}
});
}
/**
* Returns the currently active span.
*/
function getActiveSpan() {
return trace.getActiveSpan();
}
/**
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
*
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
*/
const DEBUG_BUILD$1 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
/**
* Generate a TraceState for the given data.
*/
function makeTraceState({
dsc,
sampled,
}
) {
// We store the DSC as OTEL trace state on the span context
const dscString = dsc ? dynamicSamplingContextToSentryBaggageHeader(dsc) : undefined;
const traceStateBase = new TraceState();
const traceStateWithDsc = dscString ? traceStateBase.set(SENTRY_TRACE_STATE_DSC, dscString) : traceStateBase;
// We also specifically want to store if this is sampled to be not recording,
// or unsampled (=could be either sampled or not)
return sampled === false ? traceStateWithDsc.set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1') : traceStateWithDsc;
}
const setupElements = new Set();
/** Get all the OpenTelemetry elements that have been set up. */
function openTelemetrySetupCheck() {
return Array.from(setupElements);
}
/** Mark an OpenTelemetry element as setup. */
function setIsSetup(element) {
setupElements.add(element);
}
/**
* Injects and extracts `sentry-trace` and `baggage` headers from carriers.
*/
class SentryPropagator extends W3CBaggagePropagator {
/** A map of URLs that have already been checked for if they match tracePropagationTargets. */
constructor() {
super();
setIsSetup('SentryPropagator');
// We're caching results so we don't have to recompute regexp every time we create a request.
this._urlMatchesTargetsMap = new LRUMap(100);
}
/**
* @inheritDoc
*/
inject(context, carrier, setter) {
if (isTracingSuppressed(context)) {
DEBUG_BUILD$1 && debug.log('[Tracing] Not injecting trace data for url because tracing is suppressed.');
return;
}
const activeSpan = trace.getSpan(context);
const url = activeSpan && getCurrentURL(activeSpan);
const { tracePropagationTargets, propagateTraceparent } = getClient()?.getOptions() || {};
if (!shouldPropagateTraceForUrl(url, tracePropagationTargets, this._urlMatchesTargetsMap)) {
DEBUG_BUILD$1 &&
debug.log('[Tracing] Not injecting trace data for url because it does not match tracePropagationTargets:', url);
return;
}
const existingBaggageHeader = getExistingBaggage(carrier);
let baggage = propagation.getBaggage(context) || propagation.createBaggage({});
const { dynamicSamplingContext, traceId, spanId, sampled } = getInjectionData(context);
if (existingBaggageHeader) {
const baggageEntries = parseBaggageHeader(existingBaggageHeader);
if (baggageEntries) {
Object.entries(baggageEntries).forEach(([key, value]) => {
baggage = baggage.setEntry(key, { value });
});
}
}
if (dynamicSamplingContext) {
baggage = Object.entries(dynamicSamplingContext).reduce((b, [dscKey, dscValue]) => {
if (dscValue) {
return b.setEntry(`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`, { value: dscValue });
}
return b;
}, baggage);
}
// We also want to avoid setting the default OTEL trace ID, if we get that for whatever reason
if (traceId && traceId !== INVALID_TRACEID) {
setter.set(carrier, SENTRY_TRACE_HEADER, generateSentryTraceHeader(traceId, spanId, sampled));
if (propagateTraceparent) {
setter.set(carrier, 'traceparent', generateTraceparentHeader(traceId, spanId, sampled));
}
}
super.inject(propagation.setBaggage(context, baggage), carrier, setter);
}
/**
* @inheritDoc
*/
extract(context, carrier, getter) {
const maybeSentryTraceHeader = getter.get(carrier, SENTRY_TRACE_HEADER);
const baggage = getter.get(carrier, SENTRY_BAGGAGE_HEADER);
const sentryTrace = maybeSentryTraceHeader
? Array.isArray(maybeSentryTraceHeader)
? maybeSentryTraceHeader[0]
: maybeSentryTraceHeader
: undefined;
// Add remote parent span context
// If there is no incoming trace, this will return the context as-is
return ensureScopesOnContext(getContextWithRemoteActiveSpan(context, { sentryTrace, baggage }));
}
/**
* @inheritDoc
*/
fields() {
return [SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER, 'traceparent'];
}
}
/**
* Get propagation injection data for the given context.
* The additional options can be passed to override the scope and client that is otherwise derived from the context.
*/
function getInjectionData(
context,
options = {},
)
{
const span = trace.getSpan(context);
// If we have a remote span, the spanId should be considered as the parentSpanId, not spanId itself
// Instead, we use a virtual (generated) spanId for propagation
if (span?.spanContext().isRemote) {
const spanContext = span.spanContext();
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
return {
dynamicSamplingContext,
traceId: spanContext.traceId,
spanId: undefined,
sampled: getSamplingDecision(spanContext), // TODO: Do we need to change something here?
};
}
// If we have a local span, we just use this
if (span) {
const spanContext = span.spanContext();
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
return {
dynamicSamplingContext,
traceId: spanContext.traceId,
spanId: spanContext.spanId,
sampled: getSamplingDecision(spanContext), // TODO: Do we need to change something here?
};
}
// Else we try to use the propagation context from the scope
// The only scenario where this should happen is when we neither have a span, nor an incoming trace
const scope = options.scope || getScopesFromContext(context)?.scope || getCurrentScope();
const client = options.client || getClient();
const propagationContext = scope.getPropagationContext();
const dynamicSamplingContext = client ? getDynamicSamplingContextFromScope(client, scope) : undefined;
return {
dynamicSamplingContext,
traceId: propagationContext.traceId,
spanId: propagationContext.propagationSpanId,
sampled: propagationContext.sampled,
};
}
function getContextWithRemoteActiveSpan(
ctx,
{ sentryTrace, baggage },
) {
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
const { traceId, parentSpanId, sampled, dsc } = propagationContext;
const client = getClient();
const incomingDsc = baggageHeaderToDynamicSamplingContext(baggage);
// We only want to set the virtual span if we are continuing a concrete trace
// Otherwise, we ignore the incoming trace here, e.g. if we have no trace headers
if (!parentSpanId || (client && !shouldContinueTrace(client, incomingDsc?.org_id))) {
return ctx;
}
const spanContext = generateRemoteSpanContext({
traceId,
spanId: parentSpanId,
sampled,
dsc,
});
return trace.setSpanContext(ctx, spanContext);
}
/**
* Takes trace strings and propagates them as a remote active span.
* This should be used in addition to `continueTrace` in OTEL-powered environments.
*/
function continueTraceAsRemoteSpan(
ctx,
options,
callback,
) {
const ctxWithSpanContext = ensureScopesOnContext(getContextWithRemoteActiveSpan(ctx, options));
return context.with(ctxWithSpanContext, callback);
}
function ensureScopesOnContext(ctx) {
// If there are no scopes yet on the context, ensure we have them
const scopes = getScopesFromContext(ctx);
const newScopes = {
// If we have no scope here, this is most likely either the root context or a context manually derived from it
// In this case, we want to fork the current scope, to ensure we do not pollute the root scope
scope: scopes ? scopes.scope : getCurrentScope().clone(),
isolationScope: scopes ? scopes.isolationScope : getIsolationScope(),
};
return setScopesOnContext(ctx, newScopes);
}
/** Try to get the existing baggage header so we can merge this in. */
function getExistingBaggage(carrier) {
try {
const baggage = (carrier )[SENTRY_BAGGAGE_HEADER];
return Array.isArray(baggage) ? baggage.join(',') : baggage;
} catch {
return undefined;
}
}
/**
* It is pretty tricky to get access to the outgoing request URL of a request in the propagator.
* As we only have access to the context of the span to be sent and the carrier (=headers),
* but the span may be unsampled and thus have no attributes.
*
* So we use the following logic:
* 1. If we have an active span, we check if it has a URL attribute.
* 2. Else, if the active span has no URL attribute (e.g. it is unsampled), we check a special trace state (which we set in our sampler).
*/
function getCurrentURL(span) {
const spanData = spanToJSON(span).data;
// `ATTR_URL_FULL` is the new attribute, but we still support the old one, `SEMATTRS_HTTP_URL`, for now.
// eslint-disable-next-line deprecation/deprecation
const urlAttribute = spanData[SEMATTRS_HTTP_URL] || spanData[ATTR_URL_FULL];
if (typeof urlAttribute === 'string') {
return urlAttribute;
}
// Also look at the traceState, which we may set in the sampler even for unsampled spans
const urlTraceState = span.spanContext().traceState?.get(SENTRY_TRACE_STATE_URL);
if (urlTraceState) {
return urlTraceState;
}
return undefined;
}
function generateRemoteSpanContext({
spanId,
traceId,
sampled,
dsc,
}
) {
// We store the DSC as OTEL trace state on the span context
const traceState = makeTraceState({
dsc,
sampled,
});
const spanContext = {
traceId,
spanId,
isRemote: true,
traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,
traceState,
};
return spanContext;
}
/**
* Internal helper for starting spans and manual spans. See {@link startSpan} and {@link startSpanManual} for the public APIs.
* @param options - The span context options
* @param callback - The callback to execute with the span
* @param autoEnd - Whether to automatically end the span after the callback completes
*/
function _startSpan(options, callback, autoEnd) {
const tracer = getTracer();
const { name, parentSpan: customParentSpan } = options;
// If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
const wrapper = getActiveSpanWrapper(customParentSpan);
return wrapper(() => {
const activeCtx = getContext(options.scope, options.forceTransaction);
const shouldSkipSpan = options.onlyIfParent && !trace.getSpan(activeCtx);
const ctx = shouldSkipSpan ? suppressTracing$1(activeCtx) : activeCtx;
const spanOptions = getSpanOptions(options);
// If spans are not enabled, ensure we suppress tracing for the span creation
// but preserve the original context for the callback execution
// This ensures that we don't create spans when tracing is disabled which
// would otherwise be a problem for users that don't enable tracing but use
// custom OpenTelemetry setups.
if (!hasSpansEnabled()) {
const suppressedCtx = isTracingSuppressed(ctx) ? ctx : suppressTracing$1(ctx);
return context.with(suppressedCtx, () => {
return tracer.startActiveSpan(name, spanOptions, suppressedCtx, span => {
// Restore the original unsuppressed context for the callback execution
// so that custom OpenTelemetry spans maintain the correct context.
// We use activeCtx (not ctx) because ctx may be suppressed when onlyIfParent is true
// and no parent span exists. Using activeCtx ensures custom OTel spans are never
// inadvertently suppressed.
return context.with(activeCtx, () => {
return handleCallbackErrors(
() => callback(span),
() => {
// Only set the span status to ERROR when there wasn't any status set before, in order to avoid stomping useful span statuses
if (spanToJSON(span).status === undefined) {
span.setStatus({ code: SpanStatusCode.ERROR });
}
},
autoEnd ? () => span.end() : undefined,
);
});
});
});
}
return tracer.startActiveSpan(name, spanOptions, ctx, span => {
return handleCallbackErrors(
() => callback(span),
() => {
// Only set the span status to ERROR when there wasn't any status set before, in order to avoid stomping useful span statuses
if (spanToJSON(span).status === undefined) {
span.setStatus({ code: SpanStatusCode.ERROR });
}
},
autoEnd ? () => span.end() : undefined,
);
});
});
}
/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
* The created span is the active span and will be used as parent by other spans created inside the function
* and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
*
* If you want to create a span that is not set as active, use {@link startInactiveSpan}.
*
* You'll always get a span passed to the callback,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
*/
function startSpan(options, callback) {
return _startSpan(options, callback, true);
}
/**
* Similar to `Sentry.startSpan`. Wraps a function with a span, but does not finish the span
* after the function is done automatically. You'll have to call `span.end()` or the `finish` function passed to the callback manually.
*
* The created span is the active span and will be used as parent by other spans created inside the function
* and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
*
* You'll always get a span passed to the callback,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
*/
function startSpanManual(
options,
callback,
) {
return _startSpan(options, span => callback(span, () => span.end()), false);
}
/**
* Creates a span. This span is not set as active, so will not get automatic instrumentation spans
* as children or be able to be accessed via `Sentry.getActiveSpan()`.
*
* If you want to create a span that is set as active, use {@link startSpan}.
*
* This function will always return a span,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
*/
function startInactiveSpan(options) {
const tracer = getTracer();
const { name, parentSpan: customParentSpan } = options;
// If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
const wrapper = getActiveSpanWrapper(customParentSpan);
return wrapper(() => {
const activeCtx = getContext(options.scope, options.forceTransaction);
const shouldSkipSpan = options.onlyIfParent && !trace.getSpan(activeCtx);
let ctx = shouldSkipSpan ? suppressTracing$1(activeCtx) : activeCtx;
const spanOptions = getSpanOptions(options);
if (!hasSpansEnabled()) {
ctx = isTracingSuppressed(ctx) ? ctx : suppressTracing$1(ctx);
}
return tracer.startSpan(name, spanOptions, ctx);
});
}
/**
* Forks the current scope and sets the provided span as active span in the context of the provided callback. Can be
* passed `null` to start an entirely new span tree.
*
* @param span Spans started in the context of the provided callback will be children of this span. If `null` is passed,
* spans started within the callback will be root spans.
* @param callback Execution context in which the provided span will be active. Is passed the newly forked scope.
* @returns the value returned from the provided callback function.
*/
function withActiveSpan(span, callback) {
const newContextWithActiveSpan = span ? trace.setSpan(context.active(), span) : trace.deleteSpan(context.active());
return context.with(newContextWithActiveSpan, () => callback(getCurrentScope()));
}
function getTracer() {
const client = getClient();
return client?.tracer || trace.getTracer('@sentry/opentelemetry', SDK_VERSION);
}
function getSpanOptions(options) {
const { startTime, attributes, kind, op, links } = options;
// OTEL expects timestamps in ms, not seconds
const fixedStartTime = typeof startTime === 'number' ? ensureTimestampInMilliseconds(startTime) : startTime;
return {
attributes: op
? {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
...attributes,
}
: attributes,
kind,
links,
startTime: fixedStartTime,
};
}
function ensureTimestampInMilliseconds(timestamp) {
const isMs = timestamp < 9999999999;
return isMs ? timestamp * 1000 : timestamp;
}
function getContext(scope, forceTransaction) {
const ctx = getContextForScope(scope);
const parentSpan = trace.getSpan(ctx);
// In the case that we have no parent span, we start a new trace
// Note that if we continue a trace, we'll always have a remote parent span here anyhow
if (!parentSpan) {
return ctx;
}
// If we don't want to force a transaction, and we have a parent span, all good, we just return as-is!
if (!forceTransaction) {
return ctx;
}
// Else, if we do have a parent span but want to force a transaction, we have to simulate a "root" context
// Else, we need to do two things:
// 1. Unset the parent span from the context, so we'll create a new root span
// 2. Ensure the propagation context is correct, so we'll continue from the parent span
const ctxWithoutSpan = trace.deleteSpan(ctx);
const { spanId, traceId } = parentSpan.spanContext();
const sampled = getSamplingDecision(parentSpan.spanContext());
// In this case, when we are forcing a transaction, we want to treat this like continuing an incoming trace
// so we set the traceState according to the root span
const rootSpan = getRootSpan(parentSpan);
const dsc = getDynamicSamplingContextFromSpan(rootSpan);
const traceState = makeTraceState({
dsc,
sampled,
});
const spanOptions = {
traceId,
spanId,
isRemote: true,
traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,
traceState,
};
const ctxWithSpanContext = trace.setSpanContext(ctxWithoutSpan, spanOptions);
return ctxWithSpanContext;
}
function getContextForScope(scope) {
if (scope) {
const ctx = getContextFromScope(scope);
if (ctx) {
return ctx;
}
}
return context.active();
}
/**
* Continue a trace from `sentry-trace` and `baggage` values.
* These values can be obtained from incoming request headers, or in the browser from `<meta name="sentry-trace">`
* and `<meta name="baggage">` HTML tags.
*
* Spans started with `startSpan`, `startSpanManual` and `startInactiveSpan`, within the callback will automatically
* be attached to the incoming trace.
*
* This is a custom version of `continueTrace` that is used in OTEL-powered environments.
* It propagates the trace as a remote span, in addition to setting it on the propagation context.
*/
function continueTrace(options, callback) {
return continueTraceAsRemoteSpan(context.active(), options, callback);
}
function getActiveSpanWrapper(parentSpan) {
return parentSpan !== undefined
? (callback) => {
return withActiveSpan(parentSpan, callback);
}
: (callback) => callback();
}
/** Suppress tracing in the given callback, ensuring no spans are generated inside of it. */
function suppressTracing(callback) {
const ctx = suppressTracing$1(context.active());
return context.with(ctx, callback);
}
/** Ensure the `trace` context is set on all events. */
function setupEventContextTrace(client) {
client.on('preprocessEvent', event => {
const span = getActiveSpan();
// For transaction events, this is handled separately
// Because the active span may not be the span that is actually the transaction event
if (!span || event.type === 'transaction') {
return;
}
// If event has already set `trace` context, use that one.
event.contexts = {
trace: spanToTraceContext(span),
...event.contexts,
};
const rootSpan = getRootSpan(span);
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(rootSpan),
...event.sdkProcessingMetadata,
};
return event;
});
}
/**
* Otel-specific implementation of `getTraceData`.
* @see `@sentry/core` version of `getTraceData` for more information
*/
function getTraceData({
span,
scope,
client,
propagateTraceparent,
} = {}) {
let ctx = (scope && getContextFromScope(scope)) ?? api.context.active();
if (span) {
const { scope } = getCapturedScopesOnSpan(span);
// fall back to current context if for whatever reason we can't find the one of the span
ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span);
}
const { traceId, spanId, sampled, dynamicSamplingContext } = getInjectionData(ctx, { scope, client });
const traceData = {
'sentry-trace': generateSentryTraceHeader(traceId, spanId, sampled),
baggage: dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext),
};
if (propagateTraceparent) {
traceData.traceparent = generateTraceparentHeader(traceId, spanId, sampled);
}
return traceData;
}
/**
* Sets the async context strategy to use follow the OTEL context under the hood.
* We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts)
*/
function setOpenTelemetryContextAsyncContextStrategy() {
function getScopes() {
const ctx = api.context.active();
const scopes = getScopesFromContext(ctx);
if (scopes) {
return scopes;
}
// fallback behavior:
// if, for whatever reason, we can't find scopes on the context here, we have to fix this somehow
return {
scope: getDefaultCurrentScope(),
isolationScope: getDefaultIsolationScope(),
};
}
function withScope(callback) {
const ctx = api.context.active();
// We depend on the otelContextManager to handle the context/hub
// We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by
// the OTEL context manager, which uses the presence of this key to determine if it should
// fork the isolation scope, or not
// as by default, we don't want to fork this, unless triggered explicitly by `withScope`
return api.context.with(ctx, () => {
return callback(getCurrentScope());
});
}
function withSetScope(scope, callback) {
const ctx = getContextFromScope(scope) || api.context.active();
// We depend on the otelContextManager to handle the context/hub
// We set the `SENTRY_FORK_SET_SCOPE_CONTEXT_KEY` context value, which is picked up by
// the OTEL context manager, which picks up this scope as the current scope
return api.context.with(ctx.setValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, scope), () => {
return callback(scope);
});
}
function withIsolationScope(callback) {
const ctx = api.context.active();
// We depend on the otelContextManager to handle the context/hub
// We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by
// the OTEL context manager, which uses the presence of this key to determine if it should
// fork the isolation scope, or not
return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => {
return callback(getIsolationScope());
});
}
function withSetIsolationScope(isolationScope, callback) {
const ctx = api.context.active();
// We depend on the otelContextManager to handle the context/hub
// We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by
// the OTEL context manager, which uses the presence of this key to determine if it should
// fork the isolation scope, or not
return api.context.with(ctx.setValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, isolationScope), () => {
return callback(getIsolationScope());
});
}
function getCurrentScope() {
return getScopes().scope;
}
function getIsolationScope() {
return getScopes().isolationScope;
}
setAsyncContextStrategy({
withScope,
withSetScope,
withSetIsolationScope,
withIsolationScope,
getCurrentScope,
getIsolationScope,
startSpan,
startSpanManual,
startInactiveSpan,
getActiveSpan,
suppressTracing,
getTraceData,
continueTrace,
// The types here don't fully align, because our own `Span` type is narrower
// than the OTEL one - but this is OK for here, as we now we'll only have OTEL spans passed around
withActiveSpan: withActiveSpan ,
});
}
/**
* Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Scope.
*
* Usage:
* import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
* const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager);
* const contextManager = new SentryContextManager();
*/
function wrapContextManagerClass(
ContextManagerClass,
) {
/**
* This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager.
* It ensures that we create new scopes per context, so that the OTEL Context & the Sentry Scope are always in sync.
*
* Note that we currently only support AsyncHooks with this,
* but since this should work for Node 14+ anyhow that should be good enough.
*/
// @ts-expect-error TS does not like this, but we know this is fine
class SentryContextManager extends ContextManagerClass {
constructor(...args) {
super(...args);
setIsSetup('SentryContextManager');
}
/**
* Overwrite with() of the original AsyncLocalStorageContextManager
* to ensure we also create new scopes per context.
*/
with(
context,
fn,
thisArg,
...args
) {
const currentScopes = getScopesFromContext(context);
const currentScope = currentScopes?.scope || getCurrentScope();
const currentIsolationScope = currentScopes?.isolationScope || getIsolationScope();
const shouldForkIsolationScope = context.getValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) === true;
const scope = context.getValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) ;
const isolationScope = context.getValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY) ;
const newCurrentScope = scope || currentScope.clone();
const newIsolationScope =
isolationScope || (shouldForkIsolationScope ? currentIsolationScope.clone() : currentIsolationScope);
const scopes = { scope: newCurrentScope, isolationScope: newIsolationScope };
const ctx1 = setScopesOnContext(context, scopes);
// Remove the unneeded values again
const ctx2 = ctx1
.deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY)
.deleteValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY)
.deleteValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY);
setContextOnScope(newCurrentScope, ctx2);
return super.with(ctx2, fn, thisArg, ...args);
}
/**
* Gets underlying AsyncLocalStorage and symbol to allow lookup of scope.
*/
getAsyncLocalStorageLookup() {
return {
// @ts-expect-error This is on the base class, but not part of the interface
asyncLocalStorage: this._asyncLocalStorage,
contextSymbol: SENTRY_SCOPES_CONTEXT_KEY,
};
}
}
return SentryContextManager ;
}
/**
* This function runs through a list of OTEL Spans, and wraps them in an `SpanNode`
* where each node holds a reference to their parent node.
*/
function groupSpansWithParents(spans) {
const nodeMap = new Map();
for (const span of spans) {
createOrUpdateSpanNodeAndRefs(nodeMap, span);
}
return Array.from(nodeMap, function ([_id, spanNode]) {
return spanNode;
});
}
/**
* This returns the _local_ parent ID - `parentId` on the span may point to a remote span.
*/
function getLocalParentId(span) {
const parentIsRemote = span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE] === true;
// If the parentId is the trace parent ID, we pretend it's undefined
// As this means the parent exists somewhere else
return !parentIsRemote ? getParentSpanId(span) : undefined;
}
function createOrUpdateSpanNodeAndRefs(nodeMap, span) {
const id = span.spanContext().spanId;
const parentId = getLocalParentId(span);
if (!parentId) {
createOrUpdateNode(nodeMap, { id, span, children: [] });
return;
}
// Else make sure to create parent node as well
// Note that the parent may not know it's parent _yet_, this may be updated in a later pass
const parentNode = createOrGetParentNode(nodeMap, parentId);
const node = createOrUpdateNode(nodeMap, { id, span, parentNode, children: [] });
parentNode.children.push(node);
}
function createOrGetParentNode(nodeMap, id) {
const existing = nodeMap.get(id);
if (existing) {
return existing;
}
return createOrUpdateNode(nodeMap, { id, children: [] });
}
function createOrUpdateNode(nodeMap, spanNode) {
const existing = nodeMap.get(spanNode.id);
// If span is already set, nothing to do here
if (existing?.span) {
return existing;
}
// If it exists but span is not set yet, we update it
if (existing && !existing.span) {
existing.span = spanNode.span;
existing.parentNode = spanNode.parentNode;
return existing;
}
// Else, we create a new one...
nodeMap.set(spanNode.id, spanNode);
return spanNode;
}
// canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation.
const canonicalGrpcErrorCodesMap = {
'1': 'cancelled',
'2': 'unknown_error',
'3': 'invalid_argument',
'4': 'deadline_exceeded',
'5': 'not_found',
'6': 'already_exists',
'7': 'permission_denied',
'8': 'resource_exhausted',
'9': 'failed_precondition',
'10': 'aborted',
'11': 'out_of_range',
'12': 'unimplemented',
'13': 'internal_error',
'14': 'unavailable',
'15': 'data_loss',
'16': 'unauthenticated',
} ;
const isStatusErrorMessageValid = (message) => {
return Object.values(canonicalGrpcErrorCodesMap).includes(message );
};
/**
* Get a Sentry span status from an otel span.
*/
function mapStatus(span) {
const attributes = spanHasAttributes(span) ? span.attributes : {};
const status = spanHasStatus(span) ? span.status : undefined;
if (status) {
// Since span status OK is not set by default, we give it priority: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
if (status.code === SpanStatusCode.OK) {
return { code: SPAN_STATUS_OK };
// If the span is already marked as erroneous we return that exact status
} else if (status.code === SpanStatusCode.ERROR) {
if (typeof status.message === 'undefined') {
const inferredStatus = inferStatusFromAttributes(attributes);
if (inferredStatus) {
return inferredStatus;
}
}
if (status.message && isStatusErrorMessageValid(status.message)) {
return { code: SPAN_STATUS_ERROR, message: status.message };
} else {
return { code: SPAN_STATUS_ERROR, message: 'internal_error' };
}
}
}
// If the span status is UNSET, we try to infer it from HTTP or GRPC status codes.
const inferredStatus = inferStatusFromAttributes(attributes);
if (inferredStatus) {
return inferredStatus;
}
// We default to setting the spans status to ok.
if (status?.code === SpanStatusCode.UNSET) {
return { code: SPAN_STATUS_OK };
} else {
return { code: SPAN_STATUS_ERROR, message: 'unknown_error' };
}
}
function inferStatusFromAttributes(attributes) {
// If the span status is UNSET, we try to infer it from HTTP or GRPC status codes.
// eslint-disable-next-line deprecation/deprecation
const httpCodeAttribute = attributes[ATTR_HTTP_RESPONSE_STATUS_CODE] || attributes[SEMATTRS_HTTP_STATUS_CODE];
// eslint-disable-next-line deprecation/deprecation
const grpcCodeAttribute = attributes[SEMATTRS_RPC_GRPC_STATUS_CODE];
const numberHttpCode =
typeof httpCodeAttribute === 'number'
? httpCodeAttribute
: typeof httpCodeAttribute === 'string'
? parseInt(httpCodeAttribute)
: undefined;
if (typeof numberHttpCode === 'number') {
return getSpanStatusFromHttpCode(numberHttpCode);
}
if (typeof grpcCodeAttribute === 'string') {
return { code: SPAN_STATUS_ERROR, message: canonicalGrpcErrorCodesMap[grpcCodeAttribute] || 'unknown_error' };
}
return undefined;
}
const MAX_SPAN_COUNT = 1000;
const DEFAULT_TIMEOUT = 300; // 5 min
/**
* A Sentry-specific exporter that converts OpenTelemetry Spans to Sentry Spans & Transactions.
*/
class SentrySpanExporter {
/*
* A quick explanation on the buckets: We do bucketing of finished spans for efficiency. This span exporter is
* accumulating spans until a root span is encountered and then it flushes all the spans that are descendants of that
* root span. Because it is totally in the realm of possibilities that root spans are never finished, and we don't
* want to accumulate spans indefinitely in memory, we need to periodically evacuate spans. Naively we could simply
* store the spans in an array and each time a new span comes in we could iterate through the entire array and
* evacuate all spans that have an end-timestamp that is older than our limit. This could get quite expensive because
* we would have to iterate a potentially large number of spans every time we evacuate. We want to avoid these large
* bursts of computation.
*
* Instead we go for a bucketing approach and put spans into buckets, based on what second
* (modulo the time limit) the span was put into the exporter. With buckets, when we decide to evacuate, we can
* iterate through the bucket entries instead, which have an upper bound of items, making the evacuation much more
* efficient. Cleaning up also becomes much more efficient since it simply involves de-referencing a bucket within the
* bucket array, and letting garbage collection take care of the rest.
*/
// Essentially a a set of span ids that are already sent. The values are expiration
// times in this cache so we don't hold onto them indefinitely.
/* Internally, we use a debounced flush to give some wiggle room to the span processor to accumulate more spans. */
constructor(options
) {
this._finishedSpanBucketSize = options?.timeout || DEFAULT_TIMEOUT;
this._finishedSpanBuckets = new Array(this._finishedSpanBucketSize).fill(undefined);
this._lastCleanupTimestampInS = Math.floor(_INTERNAL_safeDateNow() / 1000);
this._spansToBucketEntry = new WeakMap();
this._sentSpans = new Map();
this._debouncedFlush = debounce(this.flush.bind(this), 1, { maxWait: 100 });
}
/**
* Export a single span.
* This is called by the span processor whenever a span is ended.
*/
export(span) {
const currentTimestampInS = Math.floor(_INTERNAL_safeDateNow() / 1000);
if (this._lastCleanupTimestampInS !== currentTimestampInS) {
let droppedSpanCount = 0;
this._finishedSpanBuckets.forEach((bucket, i) => {
if (bucket && bucket.timestampInS <= currentTimestampInS - this._finishedSpanBucketSize) {
droppedSpanCount += bucket.spans.size;
this._finishedSpanBuckets[i] = undefined;
}
});
if (droppedSpanCount > 0) {
DEBUG_BUILD$1 &&
debug.log(
`SpanExporter dropped ${droppedSpanCount} spans because they were pending for more than ${this._finishedSpanBucketSize} seconds.`,
);
}
this._lastCleanupTimestampInS = currentTimestampInS;
}
const currentBucketIndex = currentTimestampInS % this._finishedSpanBucketSize;
const currentBucket = this._finishedSpanBuckets[currentBucketIndex] || {
timestampInS: currentTimestampInS,
spans: new Set(),
};
this._finishedSpanBuckets[currentBucketIndex] = currentBucket;
currentBucket.spans.add(span);
this._spansToBucketEntry.set(span, currentBucket);
// If the span doesn't have a local parent ID (it's a root span), we're gonna flush all the ended spans
const localParentId = getLocalParentId(span);
if (!localParentId || this._sentSpans.has(localParentId)) {
this._debouncedFlush();
}
}
/**
* Try to flush any pending spans immediately.
* This is called internally by the exporter (via _debouncedFlush),
* but can also be triggered externally if we force-flush.
*/
flush() {
const finishedSpans = this._finishedSpanBuckets.flatMap(bucket => (bucket ? Array.from(bucket.spans) : []));
this._flushSentSpanCache();
const sentSpans = this._maybeSend(finishedSpans);
const sentSpanCount = sentSpans.size;
const remainingOpenSpanCount = finishedSpans.length - sentSpanCount;
DEBUG_BUILD$1 &&
debug.log(
`SpanExporter exported ${sentSpanCount} spans, ${remainingOpenSpanCount} spans are waiting for their parent spans to finish`,
);
const expirationDate = _INTERNAL_safeDateNow() + DEFAULT_TIMEOUT * 1000;
for (const span of sentSpans) {
this._sentSpans.set(span.spanContext().spanId, expirationDate);
const bucketEntry = this._spansToBucketEntry.get(span);
if (bucketEntry) {
bucketEntry.spans.delete(span);
}
}
// Cancel a pending debounced flush, if there is one
// This can be relevant if we directly flush, circumventing the debounce
// in that case, we want to cancel any pending debounced flush
this._debouncedFlush.cancel();
}
/**
* Clear the exporter.
* This is called when the span processor is shut down.
*/
clear() {
this._finishedSpanBuckets = this._finishedSpanBuckets.fill(undefined);
this._sentSpans.clear();
this._debouncedFlush.cancel();
}
/**
* Send the given spans, but only if they are part of a finished transaction.
*
* Returns the sent spans.
* Spans remain unsent when their parent span is not yet finished.
* This will happen regularly, as child spans are generally finished before their parents.
* But it _could_ also happen because, for whatever reason, a parent span was lost.
* In this case, we'll eventually need to clean this up.
*/
_maybeSend(spans) {
const grouped = groupSpansWithParents(spans);
const sentSpans = new Set();
const rootNodes = this._getCompletedRootNodes(grouped);
for (const root of rootNodes) {
const span = root.span;
sentSpans.add(span);
const transactionEvent = createTransactionForOtelSpan(span);
// Add an attribute to the transaction event to indicate that this transaction is an orphaned transaction
if (root.parentNode && this._sentSpans.has(root.parentNode.id)) {
const traceData = transactionEvent.contexts?.trace?.data;
if (traceData) {
traceData['sentry.parent_span_already_sent'] = true;
}
}
// We'll recursively add all the child spans to this array
const spans = transactionEvent.spans || [];
for (const child of root.children) {
createAndFinishSpanForOtelSpan(child, spans, sentSpans);
}
// spans.sort() mutates the array, but we do not use this anymore after this point
// so we can safely mutate it here
transactionEvent.spans =
spans.length > MAX_SPAN_COUNT
? spans.sort((a, b) => a.start_timestamp - b.start_timestamp).slice(0, MAX_SPAN_COUNT)
: spans;
const measurements = timedEventsToMeasurements(span.events);
if (measurements) {
transactionEvent.measurements = measurements;
}
captureEvent(transactionEvent);
}
return sentSpans;
}
/** Remove "expired" span id entries from the _sentSpans cache. */
_flushSentSpanCache() {
const currentTimestamp = _INTERNAL_safeDateNow();
// Note, it is safe to delete items from the map as we go: https://stackoverflow.com/a/35943995/90297
for (const [spanId, expirationTime] of this._sentSpans.entries()) {
if (expirationTime <= currentTimestamp) {
this._sentSpans.delete(spanId);
}
}
}
/** Check if a node is a completed root node or a node whose parent has already been sent */
_nodeIsCompletedRootNodeOrHasSentParent(node) {
return !!node.span && (!node.parentNode || this._sentSpans.has(node.parentNode.id));
}
/** Get all completed root nodes from a list of nodes */
_getCompletedRootNodes(nodes) {
// TODO: We should be able to remove the explicit `node is SpanNodeCompleted` type guard
// once we stop supporting TS < 5.5
return nodes.filter((node) => this._nodeIsCompletedRootNodeOrHasSentParent(node));
}
}
function parseSpan(span) {
const attributes = span.attributes;
const origin = attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] ;
const op = attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] ;
const source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] ;
return { origin, op, source };
}
/** Exported only for tests. */
function createTransactionForOtelSpan(span) {
const { op, description, data, origin = 'manual', source } = getSpanData(span);
const capturedSpanScopes = getCapturedScopesOnSpan(span );
const sampleRate = span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] ;
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: sampleRate,
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,
...data,
...removeSentryAttributes(span.attributes),
};
const { links } = span;
const { traceId: trace_id, spanId: span_id } = span.spanContext();
// If parentSpanIdFromTraceState is defined at all, we want it to take precedence
// In that case, an empty string should be interpreted as "no parent span id",
// even if `span.parentSpanId` is set
// this is the case when we are starting a new trace, where we have a virtual span based on the propagationContext
// We only want to continue the traceId in this case, but ignore the parent span
const parent_span_id = getParentSpanId(span);
const status = mapStatus(span);
const traceContext = {
parent_span_id,
span_id,
trace_id,
data: attributes,
origin,
op,
status: getStatusMessage(status), // As per protocol, span status is allowed to be undefined
links: convertSpanLinksForEnvelope(links),
};
const statusCode = attributes[ATTR_HTTP_RESPONSE_STATUS_CODE];
const responseContext = typeof statusCode === 'number' ? { response: { status_code: statusCode } } : undefined;
const transactionEvent = {
contexts: {
trace: traceContext,
otel: {
resource: span.resource.attributes,
},
...responseContext,
},
spans: [],
start_timestamp: spanTimeInputToSeconds(span.startTime),
timestamp: spanTimeInputToSeconds(span.endTime),
transaction: description,
type: 'transaction',
sdkProcessingMetadata: {
capturedSpanScope: capturedSpanScopes.scope,
capturedSpanIsolationScope: capturedSpanScopes.isolationScope,
sampleRate,
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span ),
},
...(source && {
transaction_info: {
source,
},
}),
};
return transactionEvent;
}
function createAndFinishSpanForOtelSpan(node, spans, sentSpans) {
const span = node.span;
if (span) {
sentSpans.add(span);
}
const shouldDrop = !span;
// If this span should be dropped, we still want to create spans for the children of this
if (shouldDrop) {
node.children.forEach(child => {
createAndFinishSpanForOtelSpan(child, spans, sentSpans);
});
return;
}
const span_id = span.spanContext().spanId;
const trace_id = span.spanContext().traceId;
const parentSpanId = getParentSpanId(span);
const { attributes, startTime, endTime, links } = span;
const { op, description, data, origin = 'manual' } = getSpanData(span);
const allData = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
...removeSentryAttributes(attributes),
...data,
};
const status = mapStatus(span);
const spanJSON = {
span_id,
trace_id,
data: allData,
description,
parent_span_id: parentSpanId,
start_timestamp: spanTimeInputToSeconds(startTime),
// This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
timestamp: spanTimeInputToSeconds(endTime) || undefined,
status: getStatusMessage(status), // As per protocol, span status is allowed to be undefined
op,
origin,
measurements: timedEventsToMeasurements(span.events),
links: convertSpanLinksForEnvelope(links),
};
spans.push(spanJSON);
node.children.forEach(child => {
createAndFinishSpanForOtelSpan(child, spans, sentSpans);
});
}
function getSpanData(span)
{
const { op: definedOp, source: definedSource, origin } = parseSpan(span);
const { op: inferredOp, description, source: inferredSource, data: inferredData } = parseSpanDescription(span);
const op = definedOp || inferredOp;
const source = definedSource || inferredSource;
const data = { ...inferredData, ...getData(span) };
return {
op,
description,
source,
origin,
data,
};
}
/**
* Remove custom `sentry.` attributes we do not need to send.
* These are more carrier attributes we use inside of the SDK, we do not need to send them to the API.
*/
function removeSentryAttributes(data) {
const cleanedData = { ...data };
/* eslint-disable @typescript-eslint/no-dynamic-delete */
delete cleanedData[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE];
delete cleanedData[SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE];
delete cleanedData[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
/* eslint-enable @typescript-eslint/no-dynamic-delete */
return cleanedData;
}
function getData(span) {
const attributes = span.attributes;
const data = {};
if (span.kind !== SpanKind.INTERNAL) {
data['otel.kind'] = SpanKind[span.kind];
}
// eslint-disable-next-line deprecation/deprecation
const maybeHttpStatusCodeAttribute = attributes[SEMATTRS_HTTP_STATUS_CODE];
if (maybeHttpStatusCodeAttribute) {
data[ATTR_HTTP_RESPONSE_STATUS_CODE] = maybeHttpStatusCodeAttribute ;
}
const requestData = getRequestSpanData(span);
if (requestData.url) {
data.url = requestData.url;
}
if (requestData['http.query']) {
data['http.query'] = requestData['http.query'].slice(1);
}
if (requestData['http.fragment']) {
data['http.fragment'] = requestData['http.fragment'].slice(1);
}
return data;
}
function onSpanStart(span, parentContext) {
// This is a reliable way to get the parent span - because this is exactly how the parent is identified in the OTEL SDK
const parentSpan = trace.getSpan(parentContext);
let scopes = getScopesFromContext(parentContext);
// We need access to the parent span in order to be able to move up the span tree for breadcrumbs
if (parentSpan && !parentSpan.spanContext().isRemote) {
addChildSpanToSpan(parentSpan, span);
}
// We need this in the span exporter
if (parentSpan?.spanContext().isRemote) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE, true);
}
// The root context does not have scopes stored, so we check for this specifically
// As fallback we attach the global scopes
if (parentContext === ROOT_CONTEXT) {
scopes = {
scope: getDefaultCurrentScope(),
isolationScope: getDefaultIsolationScope(),
};
}
// We need the scope at time of span creation in order to apply it to the event when the span is finished
if (scopes) {
setCapturedScopesOnSpan(span, scopes.scope, scopes.isolationScope);
}
logSpanStart(span);
const client = getClient();
client?.emit('spanStart', span);
}
function onSpanEnd(span) {
logSpanEnd(span);
const client = getClient();
client?.emit('spanEnd', span);
}
/**
* Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via
* the Sentry SDK.
*/
class SentrySpanProcessor {
constructor(options) {
setIsSetup('SentrySpanProcessor');
this._exporter = new SentrySpanExporter(options);
}
/**
* @inheritDoc
*/
async forceFlush() {
this._exporter.flush();
}
/**
* @inheritDoc
*/
async shutdown() {
this._exporter.clear();
}
/**
* @inheritDoc
*/
onStart(span, parentContext) {
onSpanStart(span, parentContext);
}
/** @inheritDoc */
onEnd(span) {
onSpanEnd(span);
this._exporter.export(span);
}
}
/**
* A custom OTEL sampler that uses Sentry sampling rates to make its decision
*/
class SentrySampler {
constructor(client) {
this._client = client;
setIsSetup('SentrySampler');
}
/** @inheritDoc */
shouldSample(
context,
traceId,
spanName,
spanKind,
spanAttributes,
_links,
) {
const options = this._client.getOptions();
const parentSpan = getValidSpan(context);
const parentContext = parentSpan?.spanContext();
if (!hasSpansEnabled(options)) {
return wrapSamplingDecision({ decision: undefined, context, spanAttributes });
}
// `ATTR_HTTP_REQUEST_METHOD` is the new attribute, but we still support the old one, `SEMATTRS_HTTP_METHOD`, for now.
// eslint-disable-next-line deprecation/deprecation
const maybeSpanHttpMethod = spanAttributes[SEMATTRS_HTTP_METHOD] || spanAttributes[ATTR_HTTP_REQUEST_METHOD];
// If we have a http.client span that has no local parent, we never want to sample it
// but we want to leave downstream sampling decisions up to the server
if (spanKind === SpanKind.CLIENT && maybeSpanHttpMethod && (!parentSpan || parentContext?.isRemote)) {
return wrapSamplingDecision({ decision: undefined, context, spanAttributes });
}
const parentSampled = parentSpan ? getParentSampled(parentSpan, traceId, spanName) : undefined;
const isRootSpan = !parentSpan || parentContext?.isRemote;
// We only sample based on parameters (like tracesSampleRate or tracesSampler) for root spans (which is done in sampleSpan).
// Non-root-spans simply inherit the sampling decision from their parent.
if (!isRootSpan) {
return wrapSamplingDecision({
decision: parentSampled ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,
context,
spanAttributes,
});
}
// We want to pass the inferred name & attributes to the sampler method
const {
description: inferredSpanName,
data: inferredAttributes,
op,
} = inferSpanData(spanName, spanAttributes, spanKind);
const mergedAttributes = {
...inferredAttributes,
...spanAttributes,
};
if (op) {
mergedAttributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = op;
}
const mutableSamplingDecision = { decision: true };
this._client.emit(
'beforeSampling',
{
spanAttributes: mergedAttributes,
spanName: inferredSpanName,
parentSampled: parentSampled,
parentContext: parentContext,
},
mutableSamplingDecision,
);
if (!mutableSamplingDecision.decision) {
return wrapSamplingDecision({ decision: undefined, context, spanAttributes });
}
const { isolationScope } = getScopesFromContext(context) ?? {};
const dscString = parentContext?.traceState ? parentContext.traceState.get(SENTRY_TRACE_STATE_DSC) : undefined;
const dsc = dscString ? baggageHeaderToDynamicSamplingContext(dscString) : undefined;
const sampleRand = parseSampleRate(dsc?.sample_rand) ?? _INTERNAL_safeMathRandom();
const [sampled, sampleRate, localSampleRateWasApplied] = sampleSpan(
options,
{
name: inferredSpanName,
attributes: mergedAttributes,
normalizedRequest: isolationScope?.getScopeData().sdkProcessingMetadata.normalizedRequest,
parentSampled,
parentSampleRate: parseSampleRate(dsc?.sample_rate),
},
sampleRand,
);
const method = `${maybeSpanHttpMethod}`.toUpperCase();
if (method === 'OPTIONS' || method === 'HEAD') {
DEBUG_BUILD$1 && debug.log(`[Tracing] Not sampling span because HTTP method is '${method}' for ${spanName}`);
return wrapSamplingDecision({
decision: SamplingDecision.NOT_RECORD,
context,
spanAttributes,
sampleRand,
downstreamTraceSampleRate: 0, // we don't want to sample anything in the downstream trace either
});
}
if (
!sampled &&
// We check for `parentSampled === undefined` because we only want to record client reports for spans that are trace roots (ie. when there was incoming trace)
parentSampled === undefined
) {
DEBUG_BUILD$1 && debug.log('[Tracing] Discarding root span because its trace was not chosen to be sampled.');
this._client.recordDroppedEvent('sample_rate', 'transaction');
}
return {
...wrapSamplingDecision({
decision: sampled ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,
context,
spanAttributes,
sampleRand,
downstreamTraceSampleRate: localSampleRateWasApplied ? sampleRate : undefined,
}),
attributes: {
// We set the sample rate on the span when a local sample rate was applied to better understand how traces were sampled in Sentry
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: localSampleRateWasApplied ? sampleRate : undefined,
},
};
}
/** Returns the sampler name or short description with the configuration. */
toString() {
return 'SentrySampler';
}
}
function getParentSampled(parentSpan, traceId, spanName) {
const parentContext = parentSpan.spanContext();
// Only inherit sample rate if `traceId` is the same
// Note for testing: `isSpanContextValid()` checks the format of the traceId/spanId, so we need to pass valid ones
if (isSpanContextValid(parentContext) && parentContext.traceId === traceId) {
if (parentContext.isRemote) {
const parentSampled = getSamplingDecision(parentSpan.spanContext());
DEBUG_BUILD$1 &&
debug.log(`[Tracing] Inheriting remote parent's sampled decision for ${spanName}: ${parentSampled}`);
return parentSampled;
}
const parentSampled = getSamplingDecision(parentContext);
DEBUG_BUILD$1 && debug.log(`[Tracing] Inheriting parent's sampled decision for ${spanName}: ${parentSampled}`);
return parentSampled;
}
return undefined;
}
/**
* Wrap a sampling decision with data that Sentry needs to work properly with it.
* If you pass `decision: undefined`, it will be treated as `NOT_RECORDING`, but in contrast to passing `NOT_RECORDING`
* it will not propagate this decision to downstream Sentry SDKs.
*/
function wrapSamplingDecision({
decision,
context,
spanAttributes,
sampleRand,
downstreamTraceSampleRate,
}
) {
let traceState = getBaseTraceState(context, spanAttributes);
// We will override the propagated sample rate downstream when
// - the tracesSampleRate is applied
// - the tracesSampler is invoked
// Since unsampled OTEL spans (NonRecordingSpans) cannot hold attributes we need to store this on the (trace)context.
if (downstreamTraceSampleRate !== undefined) {
traceState = traceState.set(SENTRY_TRACE_STATE_SAMPLE_RATE, `${downstreamTraceSampleRate}`);
}
if (sampleRand !== undefined) {
traceState = traceState.set(SENTRY_TRACE_STATE_SAMPLE_RAND, `${sampleRand}`);
}
// If the decision is undefined, we treat it as NOT_RECORDING, but we don't propagate this decision to downstream SDKs
// Which is done by not setting `SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING` traceState
if (decision == undefined) {
return { decision: SamplingDecision.NOT_RECORD, traceState };
}
if (decision === SamplingDecision.NOT_RECORD) {
return { decision, traceState: traceState.set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1') };
}
return { decision, traceState };
}
function getBaseTraceState(context, spanAttributes) {
const parentSpan = trace.getSpan(context);
const parentContext = parentSpan?.spanContext();
let traceState = parentContext?.traceState || new TraceState();
// We always keep the URL on the trace state, so we can access it in the propagator
// `ATTR_URL_FULL` is the new attribute, but we still support the old one, `ATTR_HTTP_URL`, for now.
// eslint-disable-next-line deprecation/deprecation
const url = spanAttributes[SEMATTRS_HTTP_URL] || spanAttributes[ATTR_URL_FULL];
if (url && typeof url === 'string') {
traceState = traceState.set(SENTRY_TRACE_STATE_URL, url);
}
return traceState;
}
/**
* If the active span is invalid, we want to ignore it as parent.
* This aligns with how otel tracers and default samplers handle these cases.
*/
function getValidSpan(context) {
const span = trace.getSpan(context);
return span && isSpanContextValid(span.spanContext()) ? span : undefined;
}
/**
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
*
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
*/
const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
const INTEGRATION_NAME$1 = 'WinterCGFetch';
const HAS_CLIENT_MAP = new WeakMap();
const _winterCGFetch = ((options = {}) => {
const breadcrumbs = options.breadcrumbs === undefined ? true : options.breadcrumbs;
const shouldCreateSpanForRequest = options.shouldCreateSpanForRequest;
const _createSpanUrlMap = new LRUMap(100);
const _headersUrlMap = new LRUMap(100);
const spans = {};
/** Decides whether to attach trace data to the outgoing fetch request */
function _shouldAttachTraceData(url) {
const client = getClient();
if (!client) {
return false;
}
const clientOptions = client.getOptions();
if (clientOptions.tracePropagationTargets === undefined) {
return true;
}
const cachedDecision = _headersUrlMap.get(url);
if (cachedDecision !== undefined) {
return cachedDecision;
}
const decision = stringMatchesSomePattern(url, clientOptions.tracePropagationTargets);
_headersUrlMap.set(url, decision);
return decision;
}
/** Helper that wraps shouldCreateSpanForRequest option */
function _shouldCreateSpan(url) {
if (shouldCreateSpanForRequest === undefined) {
return true;
}
const cachedDecision = _createSpanUrlMap.get(url);
if (cachedDecision !== undefined) {
return cachedDecision;
}
const decision = shouldCreateSpanForRequest(url);
_createSpanUrlMap.set(url, decision);
return decision;
}
return {
name: INTEGRATION_NAME$1,
setupOnce() {
addFetchInstrumentationHandler(handlerData => {
const client = getClient();
if (!client || !HAS_CLIENT_MAP.get(client)) {
return;
}
if (isSentryRequestUrl(handlerData.fetchData.url, client)) {
return;
}
instrumentFetchRequest(handlerData, _shouldCreateSpan, _shouldAttachTraceData, spans, {
spanOrigin: 'auto.http.wintercg_fetch',
});
if (breadcrumbs) {
createBreadcrumb(handlerData);
}
});
},
setup(client) {
HAS_CLIENT_MAP.set(client, true);
},
};
}) ;
/**
* Creates spans and attaches tracing headers to fetch requests on WinterCG runtimes.
*/
const winterCGFetchIntegration = defineIntegration(_winterCGFetch);
function createBreadcrumb(handlerData) {
const { startTimestamp, endTimestamp } = handlerData;
// We only capture complete fetch requests
if (!endTimestamp) {
return;
}
const breadcrumbData = {
method: handlerData.fetchData.method,
url: handlerData.fetchData.url,
};
if (handlerData.error) {
const hint = {
data: handlerData.error,
input: handlerData.args,
startTimestamp,
endTimestamp,
};
addBreadcrumb(
{
category: 'fetch',
data: breadcrumbData,
level: 'error',
type: 'http',
},
hint,
);
} else {
const response = handlerData.response ;
breadcrumbData.request_body_size = handlerData.fetchData.request_body_size;
breadcrumbData.response_body_size = handlerData.fetchData.response_body_size;
breadcrumbData.status_code = response?.status;
const hint = {
input: handlerData.args,
response,
startTimestamp,
endTimestamp,
};
const level = getBreadcrumbLogLevelFromHttpStatusCode(breadcrumbData.status_code);
addBreadcrumb(
{
category: 'fetch',
data: breadcrumbData,
type: 'http',
level,
},
hint,
);
}
}
const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;
/**
* This is a modified promise buffer that collects tasks until drain is called.
* We need this in the edge runtime because edge function invocations may not share I/O objects, like fetch requests
* and responses, and the normal PromiseBuffer inherently buffers stuff inbetween incoming requests.
*
* A limitation we need to be aware of is that DEFAULT_TRANSPORT_BUFFER_SIZE is the maximum amount of payloads the
* SDK can send for a given edge function invocation.
*/
class IsolatedPromiseBuffer {
// We just have this field because the promise buffer interface requires it.
// If we ever remove it from the interface we should also remove it here.
constructor(_bufferSize = DEFAULT_TRANSPORT_BUFFER_SIZE) {
this.$ = [];
this._taskProducers = [];
this._bufferSize = _bufferSize;
}
/**
* @inheritdoc
*/
add(taskProducer) {
if (this._taskProducers.length >= this._bufferSize) {
return Promise.reject(SENTRY_BUFFER_FULL_ERROR);
}
this._taskProducers.push(taskProducer);
return Promise.resolve({});
}
/**
* @inheritdoc
*/
drain(timeout) {
const oldTaskProducers = [...this._taskProducers];
this._taskProducers = [];
return new Promise(resolve => {
const timer = setTimeout(() => {
if (timeout && timeout > 0) {
resolve(false);
}
}, timeout);
// This cannot reject
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Promise.all(
oldTaskProducers.map(taskProducer =>
taskProducer().then(null, () => {
// catch all failed requests
}),
),
).then(() => {
// resolve to true if all fetch requests settled
clearTimeout(timer);
resolve(true);
});
});
}
}
/**
* Creates a Transport that uses the Edge Runtimes native fetch API to send events to Sentry.
*/
function makeEdgeTransport(options) {
function makeRequest(request) {
const requestOptions = {
body: request.body,
method: 'POST',
headers: options.headers,
...options.fetchOptions,
};
return suppressTracing$2(() => {
return fetch(options.url, requestOptions).then(response => {
return {
statusCode: response.status,
headers: {
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
'retry-after': response.headers.get('Retry-After'),
},
};
});
});
}
return createTransport(options, makeRequest, new IsolatedPromiseBuffer(options.bufferSize));
}
/**
* Returns an environment setting value determined by Vercel's `VERCEL_ENV` environment variable.
*
* @param isClient Flag to indicate whether to use the `NEXT_PUBLIC_` prefixed version of the environment variable.
*/
function getVercelEnv(isClient) {
const vercelEnvVar = process.env.VERCEL_ENV;
return vercelEnvVar ? `vercel-${vercelEnvVar}` : undefined;
}
const ADD_LISTENER_METHODS = [
'addListener' ,
'on' ,
'once' ,
'prependListener' ,
'prependOnceListener' ,
];
class AbstractAsyncHooksContextManager {constructor() { AbstractAsyncHooksContextManager.prototype.__init.call(this);AbstractAsyncHooksContextManager.prototype.__init2.call(this); }
/**
* Binds a the certain context or the active one to the target function and then returns the target
* @param context A context (span) to be bind to target
* @param target a function or event emitter. When target or one of its callbacks is called,
* the provided context will be used as the active context for the duration of the call.
*/
bind(context, target) {
if (typeof target === 'object' && target !== null && 'on' in target) {
return this._bindEventEmitter(context, target ) ;
}
if (typeof target === 'function') {
return this._bindFunction(context, target);
}
return target;
}
_bindFunction(context, target) {
const manager = this;
const contextWrapper = function ( ...args) {
return manager.with(context, () => target.apply(this, args));
};
Object.defineProperty(contextWrapper, 'length', {
enumerable: false,
configurable: true,
writable: false,
value: target.length,
});
/**
* It isn't possible to tell Typescript that contextWrapper is the same as T
* so we forced to cast as any here.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return contextWrapper ;
}
/**
* By default, EventEmitter call their callback with their context, which we do
* not want, instead we will bind a specific context to all callbacks that
* go through it.
* @param context the context we want to bind
* @param ee EventEmitter an instance of EventEmitter to patch
*/
_bindEventEmitter(context, ee) {
const map = this._getPatchMap(ee);
if (map !== undefined) return ee;
this._createPatchMap(ee);
// patch methods that add a listener to propagate context
ADD_LISTENER_METHODS.forEach(methodName => {
if (ee[methodName] === undefined) return;
ee[methodName] = this._patchAddListener(ee, ee[methodName], context);
});
// patch methods that remove a listener
if (typeof ee.removeListener === 'function') {
ee.removeListener = this._patchRemoveListener(ee, ee.removeListener);
}
if (typeof ee.off === 'function') {
ee.off = this._patchRemoveListener(ee, ee.off);
}
// patch method that remove all listeners
if (typeof ee.removeAllListeners === 'function') {
ee.removeAllListeners = this._patchRemoveAllListeners(ee, ee.removeAllListeners);
}
return ee;
}
/**
* Patch methods that remove a given listener so that we match the "patched"
* version of that listener (the one that propagate context).
* @param ee EventEmitter instance
* @param original reference to the patched method
*/
_patchRemoveListener(ee, original) {
const contextManager = this;
return function ( event, listener) {
const events = contextManager._getPatchMap(ee)?.[event];
if (events === undefined) {
return original.call(this, event, listener);
}
const patchedListener = events.get(listener);
return original.call(this, event, patchedListener || listener);
};
}
/**
* Patch methods that remove all listeners so we remove our
* internal references for a given event.
* @param ee EventEmitter instance
* @param original reference to the patched method
*/
_patchRemoveAllListeners(ee, original) {
const contextManager = this;
return function ( event) {
const map = contextManager._getPatchMap(ee);
if (map !== undefined) {
if (arguments.length === 0) {
contextManager._createPatchMap(ee);
} else if (map[event] !== undefined) {
delete map[event];
}
}
return original.apply(this, arguments);
};
}
/**
* Patch methods on an event emitter instance that can add listeners so we
* can force them to propagate a given context.
* @param ee EventEmitter instance
* @param original reference to the patched method
* @param [context] context to propagate when calling listeners
*/
_patchAddListener(ee, original, context) {
const contextManager = this;
return function ( event, listener) {
/**
* This check is required to prevent double-wrapping the listener.
* The implementation for ee.once wraps the listener and calls ee.on.
* Without this check, we would wrap that wrapped listener.
* This causes an issue because ee.removeListener depends on the onceWrapper
* to properly remove the listener. If we wrap their wrapper, we break
* that detection.
*/
if (contextManager._wrapped) {
return original.call(this, event, listener);
}
let map = contextManager._getPatchMap(ee);
if (map === undefined) {
map = contextManager._createPatchMap(ee);
}
let listeners = map[event];
if (listeners === undefined) {
listeners = new WeakMap();
map[event] = listeners;
}
const patchedListener = contextManager.bind(context, listener);
// store a weak reference of the user listener to ours
listeners.set(listener, patchedListener);
/**
* See comment at the start of this function for the explanation of this property.
*/
contextManager._wrapped = true;
try {
return original.call(this, event, patchedListener);
} finally {
contextManager._wrapped = false;
}
};
}
_createPatchMap(ee) {
const map = Object.create(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(ee )[this._kOtListeners] = map;
return map;
}
_getPatchMap(ee) {
return (ee )[this._kOtListeners];
}
__init() {this._kOtListeners = Symbol('OtListeners');}
__init2() {this._wrapped = false;}
}
// Inline AsyncLocalStorage interface to avoid Node.js module dependency
// This prevents Node.js type leaks in edge runtime environments
class AsyncLocalStorageContextManager extends AbstractAsyncHooksContextManager {
constructor() {
super();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const MaybeGlobalAsyncLocalStorageConstructor = (GLOBAL_OBJ ).AsyncLocalStorage;
if (!MaybeGlobalAsyncLocalStorageConstructor) {
DEBUG_BUILD &&
debug.warn(
"Tried to register AsyncLocalStorage async context strategy in a runtime that doesn't support AsyncLocalStorage.",
);
this._asyncLocalStorage = {
getStore() {
return undefined;
},
run(_store, callback, ...args) {
return callback.apply(this, args);
},
disable() {
// noop
},
};
} else {
this._asyncLocalStorage = new MaybeGlobalAsyncLocalStorageConstructor();
}
}
active() {
return this._asyncLocalStorage.getStore() ?? ROOT_CONTEXT;
}
with(
context,
fn,
thisArg,
...args
) {
const cb = thisArg == null ? fn : fn.bind(thisArg);
return this._asyncLocalStorage.run(context, cb , ...args);
}
enable() {
return this;
}
disable() {
this._asyncLocalStorage.disable();
return this;
}
}
const nodeStackParser = createStackParser(nodeStackLineParser());
/** Get the default integrations for the browser SDK. */
function getDefaultIntegrations(options) {
return [
dedupeIntegration(),
// TODO(v11): Replace with `eventFiltersIntegration` once we remove the deprecated `inboundFiltersIntegration`
// eslint-disable-next-line deprecation/deprecation
inboundFiltersIntegration(),
functionToStringIntegration(),
conversationIdIntegration(),
linkedErrorsIntegration(),
winterCGFetchIntegration(),
consoleIntegration(),
// TODO(v11): integration can be included - but integration should not add IP address etc
...(options.sendDefaultPii ? [requestDataIntegration()] : []),
];
}
/** Inits the Sentry NextJS SDK on the Edge Runtime. */
function init(options = {}) {
setOpenTelemetryContextAsyncContextStrategy();
const scope = getCurrentScope();
scope.update(options.initialScope);
if (options.defaultIntegrations === undefined) {
options.defaultIntegrations = getDefaultIntegrations(options);
}
if (options.dsn === undefined && process.env.SENTRY_DSN) {
options.dsn = process.env.SENTRY_DSN;
}
if (options.tracesSampleRate === undefined && process.env.SENTRY_TRACES_SAMPLE_RATE) {
const tracesSampleRate = parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE);
if (isFinite(tracesSampleRate)) {
options.tracesSampleRate = tracesSampleRate;
}
}
if (options.release === undefined) {
const detectedRelease = getSentryRelease();
if (detectedRelease !== undefined) {
options.release = detectedRelease;
}
}
options.environment =
options.environment || process.env.SENTRY_ENVIRONMENT || getVercelEnv() || process.env.NODE_ENV;
const client = new VercelEdgeClient({
...options,
stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser),
integrations: getIntegrationsToSetup(options),
transport: options.transport || makeEdgeTransport,
});
// The client is on the current scope, from where it generally is inherited
getCurrentScope().setClient(client);
client.init();
// If users opt-out of this, they _have_ to set up OpenTelemetry themselves
// There is no way to use this SDK without OpenTelemetry!
if (!options.skipOpenTelemetrySetup) {
setupOtel(client);
validateOpenTelemetrySetup();
}
enhanceDscWithOpenTelemetryRootSpanName(client);
setupEventContextTrace(client);
return client;
}
function validateOpenTelemetrySetup() {
if (!DEBUG_BUILD) {
return;
}
const setup = openTelemetrySetupCheck();
const required = ['SentryContextManager', 'SentryPropagator'];
if (hasSpansEnabled()) {
required.push('SentrySpanProcessor');
}
for (const k of required) {
if (!setup.includes(k)) {
debug.error(
`You have to set up the ${k}. Without this, the OpenTelemetry & Sentry integration will not work properly.`,
);
}
}
if (!setup.includes('SentrySampler')) {
debug.warn(
'You have to set up the SentrySampler. Without this, the OpenTelemetry & Sentry integration may still work, but sample rates set for the Sentry SDK will not be respected. If you use a custom sampler, make sure to use `wrapSamplingDecision`.',
);
}
}
// exported for tests
// eslint-disable-next-line jsdoc/require-jsdoc
function setupOtel(client) {
if (client.getOptions().debug) {
setupOpenTelemetryLogger();
}
// Create and configure NodeTracerProvider
const provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
resource: defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'edge',
// eslint-disable-next-line deprecation/deprecation
[SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
[ATTR_SERVICE_VERSION]: SDK_VERSION,
}),
),
forceFlushTimeoutMillis: 500,
spanProcessors: [
new SentrySpanProcessor({
timeout: client.getOptions().maxSpanWaitDuration,
}),
],
});
const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager);
trace.setGlobalTracerProvider(provider);
propagation.setGlobalPropagator(new SentryPropagator());
context.setGlobalContextManager(new SentryContextManager());
client.traceProvider = provider;
}
/**
* Setup the OTEL logger to use our own debug logger.
*/
function setupOpenTelemetryLogger() {
// Disable diag, to ensure this works even if called multiple times
diag.disable();
diag.setLogger(
{
error: debug.error,
warn: debug.warn,
info: debug.log,
debug: debug.log,
verbose: debug.log,
},
DiagLogLevel.DEBUG,
);
}
/**
* Returns a release dynamically from environment variables.
*/
// eslint-disable-next-line complexity
function getSentryRelease(fallback) {
// Always read first as Sentry takes this as precedence
if (process.env.SENTRY_RELEASE) {
return process.env.SENTRY_RELEASE;
}
// This supports the variable that sentry-webpack-plugin injects
if (GLOBAL_OBJ.SENTRY_RELEASE?.id) {
return GLOBAL_OBJ.SENTRY_RELEASE.id;
}
// This list is in approximate alpha order, separated into 3 categories:
// 1. Git providers
// 2. CI providers with specific environment variables (has the provider name in the variable name)
// 3. CI providers with generic environment variables (checked for last to prevent possible false positives)
const possibleReleaseNameOfGitProvider =
// GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
process.env['GITHUB_SHA'] ||
// GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
process.env['CI_MERGE_REQUEST_SOURCE_BRANCH_SHA'] ||
process.env['CI_BUILD_REF'] ||
process.env['CI_COMMIT_SHA'] ||
// Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/
process.env['BITBUCKET_COMMIT'];
const possibleReleaseNameOfCiProvidersWithSpecificEnvVar =
// AppVeyor - https://www.appveyor.com/docs/environment-variables/
process.env['APPVEYOR_PULL_REQUEST_HEAD_COMMIT'] ||
process.env['APPVEYOR_REPO_COMMIT'] ||
// AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
process.env['CODEBUILD_RESOLVED_SOURCE_VERSION'] ||
// AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html
process.env['AWS_COMMIT_ID'] ||
// Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml
process.env['BUILD_SOURCEVERSION'] ||
// Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/
process.env['GIT_CLONE_COMMIT_HASH'] ||
// Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables
process.env['BUDDY_EXECUTION_REVISION'] ||
// Builtkite - https://buildkite.com/docs/pipelines/environment-variables
process.env['BUILDKITE_COMMIT'] ||
// CircleCI - https://circleci.com/docs/variables/
process.env['CIRCLE_SHA1'] ||
// Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables
process.env['CIRRUS_CHANGE_IN_REPO'] ||
// Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/
process.env['CF_REVISION'] ||
// Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/
process.env['CM_COMMIT'] ||
// Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables
process.env['CF_PAGES_COMMIT_SHA'] ||
// Drone - https://docs.drone.io/pipeline/environment/reference/
process.env['DRONE_COMMIT_SHA'] ||
// Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables
process.env['FC_GIT_COMMIT_SHA'] ||
// Heroku #1 https://devcenter.heroku.com/articles/heroku-ci
process.env['HEROKU_TEST_RUN_COMMIT_VERSION'] ||
// Heroku #2 https://docs.sentry.io/product/integrations/deployment/heroku/#configure-releases
process.env['HEROKU_SLUG_COMMIT'] ||
// Railway - https://docs.railway.app/reference/variables#git-variables
process.env['RAILWAY_GIT_COMMIT_SHA'] ||
// Render - https://render.com/docs/environment-variables
process.env['RENDER_GIT_COMMIT'] ||
// Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables
process.env['SEMAPHORE_GIT_SHA'] ||
// TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
process.env['TRAVIS_PULL_REQUEST_SHA'] ||
// Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables
process.env['VERCEL_GIT_COMMIT_SHA'] ||
process.env['VERCEL_GITHUB_COMMIT_SHA'] ||
process.env['VERCEL_GITLAB_COMMIT_SHA'] ||
process.env['VERCEL_BITBUCKET_COMMIT_SHA'] ||
// Zeit (now known as Vercel)
process.env['ZEIT_GITHUB_COMMIT_SHA'] ||
process.env['ZEIT_GITLAB_COMMIT_SHA'] ||
process.env['ZEIT_BITBUCKET_COMMIT_SHA'];
const possibleReleaseNameOfCiProvidersWithGenericEnvVar =
// CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables
process.env['CI_COMMIT_ID'] ||
// Coolify - https://coolify.io/docs/knowledge-base/environment-variables
process.env['SOURCE_COMMIT'] ||
// Heroku #3 https://devcenter.heroku.com/changelog-items/630
process.env['SOURCE_VERSION'] ||
// Jenkins - https://plugins.jenkins.io/git/#environment-variables
process.env['GIT_COMMIT'] ||
// Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata
process.env['COMMIT_REF'] ||
// TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html
process.env['BUILD_VCS_NUMBER'] ||
// Woodpecker CI - https://woodpecker-ci.org/docs/usage/environment
process.env['CI_COMMIT_SHA'];
return (
possibleReleaseNameOfGitProvider ||
possibleReleaseNameOfCiProvidersWithSpecificEnvVar ||
possibleReleaseNameOfCiProvidersWithGenericEnvVar ||
fallback
);
}
const INTEGRATION_NAME = 'VercelAI';
const _vercelAIIntegration = (() => {
return {
name: INTEGRATION_NAME,
setup(client) {
addVercelAiProcessors(client);
},
};
}) ;
/**
* Adds Sentry tracing instrumentation for the [ai](https://www.npmjs.com/package/ai) library.
* This integration is not enabled by default, you need to manually add it.
*
* For more information, see the [`ai` documentation](https://sdk.vercel.ai/docs/ai-sdk-core/telemetry).
*
* You need to enable collecting spans for a specific call by setting
* `experimental_telemetry.isEnabled` to `true` in the first argument of the function call.
*
* ```javascript
* const result = await generateText({
* model: openai('gpt-4-turbo'),
* experimental_telemetry: { isEnabled: true },
* });
* ```
*
* If you want to collect inputs and outputs for a specific call, you must specifically opt-in to each
* function call by setting `experimental_telemetry.recordInputs` and `experimental_telemetry.recordOutputs`
* to `true`.
*
* ```javascript
* const result = await generateText({
* model: openai('gpt-4-turbo'),
* experimental_telemetry: { isEnabled: true, recordInputs: true, recordOutputs: true },
* });
*/
const vercelAIIntegration = defineIntegration(_vercelAIIntegration);
export { VercelEdgeClient, getDefaultIntegrations, init, vercelAIIntegration, winterCGFetchIntegration };
//# sourceMappingURL=index.js.map