diff --git a/packages/node/src/integrations/tracing/kafka/index.ts b/packages/node/src/integrations/tracing/kafka/index.ts deleted file mode 100644 index b56288771904..000000000000 --- a/packages/node/src/integrations/tracing/kafka/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { KafkaJsInstrumentation } from './vendored/instrumentation'; -import { generateInstrumentOnce } from '../../../otel/instrument'; - -const INTEGRATION_NAME = 'Kafka' as const; - -export const instrumentKafka = generateInstrumentOnce(INTEGRATION_NAME, () => new KafkaJsInstrumentation()); diff --git a/packages/node/src/integrations/tracing/kafka/vendored/instrumentation.ts b/packages/node/src/integrations/tracing/kafka/vendored/instrumentation.ts deleted file mode 100644 index d9a5e65237ee..000000000000 --- a/packages/node/src/integrations/tracing/kafka/vendored/instrumentation.ts +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors, Aspecto - * SPDX-License-Identifier: Apache-2.0 - * - * NOTICE from the Sentry authors: - * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-kafkajs - * - Upstream version: @opentelemetry/instrumentation-kafkajs@0.27.0 - * - Some types vendored from kafkajs with simplifications - * - Refactored to use Sentry's span APIs instead of OpenTelemetry tracing APIs - * - Cross-broker trace propagation uses Sentry's `getTraceData`/`continueTrace` instead of the OTel - * propagator, so the vendored `bufferTextMapGetter` propagator is gone - * - Dropped the OTel metrics (no MeterProvider is wired up) and, with them, the `network.request` - * event listeners they relied on; origin is folded into span creation instead of `index.ts` hooks - */ - -import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; -import { InstrumentationBase, InstrumentationNodeModuleDefinition, isWrapped } from '@opentelemetry/instrumentation'; -import { MESSAGING_BATCH_MESSAGE_COUNT } from '@sentry/conventions/attributes'; -import type { Span } from '@sentry/core'; -import { - continueTrace, - SDK_VERSION, - SPAN_STATUS_ERROR, - SPAN_STATUS_OK, - startInactiveSpan, - startNewTrace, - withActiveSpan, -} from '@sentry/core'; -import type { - Consumer, - ConsumerRunConfig, - EachBatchHandler, - EachMessageHandler, - Kafka, - KafkaMessage, - Producer, - RecordMetadata, - Transaction, -} from './kafkajs-types'; -import { - ATTR_MESSAGING_DESTINATION_PARTITION_ID, - MESSAGING_OPERATION_TYPE_VALUE_PROCESS, - MESSAGING_OPERATION_TYPE_VALUE_RECEIVE, -} from './semconv'; -import { - endSpansOnPromise, - getHeaderAsString, - getLinksFromHeaders, - startConsumerSpan, - startProducerSpan, -} from './utils'; - -const PACKAGE_NAME = '@sentry/instrumentation-kafkajs'; - -export class KafkaJsInstrumentation extends InstrumentationBase { - public constructor(config: InstrumentationConfig = {}) { - super(PACKAGE_NAME, SDK_VERSION, config); - } - - protected init(): InstrumentationNodeModuleDefinition { - const unpatch = (moduleExports: any): void => { - if (isWrapped(moduleExports?.Kafka?.prototype.producer)) { - this._unwrap(moduleExports.Kafka.prototype, 'producer'); - } - if (isWrapped(moduleExports?.Kafka?.prototype.consumer)) { - this._unwrap(moduleExports.Kafka.prototype, 'consumer'); - } - }; - - const module = new InstrumentationNodeModuleDefinition( - 'kafkajs', - ['>=0.3.0 <3'], - (moduleExports: any) => { - unpatch(moduleExports); - this._wrap(moduleExports?.Kafka?.prototype, 'producer', this._getProducerPatch()); - this._wrap(moduleExports?.Kafka?.prototype, 'consumer', this._getConsumerPatch()); - - return moduleExports; - }, - unpatch, - ); - return module; - } - - private _getConsumerPatch() { - const instrumentation = this; - return (original: Kafka['consumer']) => { - return function consumer(this: Kafka, ...args: Parameters) { - const newConsumer: Consumer = original.apply(this, args); - - // oxlint-disable-next-line typescript/unbound-method -- property check, the method is never called - if (isWrapped(newConsumer.run)) { - instrumentation._unwrap(newConsumer, 'run'); - } - - instrumentation._wrap(newConsumer, 'run', instrumentation._getConsumerRunPatch()); - - return newConsumer; - }; - }; - } - - private _getProducerPatch() { - const instrumentation = this; - return (original: Kafka['producer']) => { - return function consumer(this: Kafka, ...args: Parameters) { - const newProducer: Producer = original.apply(this, args); - - // oxlint-disable-next-line typescript/unbound-method -- property check, the method is never called - if (isWrapped(newProducer.sendBatch)) { - instrumentation._unwrap(newProducer, 'sendBatch'); - } - instrumentation._wrap(newProducer, 'sendBatch', instrumentation._getSendBatchPatch()); - - // oxlint-disable-next-line typescript/unbound-method -- property check, the method is never called - if (isWrapped(newProducer.send)) { - instrumentation._unwrap(newProducer, 'send'); - } - instrumentation._wrap(newProducer, 'send', instrumentation._getSendPatch()); - - // oxlint-disable-next-line typescript/unbound-method -- property check, the method is never called - if (isWrapped(newProducer.transaction)) { - instrumentation._unwrap(newProducer, 'transaction'); - } - instrumentation._wrap(newProducer, 'transaction', instrumentation._getProducerTransactionPatch()); - - return newProducer; - }; - }; - } - - private _getConsumerRunPatch() { - const instrumentation = this; - return (original: Consumer['run']) => { - return function run(this: Consumer, ...args: Parameters): ReturnType { - const config = args[0]; - if (config?.eachMessage) { - if (isWrapped(config.eachMessage)) { - instrumentation._unwrap(config, 'eachMessage'); - } - instrumentation._wrap(config, 'eachMessage', instrumentation._getConsumerEachMessagePatch()); - } - if (config?.eachBatch) { - if (isWrapped(config.eachBatch)) { - instrumentation._unwrap(config, 'eachBatch'); - } - instrumentation._wrap(config, 'eachBatch', instrumentation._getConsumerEachBatchPatch()); - } - return original.call(this, config); - }; - }; - } - - private _getConsumerEachMessagePatch() { - return (original: ConsumerRunConfig['eachMessage']) => { - return function eachMessage(this: unknown, ...args: Parameters): Promise { - const payload = args[0]; - const sentryTrace = getHeaderAsString(payload.message.headers, 'sentry-trace'); - const baggage = getHeaderAsString(payload.message.headers, 'baggage'); - - // Continue the producer's trace so the consumer span is parented to the message's producer. - return continueTrace({ sentryTrace, baggage }, () => { - const span = startConsumerSpan({ - topic: payload.topic, - message: payload.message, - operationType: MESSAGING_OPERATION_TYPE_VALUE_PROCESS, - attributes: { - [ATTR_MESSAGING_DESTINATION_PARTITION_ID]: String(payload.partition), - }, - }); - - const eachMessagePromise = withActiveSpan(span, () => { - return original!.apply(this, args); - }); - return endSpansOnPromise([span], eachMessagePromise); - }); - }; - }; - } - - private _getConsumerEachBatchPatch() { - return (original: ConsumerRunConfig['eachBatch']) => { - return function eachBatch(this: unknown, ...args: Parameters): Promise { - const payload = args[0]; - // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#topic-with-multiple-consumers - // A batch pull aggregates messages from many producers, so the receiving span is a fresh root - // trace and each processed message links back to its own producer span. - const receivingSpan = startNewTrace(() => - startConsumerSpan({ - topic: payload.batch.topic, - message: undefined, - operationType: MESSAGING_OPERATION_TYPE_VALUE_RECEIVE, - attributes: { - [MESSAGING_BATCH_MESSAGE_COUNT]: payload.batch.messages.length, - [ATTR_MESSAGING_DESTINATION_PARTITION_ID]: String(payload.batch.partition), - }, - }), - ); - - return withActiveSpan(receivingSpan, () => { - const spans: Span[] = [receivingSpan]; - payload.batch.messages.forEach((message: KafkaMessage) => { - spans.push( - startConsumerSpan({ - topic: payload.batch.topic, - message, - operationType: MESSAGING_OPERATION_TYPE_VALUE_PROCESS, - links: getLinksFromHeaders(message.headers), - attributes: { - [ATTR_MESSAGING_DESTINATION_PARTITION_ID]: String(payload.batch.partition), - }, - }), - ); - }); - const batchMessagePromise: Promise = original!.apply(this, args); - return endSpansOnPromise(spans, batchMessagePromise); - }); - }; - }; - } - - private _getProducerTransactionPatch() { - const instrumentation = this; - return (original: Producer['transaction']) => { - return function transaction( - this: Producer, - ...args: Parameters - ): ReturnType { - const transactionSpan = startInactiveSpan({ name: 'transaction' }); - - const transactionPromise = original.apply(this, args); - - transactionPromise - .then((transaction: Transaction) => { - // oxlint-disable-next-line typescript/unbound-method -- re-bound below via `.apply(this, args)` - const originalSend = transaction.send; - transaction.send = function send(this: Transaction, ...args) { - return withActiveSpan(transactionSpan, () => { - const patched = instrumentation._getSendPatch()(originalSend); - return patched.apply(this, args).catch((err: any) => { - transactionSpan.setStatus({ - code: SPAN_STATUS_ERROR, - message: err?.message, - }); - throw err; - }); - }); - }; - - // oxlint-disable-next-line typescript/unbound-method -- re-bound below via `.apply(this, args)` - const originalSendBatch = transaction.sendBatch; - transaction.sendBatch = function sendBatch(this: Transaction, ...args) { - return withActiveSpan(transactionSpan, () => { - const patched = instrumentation._getSendBatchPatch()(originalSendBatch); - return patched.apply(this, args).catch((err: any) => { - transactionSpan.setStatus({ - code: SPAN_STATUS_ERROR, - message: err?.message, - }); - throw err; - }); - }); - }; - - // oxlint-disable-next-line typescript/unbound-method -- re-bound below via `.apply(this, args)` - const originalCommit = transaction.commit; - transaction.commit = function commit(this: Transaction, ...args) { - const originCommitPromise = originalCommit.apply(this, args).then(() => { - transactionSpan.setStatus({ code: SPAN_STATUS_OK }); - }); - return endSpansOnPromise([transactionSpan], originCommitPromise); - }; - - // oxlint-disable-next-line typescript/unbound-method -- re-bound below via `.apply(this, args)` - const originalAbort = transaction.abort; - transaction.abort = function abort(this: Transaction, ...args) { - const originAbortPromise = originalAbort.apply(this, args); - return endSpansOnPromise([transactionSpan], originAbortPromise); - }; - }) - .catch((err: any) => { - transactionSpan.setStatus({ - code: SPAN_STATUS_ERROR, - message: err?.message, - }); - transactionSpan.end(); - }); - - return transactionPromise; - }; - }; - } - - private _getSendBatchPatch() { - return (original: Producer['sendBatch'] | Transaction['sendBatch']) => { - return function sendBatch( - this: Producer | Transaction, - ...args: Parameters - ): ReturnType { - const batch = args[0]; - const messages = batch.topicMessages || []; - - const spans: Span[] = []; - - messages.forEach((topicMessage: any) => { - topicMessage.messages.forEach((message: any) => { - spans.push(startProducerSpan(topicMessage.topic, message)); - }); - }); - const origSendResult: Promise = original.apply(this, args); - return endSpansOnPromise(spans, origSendResult); - }; - }; - } - - private _getSendPatch() { - return (original: Producer['send'] | Transaction['send']) => { - return function send( - this: Producer | Transaction, - ...args: Parameters - ): ReturnType { - const record = args[0]; - const spans: Span[] = record.messages.map((message: any) => { - return startProducerSpan(record.topic, message); - }); - - const origSendResult: Promise = original.apply(this, args); - return endSpansOnPromise(spans, origSendResult); - }; - }; - } -} diff --git a/packages/node/src/integrations/tracing/kafka/vendored/kafkajs-types.ts b/packages/node/src/integrations/tracing/kafka/vendored/kafkajs-types.ts deleted file mode 100644 index d6db2e7a486e..000000000000 --- a/packages/node/src/integrations/tracing/kafka/vendored/kafkajs-types.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Simplified types inlined from kafkajs/types/index.d.ts. - * Only includes members accessed by this instrumentation. - */ - -type Sender = { - send(record: any): Promise; - sendBatch(batch: any): Promise; -}; - -export type Producer = Sender & { - connect(): Promise; - disconnect(): Promise; - isIdempotent(): boolean; - transaction(): Promise; - [key: string]: any; -}; - -export type Transaction = Sender & { - sendOffsets(offsets: any): Promise; - commit(): Promise; - abort(): Promise; - isActive(): boolean; -}; - -export type Consumer = { - connect(): Promise; - disconnect(): Promise; - subscribe(subscription: any): Promise; - run(config?: any): Promise; - [key: string]: any; -}; - -export declare class Kafka { - consumer(config: any): Consumer; - producer(config?: any): Producer; - [key: string]: any; -} - -export interface Message { - key?: Buffer | string | null; - value: Buffer | string | null; - partition?: number; - headers?: Record; - timestamp?: string; -} - -export type KafkaMessage = { [key: string]: any } & Message; - -export type RecordMetadata = { - topicName: string; - partition: number; - errorCode: number; - offset?: string; - timestamp?: string; - baseOffset?: string; - logAppendTime?: string; - logStartOffset?: string; -}; - -export interface EachMessagePayload { - topic: string; - partition: number; - message: KafkaMessage; - heartbeat(): Promise; - pause(): () => void; -} - -export interface EachBatchPayload { - batch: any; - resolveOffset(offset: string): void; - heartbeat(): Promise; - pause(): () => void; - commitOffsetsIfNecessary(offsets?: any): Promise; - uncommittedOffsets(): any; - isRunning(): boolean; - isStale(): boolean; -} - -export type EachMessageHandler = (payload: EachMessagePayload) => Promise; -export type EachBatchHandler = (payload: EachBatchPayload) => Promise; - -export type ConsumerRunConfig = { - autoCommit?: boolean; - autoCommitInterval?: number | null; - autoCommitThreshold?: number | null; - eachBatchAutoResolve?: boolean; - partitionsConsumedConcurrently?: number; - eachBatch?: EachBatchHandler; - eachMessage?: EachMessageHandler; -}; diff --git a/packages/node/src/integrations/tracing/kafka/vendored/semconv.ts b/packages/node/src/integrations/tracing/kafka/vendored/semconv.ts deleted file mode 100644 index 210867048709..000000000000 --- a/packages/node/src/integrations/tracing/kafka/vendored/semconv.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors, Aspecto - * SPDX-License-Identifier: Apache-2.0 - * - * NOTICE from the Sentry authors: - * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-kafkajs - * - Upstream version: @opentelemetry/instrumentation-kafkajs@0.27.0 - * - Metric semantic conventions dropped; `error.type` inlined from `@opentelemetry/semantic-conventions` - */ - -/* - * This file contains a copy of unstable semantic convention definitions - * used by this package. - * @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv - */ - -/** - * The identifier of the partition messages are sent to or received from, unique within the `messaging.destination.name`. - * - * @example "1" - * - * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. - */ -export const ATTR_MESSAGING_DESTINATION_PARTITION_ID = 'messaging.destination.partition.id' as const; - -/** - * Message keys in Kafka are used for grouping alike messages to ensure they're processed on the same partition. They differ from `messaging.message.id` in that they're not unique. If the key is `null`, the attribute **MUST NOT** be set. - * - * @example "myKey" - * - * @note If the key type is not string, it's string representation has to be supplied for the attribute. If the key has no unambiguous, canonical string form, don't include its value. - * - * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. - */ -export const ATTR_MESSAGING_KAFKA_MESSAGE_KEY = 'messaging.kafka.message.key' as const; - -/** - * A boolean that is true if the message is a tombstone. - * - * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. - */ -export const ATTR_MESSAGING_KAFKA_MESSAGE_TOMBSTONE = 'messaging.kafka.message.tombstone' as const; - -/** - * The offset of a record in the corresponding Kafka partition. - * - * @example 42 - * - * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. - */ -export const ATTR_MESSAGING_KAFKA_OFFSET = 'messaging.kafka.offset' as const; - -/** - * Enum value "process" for attribute `messaging.operation.type`. - */ -export const MESSAGING_OPERATION_TYPE_VALUE_PROCESS = 'process' as const; - -/** - * Enum value "receive" for attribute `messaging.operation.type`. - */ -export const MESSAGING_OPERATION_TYPE_VALUE_RECEIVE = 'receive' as const; - -/** - * Enum value "send" for attribute `messaging.operation.type`. - */ -export const MESSAGING_OPERATION_TYPE_VALUE_SEND = 'send' as const; - -/** - * Enum value "kafka" for attribute `messaging.system`. - */ -export const MESSAGING_SYSTEM_VALUE_KAFKA = 'kafka' as const; - -/** - * Enum value "_OTHER" for attribute `error.type`. A fallback error value to be used when - * the instrumentation doesn't define a custom value. - */ -export const ERROR_TYPE_VALUE_OTHER = '_OTHER' as const; diff --git a/packages/node/src/integrations/tracing/kafka/vendored/utils.ts b/packages/node/src/integrations/tracing/kafka/vendored/utils.ts deleted file mode 100644 index d9a2bd88acd8..000000000000 --- a/packages/node/src/integrations/tracing/kafka/vendored/utils.ts +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors, Aspecto - * SPDX-License-Identifier: Apache-2.0 - * - * NOTICE from the Sentry authors: - * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-kafkajs - * - Upstream version: @opentelemetry/instrumentation-kafkajs@0.27.0 - * - Span creation extracted here and migrated to the @sentry/core API; origin folded into span creation - */ - -import { TraceFlags } from '@opentelemetry/api'; -import { - ERROR_TYPE, - MESSAGING_DESTINATION_NAME, - MESSAGING_OPERATION_NAME, - MESSAGING_OPERATION_TYPE, - MESSAGING_SYSTEM, - SENTRY_KIND, -} from '@sentry/conventions/attributes'; -import type { Span, SpanAttributes, SpanLink } from '@sentry/core'; -import { - getTraceData, - propagationContextFromHeaders, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SPAN_STATUS_ERROR, - startInactiveSpan, -} from '@sentry/core'; -import type { KafkaMessage, Message } from './kafkajs-types'; -import { - ATTR_MESSAGING_DESTINATION_PARTITION_ID, - ATTR_MESSAGING_KAFKA_MESSAGE_KEY, - ATTR_MESSAGING_KAFKA_MESSAGE_TOMBSTONE, - ATTR_MESSAGING_KAFKA_OFFSET, - ERROR_TYPE_VALUE_OTHER, - MESSAGING_OPERATION_TYPE_VALUE_RECEIVE, - MESSAGING_OPERATION_TYPE_VALUE_SEND, - MESSAGING_SYSTEM_VALUE_KAFKA, -} from './semconv'; - -const PRODUCER_ORIGIN = 'auto.kafkajs.otel.producer'; -const CONSUMER_ORIGIN = 'auto.kafkajs.otel.consumer'; - -export interface ConsumerSpanOptions { - topic: string; - message: KafkaMessage | undefined; - operationType: string; - attributes: SpanAttributes; - links?: SpanLink[]; -} - -/** - * Reads a header value off a kafkajs message as a string. kafkajs delivers headers as `Buffer`s (or - * arrays of them), so we normalize to a string before handing them to Sentry's trace helpers. - */ -export function getHeaderAsString(headers: KafkaMessage['headers'], key: string): string | undefined { - const value = headers?.[key]; - if (value == null) { - return undefined; - } - return Array.isArray(value) ? value[0]?.toString() : value.toString(); -} - -/** - * Builds a span link to the producer span carried in the message headers, mirroring the upstream - * behavior of linking each batch-processed message to its originating producer span. - */ -export function getLinksFromHeaders(headers: KafkaMessage['headers']): SpanLink[] | undefined { - const sentryTrace = getHeaderAsString(headers, 'sentry-trace'); - if (!sentryTrace) { - return undefined; - } - - const { traceId, parentSpanId, sampled } = propagationContextFromHeaders( - sentryTrace, - getHeaderAsString(headers, 'baggage'), - ); - if (!parentSpanId) { - return undefined; - } - - return [ - { - context: { - traceId, - spanId: parentSpanId, - isRemote: true, - traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE, - }, - }, - ]; -} - -/** Starts an inactive consumer (process/receive) span carrying the kafkajs messaging attributes. */ -export function startConsumerSpan({ topic, message, operationType, links, attributes }: ConsumerSpanOptions): Span { - const operationName = - operationType === MESSAGING_OPERATION_TYPE_VALUE_RECEIVE - ? 'poll' // for batch processing spans - : operationType; // for individual message processing spans - - return startInactiveSpan({ - name: `${operationName} ${topic}`, - links, - attributes: { - [SENTRY_KIND]: operationType === MESSAGING_OPERATION_TYPE_VALUE_RECEIVE ? 'client' : 'consumer', - ...attributes, - [MESSAGING_SYSTEM]: MESSAGING_SYSTEM_VALUE_KAFKA, - [MESSAGING_DESTINATION_NAME]: topic, - [MESSAGING_OPERATION_TYPE]: operationType, - [MESSAGING_OPERATION_NAME]: operationName, - [ATTR_MESSAGING_KAFKA_MESSAGE_KEY]: message?.key ? String(message.key) : undefined, - [ATTR_MESSAGING_KAFKA_MESSAGE_TOMBSTONE]: message?.key && message.value === null ? true : undefined, - [ATTR_MESSAGING_KAFKA_OFFSET]: message?.offset, - // Mirror the upstream behavior of only tagging per-message processing spans (not the batch - // receiving span, which carries no message) with the auto origin. - ...(message ? { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: CONSUMER_ORIGIN } : {}), - }, - }); -} - -/** Starts an inactive producer span and propagates its trace into the message headers. */ -export function startProducerSpan(topic: string, message: Message): Span { - const span = startInactiveSpan({ - name: `send ${topic}`, - attributes: { - [SENTRY_KIND]: 'producer', - [MESSAGING_SYSTEM]: MESSAGING_SYSTEM_VALUE_KAFKA, - [MESSAGING_DESTINATION_NAME]: topic, - [ATTR_MESSAGING_KAFKA_MESSAGE_KEY]: message.key ? String(message.key) : undefined, - [ATTR_MESSAGING_KAFKA_MESSAGE_TOMBSTONE]: message.key && message.value === null ? true : undefined, - [ATTR_MESSAGING_DESTINATION_PARTITION_ID]: - message.partition !== undefined ? String(message.partition) : undefined, - [MESSAGING_OPERATION_NAME]: 'send', - [MESSAGING_OPERATION_TYPE]: MESSAGING_OPERATION_TYPE_VALUE_SEND, - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: PRODUCER_ORIGIN, - }, - }); - - // Propagate the producer span's trace to consumers via the message headers. - message.headers = message.headers ?? {}; - const traceData = getTraceData({ span }); - if (traceData['sentry-trace']) { - message.headers['sentry-trace'] = traceData['sentry-trace']; - } - if (traceData.baggage) { - message.headers['baggage'] = traceData.baggage; - } - - return span; -} - -/** - * Resolves once `sendPromise` settles, ending all `spans` and, on failure, marking them with the - * error status and `error.type` before re-throwing. - */ -export function endSpansOnPromise(spans: Span[], sendPromise: Promise): Promise { - return Promise.resolve(sendPromise) - .catch(reason => { - let errorMessage: string | undefined; - let errorType: string = ERROR_TYPE_VALUE_OTHER; - if (typeof reason === 'string' || reason === undefined) { - errorMessage = reason; - } else if (typeof reason === 'object' && Object.prototype.hasOwnProperty.call(reason, 'message')) { - errorMessage = reason.message; - errorType = reason.constructor.name; - } - - spans.forEach(span => { - span.setAttribute(ERROR_TYPE, errorType); - span.setStatus({ - code: SPAN_STATUS_ERROR, - message: errorMessage, - }); - }); - - throw reason; - }) - .finally(() => { - spans.forEach(span => span.end()); - }); -}