From 4d64e3a6c8e30f170158a16fff1a7d8a87ecdea7 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 31 Oct 2022 14:50:50 +0100 Subject: [PATCH 1/7] feat(otel): Add propogator --- packages/opentelemetry-node/package.json | 2 ++ packages/opentelemetry-node/src/propogator.ts | 29 +++++++++++++++++++ yarn.lock | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 packages/opentelemetry-node/src/propogator.ts diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index c0ba088886e8..8e75d9457a9a 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -24,11 +24,13 @@ }, "peerDependencies": { "@opentelemetry/api": "1.x", + "@opentelemetry/core": "1.x", "@opentelemetry/sdk-trace-base": "1.x", "@opentelemetry/semantic-conventions": "1.x" }, "devDependencies": { "@opentelemetry/api": "^1.2.0", + "@opentelemetry/core": "^1.7.0", "@opentelemetry/sdk-trace-base": "^1.7.0", "@opentelemetry/sdk-trace-node": "^1.7.0" }, diff --git a/packages/opentelemetry-node/src/propogator.ts b/packages/opentelemetry-node/src/propogator.ts new file mode 100644 index 000000000000..66b7c022dc91 --- /dev/null +++ b/packages/opentelemetry-node/src/propogator.ts @@ -0,0 +1,29 @@ +import { Context as OpenTelemetryContext, TextMapPropagator } from '@opentelemetry/api'; + +const SENTRY_TRACE_HEADER = 'sentry-trace'; + +const BAGGAGE_HEADER = 'baggage'; + +/** + * Injects and extracts `sentry-trace` and `baggage` headers from carriers. + */ +export class SentryPropogator implements TextMapPropagator { + /** + * @inheritDoc + */ + public inject(context: OpenTelemetryContext, carrier: unknown, setter: TextMapSetter): void {} + + /** + * @inheritDoc + */ + public extract(context: OpenTelemetryContext, carrier: unknown, getter: TextMapGetter): OpenTelemetryContext { + return context; + } + + /** + * @inheritDoc + */ + public fields(): string[] { + return [SENTRY_TRACE_HEADER, BAGGAGE_HEADER]; + } +} diff --git a/yarn.lock b/yarn.lock index e7732a741fdf..6f51104dd44b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3913,7 +3913,7 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.14.0.tgz#c67fc20a4d891447ca1a855d7d70fa79a3533001" integrity sha512-sDOAZcYwynHFTbLo6n8kIbLiVF3a3BLkrmehJUyEbT9F+Smbi47kLGS2gG2g0fjBLR/Lr1InPD7kXL7FaTqEkw== -"@opentelemetry/core@1.7.0": +"@opentelemetry/core@1.7.0", "@opentelemetry/core@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.7.0.tgz#83bdd1b7a4ceafcdffd6590420657caec5f7b34c" integrity sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ== From 0787574f3a480c9e9378676a490753af29dfdda2 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 31 Oct 2022 17:39:23 +0100 Subject: [PATCH 2/7] get it working --- packages/opentelemetry-node/src/constants.ts | 9 +++ packages/opentelemetry-node/src/propogator.ts | 63 +++++++++++++++++-- .../opentelemetry-node/src/spanprocessor.ts | 27 ++++++-- 3 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 packages/opentelemetry-node/src/constants.ts diff --git a/packages/opentelemetry-node/src/constants.ts b/packages/opentelemetry-node/src/constants.ts new file mode 100644 index 000000000000..045baec093b1 --- /dev/null +++ b/packages/opentelemetry-node/src/constants.ts @@ -0,0 +1,9 @@ +import { createContextKey } from '@opentelemetry/api'; + +export const SENTRY_TRACE_HEADER = 'sentry-trace'; + +export const BAGGAGE_HEADER = 'baggage'; + +export const SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY = createContextKey('SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY'); + +export const SENTRY_TRACE_PARENT_KEY = createContextKey('SENTRY_TRACE_PARENT_KEY'); diff --git a/packages/opentelemetry-node/src/propogator.ts b/packages/opentelemetry-node/src/propogator.ts index 66b7c022dc91..c9f9b8684eef 100644 --- a/packages/opentelemetry-node/src/propogator.ts +++ b/packages/opentelemetry-node/src/propogator.ts @@ -1,8 +1,21 @@ -import { Context as OpenTelemetryContext, TextMapPropagator } from '@opentelemetry/api'; +import { + Context as OpenTelemetryContext, + isSpanContextValid, + TextMapGetter, + TextMapPropagator, + TextMapSetter, + trace, + TraceFlags, +} from '@opentelemetry/api'; +import { isTracingSuppressed } from '@opentelemetry/core'; +import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils'; -const SENTRY_TRACE_HEADER = 'sentry-trace'; - -const BAGGAGE_HEADER = 'baggage'; +import { + BAGGAGE_HEADER, + SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, + SENTRY_TRACE_HEADER, + SENTRY_TRACE_PARENT_KEY, +} from './constants'; /** * Injects and extracts `sentry-trace` and `baggage` headers from carriers. @@ -11,13 +24,51 @@ export class SentryPropogator implements TextMapPropagator { /** * @inheritDoc */ - public inject(context: OpenTelemetryContext, carrier: unknown, setter: TextMapSetter): void {} + public inject(context: OpenTelemetryContext, carrier: unknown, setter: TextMapSetter): void { + const spanContext = trace.getSpanContext(context); + if (!spanContext || !isSpanContextValid(spanContext) || isTracingSuppressed(context)) { + return; + } + + // TODO: if sentry span use `parentSpanId`. + // Same `isSentryRequest` as is used in `SentrySpanProcessor`. + // const spanId = isSentryRequest(spanContext) ? spanContext.parentSpanId : spanContext.spanId; + + const traceparent = `${spanContext.traceId}-${spanContext.spanId}-0${ + // eslint-disable-next-line no-bitwise + spanContext.traceFlags & TraceFlags.SAMPLED ? 1 : 0 + }`; + setter.set(carrier, SENTRY_TRACE_HEADER, traceparent); + } /** * @inheritDoc */ public extract(context: OpenTelemetryContext, carrier: unknown, getter: TextMapGetter): OpenTelemetryContext { - return context; + let newContext = context; + + const maybeSentryTraceHeader: string | string[] | undefined = getter.get(carrier, SENTRY_TRACE_HEADER); + if (maybeSentryTraceHeader) { + const header = maybeSentryTraceHeader ? maybeSentryTraceHeader[0] : maybeSentryTraceHeader; + const traceparentData = extractTraceparentData(header); + newContext.setValue(SENTRY_TRACE_PARENT_KEY, traceparentData); + if (traceparentData) { + const traceFlags = traceparentData.parentSampled ? TraceFlags.SAMPLED : TraceFlags.NONE; + const spanContext = { + traceId: traceparentData.traceId || '', + spanId: traceparentData.parentSpanId || '', + isRemote: true, + traceFlags, + }; + newContext = trace.setSpanContext(context, spanContext); + } + } + + const maybeBaggageHeader = getter.get(carrier, BAGGAGE_HEADER); + const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(maybeBaggageHeader); + newContext.setValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, dynamicSamplingContext); + + return newContext; } /** diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index 099d86a9e3e1..f615d4034bc8 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -2,9 +2,10 @@ import { Context } from '@opentelemetry/api'; import { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { getCurrentHub, withScope } from '@sentry/core'; import { Transaction } from '@sentry/tracing'; -import { Span as SentrySpan, TransactionContext } from '@sentry/types'; +import { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types'; import { logger } from '@sentry/utils'; +import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_KEY } from './constants'; import { mapOtelStatus } from './utils/map-otel-status'; import { parseSpanDescription } from './utils/parse-otel-span-description'; @@ -19,7 +20,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { /** * @inheritDoc */ - public onStart(otelSpan: OtelSpan, _parentContext: Context): void { + public onStart(otelSpan: OtelSpan, parentContext: Context): void { const hub = getCurrentHub(); if (!hub) { __DEBUG_BUILD__ && logger.error('SentrySpanProcessor has triggered onStart before a hub has been setup.'); @@ -51,7 +52,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { this._map.set(otelSpanId, sentryChildSpan); } else { - const traceCtx = getTraceData(otelSpan); + const traceCtx = getTraceData(otelSpan, parentContext); const transaction = hub.startTransaction({ name: otelSpan.name, ...traceCtx, @@ -107,13 +108,27 @@ export class SentrySpanProcessor implements OtelSpanProcessor { } } -function getTraceData(otelSpan: OtelSpan): Partial { +function getTraceData(otelSpan: OtelSpan, parentContext: Context): Partial { const spanContext = otelSpan.spanContext(); const traceId = spanContext.traceId; const spanId = spanContext.spanId; - const parentSpanId = otelSpan.parentSpanId; - return { spanId, traceId, parentSpanId }; + + const traceparentData = parentContext.getValue(SENTRY_TRACE_PARENT_KEY) as TraceparentData | undefined; + const dynamicSamplingContext = parentContext.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY) as + | Partial + | undefined; + + return { + spanId, + traceId, + parentSpanId, + metadata: { + // only set dynamic sampling context if sentry-trace header was set + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, + source: 'custom', + }, + }; } function finishTransactionWithContextFromOtelData(transaction: Transaction, otelSpan: OtelSpan): void { From a1dfa06c9cf7508ecece89488c05a042018e30dc Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 31 Oct 2022 17:45:47 +0100 Subject: [PATCH 3/7] set baggage on outgoing header --- packages/opentelemetry-node/src/constants.ts | 6 +++-- packages/opentelemetry-node/src/propogator.ts | 23 +++++++++++++++---- .../opentelemetry-node/src/spanprocessor.ts | 10 ++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/opentelemetry-node/src/constants.ts b/packages/opentelemetry-node/src/constants.ts index 045baec093b1..c171217b4957 100644 --- a/packages/opentelemetry-node/src/constants.ts +++ b/packages/opentelemetry-node/src/constants.ts @@ -2,8 +2,10 @@ import { createContextKey } from '@opentelemetry/api'; export const SENTRY_TRACE_HEADER = 'sentry-trace'; -export const BAGGAGE_HEADER = 'baggage'; +export const SENTRY_BAGGAGE_HEADER = 'baggage'; export const SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY = createContextKey('SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY'); -export const SENTRY_TRACE_PARENT_KEY = createContextKey('SENTRY_TRACE_PARENT_KEY'); +export const SENTRY_TRACE_PARENT_CONTEXT_KEY = createContextKey('SENTRY_TRACE_PARENT_CONTEXT_KEY'); + +export const SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY = createContextKey('SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY'); diff --git a/packages/opentelemetry-node/src/propogator.ts b/packages/opentelemetry-node/src/propogator.ts index c9f9b8684eef..624b74596fca 100644 --- a/packages/opentelemetry-node/src/propogator.ts +++ b/packages/opentelemetry-node/src/propogator.ts @@ -8,13 +8,19 @@ import { TraceFlags, } from '@opentelemetry/api'; import { isTracingSuppressed } from '@opentelemetry/core'; -import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils'; +import { Transaction } from '@sentry/types'; +import { + baggageHeaderToDynamicSamplingContext, + dynamicSamplingContextToSentryBaggageHeader, + extractTraceparentData, +} from '@sentry/utils'; import { - BAGGAGE_HEADER, + SENTRY_BAGGAGE_HEADER, + SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY, SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_HEADER, - SENTRY_TRACE_PARENT_KEY, + SENTRY_TRACE_PARENT_CONTEXT_KEY, } from './constants'; /** @@ -39,6 +45,15 @@ export class SentryPropogator implements TextMapPropagator { spanContext.traceFlags & TraceFlags.SAMPLED ? 1 : 0 }`; setter.set(carrier, SENTRY_TRACE_HEADER, traceparent); + + const transaction = context.getValue(SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY) as Transaction | undefined; + if (transaction) { + const dynamicSamplingContext = transaction.getDynamicSamplingContext(); + const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + if (sentryBaggageHeader) { + setter.set(carrier, SENTRY_BAGGAGE_HEADER, sentryBaggageHeader); + } + } } /** @@ -51,7 +66,7 @@ export class SentryPropogator implements TextMapPropagator { if (maybeSentryTraceHeader) { const header = maybeSentryTraceHeader ? maybeSentryTraceHeader[0] : maybeSentryTraceHeader; const traceparentData = extractTraceparentData(header); - newContext.setValue(SENTRY_TRACE_PARENT_KEY, traceparentData); + newContext.setValue(SENTRY_TRACE_PARENT_CONTEXT_KEY, traceparentData); if (traceparentData) { const traceFlags = traceparentData.parentSampled ? TraceFlags.SAMPLED : TraceFlags.NONE; const spanContext = { diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index f615d4034bc8..92b192775f4e 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -5,7 +5,11 @@ import { Transaction } from '@sentry/tracing'; import { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_KEY } from './constants'; +import { + SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY, + SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, + SENTRY_TRACE_PARENT_CONTEXT_KEY, +} from './constants'; import { mapOtelStatus } from './utils/map-otel-status'; import { parseSpanDescription } from './utils/parse-otel-span-description'; @@ -61,6 +65,8 @@ export class SentrySpanProcessor implements OtelSpanProcessor { spanId: otelSpanId, }); + parentContext.setValue(SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY, transaction); + this._map.set(otelSpanId, transaction); } } @@ -114,7 +120,7 @@ function getTraceData(otelSpan: OtelSpan, parentContext: Context): Partial | undefined; From a3d87be2b5847fa7c5625b15caffdff6352bb697 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 31 Oct 2022 18:35:34 +0100 Subject: [PATCH 4/7] make SENTRY_SPAN_PROCESSOR_MAP public --- packages/opentelemetry-node/src/constants.ts | 2 -- packages/opentelemetry-node/src/propogator.ts | 13 +++++---- .../opentelemetry-node/src/spanprocessor.ts | 27 ++++++++----------- .../test/spanprocessor.test.ts | 4 +-- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/packages/opentelemetry-node/src/constants.ts b/packages/opentelemetry-node/src/constants.ts index c171217b4957..55f386f2b39f 100644 --- a/packages/opentelemetry-node/src/constants.ts +++ b/packages/opentelemetry-node/src/constants.ts @@ -7,5 +7,3 @@ export const SENTRY_BAGGAGE_HEADER = 'baggage'; export const SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY = createContextKey('SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY'); export const SENTRY_TRACE_PARENT_CONTEXT_KEY = createContextKey('SENTRY_TRACE_PARENT_CONTEXT_KEY'); - -export const SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY = createContextKey('SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY'); diff --git a/packages/opentelemetry-node/src/propogator.ts b/packages/opentelemetry-node/src/propogator.ts index 624b74596fca..880946cb24b7 100644 --- a/packages/opentelemetry-node/src/propogator.ts +++ b/packages/opentelemetry-node/src/propogator.ts @@ -8,7 +8,6 @@ import { TraceFlags, } from '@opentelemetry/api'; import { isTracingSuppressed } from '@opentelemetry/core'; -import { Transaction } from '@sentry/types'; import { baggageHeaderToDynamicSamplingContext, dynamicSamplingContextToSentryBaggageHeader, @@ -17,11 +16,11 @@ import { import { SENTRY_BAGGAGE_HEADER, - SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY, SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_HEADER, SENTRY_TRACE_PARENT_CONTEXT_KEY, } from './constants'; +import { SENTRY_SPAN_PROCESSOR_MAP } from './spanprocessor'; /** * Injects and extracts `sentry-trace` and `baggage` headers from carriers. @@ -46,9 +45,9 @@ export class SentryPropogator implements TextMapPropagator { }`; setter.set(carrier, SENTRY_TRACE_HEADER, traceparent); - const transaction = context.getValue(SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY) as Transaction | undefined; - if (transaction) { - const dynamicSamplingContext = transaction.getDynamicSamplingContext(); + const span = SENTRY_SPAN_PROCESSOR_MAP.get(spanContext.spanId); + if (span && span.transaction) { + const dynamicSamplingContext = span.transaction.getDynamicSamplingContext(); const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); if (sentryBaggageHeader) { setter.set(carrier, SENTRY_BAGGAGE_HEADER, sentryBaggageHeader); @@ -79,7 +78,7 @@ export class SentryPropogator implements TextMapPropagator { } } - const maybeBaggageHeader = getter.get(carrier, BAGGAGE_HEADER); + const maybeBaggageHeader = getter.get(carrier, SENTRY_BAGGAGE_HEADER); const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(maybeBaggageHeader); newContext.setValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, dynamicSamplingContext); @@ -90,6 +89,6 @@ export class SentryPropogator implements TextMapPropagator { * @inheritDoc */ public fields(): string[] { - return [SENTRY_TRACE_HEADER, BAGGAGE_HEADER]; + return [SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]; } } diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index 92b192775f4e..58aaaa37afd5 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -5,22 +5,20 @@ import { Transaction } from '@sentry/tracing'; import { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { - SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY, - SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, - SENTRY_TRACE_PARENT_CONTEXT_KEY, -} from './constants'; +import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; import { mapOtelStatus } from './utils/map-otel-status'; import { parseSpanDescription } from './utils/parse-otel-span-description'; +export const SENTRY_SPAN_PROCESSOR_MAP: Map = new Map< + SentrySpan['spanId'], + SentrySpan +>(); + /** * Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via * the Sentry SDK. */ export class SentrySpanProcessor implements OtelSpanProcessor { - // public only for testing - public readonly _map: Map = new Map(); - /** * @inheritDoc */ @@ -44,7 +42,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { // Otel supports having multiple non-nested spans at the same time // so we cannot use hub.getSpan(), as we cannot rely on this being on the current span - const sentryParentSpan = otelParentSpanId && this._map.get(otelParentSpanId); + const sentryParentSpan = otelParentSpanId && SENTRY_SPAN_PROCESSOR_MAP.get(otelParentSpanId); if (sentryParentSpan) { const sentryChildSpan = sentryParentSpan.startChild({ @@ -54,7 +52,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { spanId: otelSpanId, }); - this._map.set(otelSpanId, sentryChildSpan); + SENTRY_SPAN_PROCESSOR_MAP.set(otelSpanId, sentryChildSpan); } else { const traceCtx = getTraceData(otelSpan, parentContext); const transaction = hub.startTransaction({ @@ -64,10 +62,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { startTimestamp: otelSpan.startTime[0], spanId: otelSpanId, }); - - parentContext.setValue(SENTRY_CURRENT_TRANSACTION_CONTEXT_KEY, transaction); - - this._map.set(otelSpanId, transaction); + SENTRY_SPAN_PROCESSOR_MAP.set(otelSpanId, transaction); } } @@ -76,7 +71,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { */ public onEnd(otelSpan: OtelSpan): void { const otelSpanId = otelSpan.spanContext().spanId; - const sentrySpan = this._map.get(otelSpanId); + const sentrySpan = SENTRY_SPAN_PROCESSOR_MAP.get(otelSpanId); if (!sentrySpan) { __DEBUG_BUILD__ && @@ -92,7 +87,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { sentrySpan.finish(otelSpan.endTime[0]); } - this._map.delete(otelSpanId); + SENTRY_SPAN_PROCESSOR_MAP.delete(otelSpanId); } /** diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index a86645cbe462..4a6b2ec78daa 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -8,7 +8,7 @@ import { Hub, makeMain } from '@sentry/core'; import { addExtensionMethods, Span as SentrySpan, SpanStatusType, Transaction } from '@sentry/tracing'; import { Contexts, Scope } from '@sentry/types'; -import { SentrySpanProcessor } from '../src/spanprocessor'; +import { SENTRY_SPAN_PROCESSOR_MAP, SentrySpanProcessor } from '../src/spanprocessor'; // Integration Test of SentrySpanProcessor @@ -41,7 +41,7 @@ describe('SentrySpanProcessor', () => { }); function getSpanForOtelSpan(otelSpan: OtelSpan | OpenTelemetry.Span) { - return spanProcessor._map.get(otelSpan.spanContext().spanId); + return SENTRY_SPAN_PROCESSOR_MAP.get(otelSpan.spanContext().spanId); } function getContext(transaction: Transaction) { From 4253cffdcccdf35cc41b9ed5abd2e5e1f151344d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 31 Oct 2022 20:01:15 +0100 Subject: [PATCH 5/7] add inject unit tests --- packages/opentelemetry-node/src/index.ts | 1 + packages/opentelemetry-node/src/propogator.ts | 8 +- .../test/propogator.test.ts | 200 ++++++++++++++++++ 3 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 packages/opentelemetry-node/test/propogator.test.ts diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts index 21b5f209d3a8..95ea469b1b74 100644 --- a/packages/opentelemetry-node/src/index.ts +++ b/packages/opentelemetry-node/src/index.ts @@ -1,3 +1,4 @@ import '@sentry/tracing'; export { SentrySpanProcessor } from './spanprocessor'; +export { SentryPropogator } from './propogator'; diff --git a/packages/opentelemetry-node/src/propogator.ts b/packages/opentelemetry-node/src/propogator.ts index 880946cb24b7..4e4184f442cc 100644 --- a/packages/opentelemetry-node/src/propogator.ts +++ b/packages/opentelemetry-node/src/propogator.ts @@ -1,5 +1,5 @@ import { - Context as OpenTelemetryContext, + Context, isSpanContextValid, TextMapGetter, TextMapPropagator, @@ -29,7 +29,7 @@ export class SentryPropogator implements TextMapPropagator { /** * @inheritDoc */ - public inject(context: OpenTelemetryContext, carrier: unknown, setter: TextMapSetter): void { + public inject(context: Context, carrier: unknown, setter: TextMapSetter): void { const spanContext = trace.getSpanContext(context); if (!spanContext || !isSpanContextValid(spanContext) || isTracingSuppressed(context)) { return; @@ -39,7 +39,7 @@ export class SentryPropogator implements TextMapPropagator { // Same `isSentryRequest` as is used in `SentrySpanProcessor`. // const spanId = isSentryRequest(spanContext) ? spanContext.parentSpanId : spanContext.spanId; - const traceparent = `${spanContext.traceId}-${spanContext.spanId}-0${ + const traceparent = `${spanContext.traceId}-${spanContext.spanId}-${ // eslint-disable-next-line no-bitwise spanContext.traceFlags & TraceFlags.SAMPLED ? 1 : 0 }`; @@ -58,7 +58,7 @@ export class SentryPropogator implements TextMapPropagator { /** * @inheritDoc */ - public extract(context: OpenTelemetryContext, carrier: unknown, getter: TextMapGetter): OpenTelemetryContext { + public extract(context: Context, carrier: unknown, getter: TextMapGetter): Context { let newContext = context; const maybeSentryTraceHeader: string | string[] | undefined = getter.get(carrier, SENTRY_TRACE_HEADER); diff --git a/packages/opentelemetry-node/test/propogator.test.ts b/packages/opentelemetry-node/test/propogator.test.ts new file mode 100644 index 000000000000..869681104ef2 --- /dev/null +++ b/packages/opentelemetry-node/test/propogator.test.ts @@ -0,0 +1,200 @@ +import { defaultTextMapSetter, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api'; +import { suppressTracing } from '@opentelemetry/core'; +import { Hub, makeMain } from '@sentry/core'; +import { addExtensionMethods, Transaction } from '@sentry/tracing'; + +import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER } from '../src/constants'; +import { SentryPropogator } from '../src/propogator'; +import { SENTRY_SPAN_PROCESSOR_MAP } from '../src/spanprocessor'; + +beforeAll(() => { + addExtensionMethods(); +}); + +describe('SentryPropogator', () => { + const propogator = new SentryPropogator(); + let carrier: { [key: string]: unknown }; + + beforeEach(() => { + carrier = {}; + }); + + describe('inject', () => { + describe('sentry-trace', () => { + it.each([ + [ + 'should set sentry-trace header when sampled', + { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }, + 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1', + ], + [ + 'should set sentry-trace header when not sampled', + { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + }, + 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0', + ], + [ + 'should NOT set sentry-trace header when traceId is empty', + { + traceId: '', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }, + undefined, + ], + [ + 'should NOT set sentry-trace header when spanId is empty', + { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '', + traceFlags: TraceFlags.NONE, + }, + undefined, + ], + ])('%s', (_name, spanContext, expected) => { + const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); + propogator.inject(context, carrier, defaultTextMapSetter); + expect(carrier[SENTRY_TRACE_HEADER]).toBe(expected); + }); + + it('should NOT set sentry-trace header if instrumentation is supressed', () => { + const spanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext)); + propogator.inject(context, carrier, defaultTextMapSetter); + expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); + }); + }); + + describe('baggage', () => { + const client = { + getOptions: () => ({ + environment: 'production', + release: '1.0.0', + }), + getDsn: () => ({ + publicKey: 'abc', + }), + }; + // @ts-ignore Use mock client for unit tests + const hub: Hub = new Hub(client); + makeMain(hub); + + afterEach(() => { + SENTRY_SPAN_PROCESSOR_MAP.clear(); + }); + + describe.each(['transction', 'span'])('with active %s', type => { + it.each([ + [ + 'should set baggage header when sampled', + { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }, + { + name: 'sampled-transaction', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + sampled: true, + }, + 'sentry-environment=production,sentry-release=1.0.0,sentry-transaction=sampled-transaction,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', + ], + [ + 'should NOT set baggage header when not sampled', + { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + }, + { + name: 'not-sampled-transaction', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + sampled: false, + }, + 'sentry-environment=production,sentry-release=1.0.0,sentry-transaction=not-sampled-transaction,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', + ], + [ + 'should NOT set baggage header when traceId is empty', + { + traceId: '', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }, + { + name: 'empty-traceId-transaction', + traceId: '', + spanId: '6e0c63257de34c92', + sampled: true, + }, + undefined, + ], + [ + 'should NOT set baggage header when spanId is empty', + { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '', + traceFlags: TraceFlags.SAMPLED, + }, + { + name: 'empty-spanId-transaction', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '', + sampled: true, + }, + undefined, + ], + ])('%s', (_name, spanContext, transactionContext, expected) => { + const transaction = new Transaction(transactionContext, hub); + SENTRY_SPAN_PROCESSOR_MAP.set(transaction.spanId, transaction); + if (type === 'span') { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { spanId, ...ctx } = transactionContext; + const span = transaction.startChild({ ...ctx, description: transaction.name }); + SENTRY_SPAN_PROCESSOR_MAP.set(span.spanId, span); + } + const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); + propogator.inject(context, carrier, defaultTextMapSetter); + expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(expected); + }); + + it('should NOT set sentry-trace header if instrumentation is supressed', () => { + const spanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + const transactionContext = { + name: 'sampled-transaction', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + sampled: true, + }; + const transaction = new Transaction(transactionContext, hub); + SENTRY_SPAN_PROCESSOR_MAP.set(transaction.spanId, transaction); + if (type === 'span') { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { spanId, ...ctx } = transactionContext; + const span = transaction.startChild({ ...ctx, description: transaction.name }); + SENTRY_SPAN_PROCESSOR_MAP.set(span.spanId, span); + } + const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext)); + propogator.inject(context, carrier, defaultTextMapSetter); + expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); + }); + }); + }); + }); +}); From d3383b713d5087e7133a00f7a056d1e27f44305a Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 31 Oct 2022 20:06:36 +0100 Subject: [PATCH 6/7] refactor inject tests --- .../test/propogator.test.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/opentelemetry-node/test/propogator.test.ts b/packages/opentelemetry-node/test/propogator.test.ts index 869681104ef2..b00659c46edd 100644 --- a/packages/opentelemetry-node/test/propogator.test.ts +++ b/packages/opentelemetry-node/test/propogator.test.ts @@ -2,6 +2,7 @@ import { defaultTextMapSetter, ROOT_CONTEXT, trace, TraceFlags } from '@opentele import { suppressTracing } from '@opentelemetry/core'; import { Hub, makeMain } from '@sentry/core'; import { addExtensionMethods, Transaction } from '@sentry/tracing'; +import { TransactionContext } from '@sentry/types'; import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER } from '../src/constants'; import { SentryPropogator } from '../src/propogator'; @@ -94,7 +95,23 @@ describe('SentryPropogator', () => { SENTRY_SPAN_PROCESSOR_MAP.clear(); }); - describe.each(['transction', 'span'])('with active %s', type => { + enum PerfType { + Transaction = 'transaction', + Span = 'span', + } + + function createTransactionAndMaybeSpan(type: PerfType, transactionContext: TransactionContext) { + const transaction = new Transaction(transactionContext, hub); + SENTRY_SPAN_PROCESSOR_MAP.set(transaction.spanId, transaction); + if (type === PerfType.Span) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { spanId, ...ctx } = transactionContext; + const span = transaction.startChild({ ...ctx, description: transaction.name }); + SENTRY_SPAN_PROCESSOR_MAP.set(span.spanId, span); + } + } + + describe.each([PerfType.Transaction, PerfType.Span])('with active %s', type => { it.each([ [ 'should set baggage header when sampled', @@ -157,14 +174,7 @@ describe('SentryPropogator', () => { undefined, ], ])('%s', (_name, spanContext, transactionContext, expected) => { - const transaction = new Transaction(transactionContext, hub); - SENTRY_SPAN_PROCESSOR_MAP.set(transaction.spanId, transaction); - if (type === 'span') { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { spanId, ...ctx } = transactionContext; - const span = transaction.startChild({ ...ctx, description: transaction.name }); - SENTRY_SPAN_PROCESSOR_MAP.set(span.spanId, span); - } + createTransactionAndMaybeSpan(type, transactionContext); const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); propogator.inject(context, carrier, defaultTextMapSetter); expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(expected); @@ -182,14 +192,7 @@ describe('SentryPropogator', () => { spanId: '6e0c63257de34c92', sampled: true, }; - const transaction = new Transaction(transactionContext, hub); - SENTRY_SPAN_PROCESSOR_MAP.set(transaction.spanId, transaction); - if (type === 'span') { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { spanId, ...ctx } = transactionContext; - const span = transaction.startChild({ ...ctx, description: transaction.name }); - SENTRY_SPAN_PROCESSOR_MAP.set(span.spanId, span); - } + createTransactionAndMaybeSpan(type, transactionContext); const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext)); propogator.inject(context, carrier, defaultTextMapSetter); expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); @@ -197,4 +200,6 @@ describe('SentryPropogator', () => { }); }); }); + + describe('extract', () => {}); }); From cb2a100268918614b2612c1cc47bd274419ce160 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 31 Oct 2022 20:24:16 +0100 Subject: [PATCH 7/7] add tests for extract --- packages/opentelemetry-node/src/propogator.ts | 8 +-- .../test/propogator.test.ts | 66 ++++++++++++++++++- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/packages/opentelemetry-node/src/propogator.ts b/packages/opentelemetry-node/src/propogator.ts index 4e4184f442cc..e050bb3a2f69 100644 --- a/packages/opentelemetry-node/src/propogator.ts +++ b/packages/opentelemetry-node/src/propogator.ts @@ -63,9 +63,9 @@ export class SentryPropogator implements TextMapPropagator { const maybeSentryTraceHeader: string | string[] | undefined = getter.get(carrier, SENTRY_TRACE_HEADER); if (maybeSentryTraceHeader) { - const header = maybeSentryTraceHeader ? maybeSentryTraceHeader[0] : maybeSentryTraceHeader; + const header = Array.isArray(maybeSentryTraceHeader) ? maybeSentryTraceHeader[0] : maybeSentryTraceHeader; const traceparentData = extractTraceparentData(header); - newContext.setValue(SENTRY_TRACE_PARENT_CONTEXT_KEY, traceparentData); + newContext = newContext.setValue(SENTRY_TRACE_PARENT_CONTEXT_KEY, traceparentData); if (traceparentData) { const traceFlags = traceparentData.parentSampled ? TraceFlags.SAMPLED : TraceFlags.NONE; const spanContext = { @@ -74,13 +74,13 @@ export class SentryPropogator implements TextMapPropagator { isRemote: true, traceFlags, }; - newContext = trace.setSpanContext(context, spanContext); + newContext = trace.setSpanContext(newContext, spanContext); } } const maybeBaggageHeader = getter.get(carrier, SENTRY_BAGGAGE_HEADER); const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(maybeBaggageHeader); - newContext.setValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, dynamicSamplingContext); + newContext = newContext.setValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, dynamicSamplingContext); return newContext; } diff --git a/packages/opentelemetry-node/test/propogator.test.ts b/packages/opentelemetry-node/test/propogator.test.ts index b00659c46edd..3f87b39260de 100644 --- a/packages/opentelemetry-node/test/propogator.test.ts +++ b/packages/opentelemetry-node/test/propogator.test.ts @@ -1,10 +1,15 @@ -import { defaultTextMapSetter, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api'; +import { defaultTextMapGetter, defaultTextMapSetter, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; import { Hub, makeMain } from '@sentry/core'; import { addExtensionMethods, Transaction } from '@sentry/tracing'; import { TransactionContext } from '@sentry/types'; -import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER } from '../src/constants'; +import { + SENTRY_BAGGAGE_HEADER, + SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, + SENTRY_TRACE_HEADER, + SENTRY_TRACE_PARENT_CONTEXT_KEY, +} from '../src/constants'; import { SentryPropogator } from '../src/propogator'; import { SENTRY_SPAN_PROCESSOR_MAP } from '../src/spanprocessor'; @@ -20,6 +25,10 @@ describe('SentryPropogator', () => { carrier = {}; }); + it('returns fields set', () => { + expect(propogator.fields()).toEqual([SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]); + }); + describe('inject', () => { describe('sentry-trace', () => { it.each([ @@ -201,5 +210,56 @@ describe('SentryPropogator', () => { }); }); - describe('extract', () => {}); + describe('extract', () => { + it('sets sentry span context on the context', () => { + const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; + carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; + const context = propogator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + expect(trace.getSpanContext(context)).toEqual({ + isRemote: true, + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + }); + }); + + it('sets defined sentry trace header on context', () => { + const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; + carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; + const context = propogator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual({ + parentSampled: true, + parentSpanId: '6e0c63257de34c92', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + }); + }); + + it('sets undefined sentry trace header on context', () => { + const sentryTraceHeader = undefined; + carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; + const context = propogator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual(undefined); + }); + + it('sets defined dynamic sampling context on context', () => { + const baggage = + 'sentry-environment=production,sentry-release=1.0.0,sentry-transaction=dsc-transaction,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b'; + carrier[SENTRY_BAGGAGE_HEADER] = baggage; + const context = propogator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + expect(context.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY)).toEqual({ + environment: 'production', + public_key: 'abc', + release: '1.0.0', + trace_id: 'd4cda95b652f4a1592b449d5929fda1b', + transaction: 'dsc-transaction', + }); + }); + + it('sets undefined dynamic sampling context on context', () => { + const baggage = ''; + carrier[SENTRY_BAGGAGE_HEADER] = baggage; + const context = propogator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + expect(context.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY)).toEqual(undefined); + }); + }); });