diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index 4bf1f6bae01e..1aaee273a2e4 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -54,5 +54,12 @@ }, "volta": { "extends": "../../package.json" - } + }, + "sideEffects": [ + "./cjs/index.js", + "./esm/index.js", + "./build/npm/cjs/index.js", + "./build/npm/esm/index.js", + "./src/index.ts" + ] } diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts index d8d78c6350bb..21b5f209d3a8 100644 --- a/packages/opentelemetry-node/src/index.ts +++ b/packages/opentelemetry-node/src/index.ts @@ -1,101 +1,3 @@ -import { Context } from '@opentelemetry/api'; -import { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { getCurrentHub } from '@sentry/core'; -import { Span as SentrySpan } from '@sentry/types'; +import '@sentry/tracing'; -/** - * Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via - * the Sentry SDK. - */ -export class SentrySpanProcessor implements OtelSpanProcessor { - private readonly _map: Record = {}; - - /** - * @inheritDoc - */ - public onStart(otelSpan: OtelSpan, _parentContext: Context): void { - const hub = getCurrentHub(); - if (!hub) { - return; - } - const scope = hub.getScope(); - if (!scope) { - return; - } - - // if isSentryRequest(otelSpan) return; - - const otelSpanId = otelSpan.spanContext().spanId; - - const sentryParentSpan = scope.getSpan(); - if (sentryParentSpan) { - const sentryChildSpan = sentryParentSpan.startChild({ - description: otelSpan.name, - // instrumentor: 'otel', - startTimestamp: otelSpan.startTime[0], - }); - sentryChildSpan.spanId = otelSpanId; - - this._map[otelSpanId] = [sentryChildSpan, sentryParentSpan]; - scope.setSpan(sentryChildSpan); - } else { - // const traceCtx = getTraceData(otelSpan); - const transaction = hub.startTransaction({ - name: otelSpan.name, - // ...traceCtx, - // instrumentor: 'otel', - startTimestamp: otelSpan.startTime[0], - }); - transaction.spanId = otelSpanId; - - this._map[otelSpanId] = [transaction, undefined]; - scope.setSpan(transaction); - } - } - - /** - * @inheritDoc - */ - public onEnd(otelSpan: OtelSpan): void { - const hub = getCurrentHub(); - if (!hub) { - return; - } - const scope = hub.getScope(); - if (!scope) { - return; - } - - const otelSpanId = otelSpan.spanContext().spanId; - const mapVal = this._map[otelSpanId]; - - if (mapVal) { - const [sentrySpan, sentryParentSpan] = mapVal; - - // updateSpanWithOtelData(sentrySpan, otelSpan); - - sentrySpan.finish(otelSpan.endTime[0]); - scope.setSpan(sentryParentSpan); - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete this._map[otelSpanId]; - } - } - - /** - * @inheritDoc - */ - public shutdown(): Promise { - return Promise.resolve(); - } - - /** - * @inheritDoc - */ - public async forceFlush(): Promise { - const client = getCurrentHub().getClient(); - if (client) { - return client.flush().then(); - } - return Promise.resolve(); - } -} +export { SentrySpanProcessor } from './spanprocessor'; diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts new file mode 100644 index 000000000000..ef71c9cbef5b --- /dev/null +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -0,0 +1,115 @@ +import { Context } from '@opentelemetry/api'; +import { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { getCurrentHub } from '@sentry/core'; +import { Span as SentrySpan, TransactionContext } from '@sentry/types'; + +/** + * Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via + * the Sentry SDK. + */ +export class SentrySpanProcessor implements OtelSpanProcessor { + private readonly _map: Record = {}; + + /** + * @inheritDoc + */ + public onStart(otelSpan: OtelSpan, _parentContext: Context): void { + const hub = getCurrentHub(); + if (!hub) { + return; + } + const scope = hub.getScope(); + if (!scope) { + return; + } + + // if isSentryRequest(otelSpan) return; + + const otelSpanId = otelSpan.spanContext().spanId; + + const sentryParentSpan = scope.getSpan(); + if (sentryParentSpan) { + const sentryChildSpan = sentryParentSpan.startChild({ + description: otelSpan.name, + // instrumentor: 'otel', + startTimestamp: otelSpan.startTime[0], + }); + sentryChildSpan.spanId = otelSpanId; + console.log(sentryParentSpan, sentryChildSpan, otelSpan); + + this._map[otelSpanId] = [sentryChildSpan, sentryParentSpan]; + scope.setSpan(sentryChildSpan); + } else { + const traceCtx = getTraceData(otelSpan); + const transaction = hub.startTransaction({ + name: otelSpan.name, + ...traceCtx, + // instrumentor: 'otel', + startTimestamp: otelSpan.startTime[0], + }); + transaction.spanId = otelSpanId; + + this._map[otelSpanId] = [transaction, undefined]; + + scope.setSpan(transaction); + } + } + + /** + * @inheritDoc + */ + public onEnd(otelSpan: OtelSpan): void { + const hub = getCurrentHub(); + if (!hub) { + return; + } + const scope = hub.getScope(); + if (!scope) { + return; + } + + const otelSpanId = otelSpan.spanContext().spanId; + const mapVal = this._map[otelSpanId]; + + if (mapVal) { + const [sentrySpan, sentryParentSpan] = mapVal; + + // updateSpanWithOtelData(sentrySpan, otelSpan); + + sentrySpan.finish(otelSpan.endTime[0]); + scope.setSpan(sentryParentSpan); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this._map[otelSpanId]; + } + } + + /** + * @inheritDoc + */ + public shutdown(): Promise { + return Promise.resolve(); + } + + /** + * @inheritDoc + */ + public async forceFlush(): Promise { + const client = getCurrentHub().getClient(); + if (client) { + return client.flush().then(); + } + return Promise.resolve(); + } +} + +function getTraceData(otelSpan: OtelSpan): Partial { + const spanContext = otelSpan.spanContext(); + const traceId = spanContext.traceId; + const spanId = spanContext.spanId; + + const parentSpanId = otelSpan.parentSpanId; + return { spanId, traceId, parentSpanId }; +} + +// function updateSpanWithOtelData(sentrySpan: SentrySpan, otelSpan: OtelSpan): void { +// } diff --git a/packages/opentelemetry-node/test/index.test.ts b/packages/opentelemetry-node/test/index.test.ts deleted file mode 100644 index 13c12febdc85..000000000000 --- a/packages/opentelemetry-node/test/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { test } from '../src'; - -describe('index', () => { - it('runs', () => { - test(); - }); -}); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts new file mode 100644 index 000000000000..fcd55e063c16 --- /dev/null +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -0,0 +1,80 @@ +import * as OpenTelemetry from '@opentelemetry/api'; +import { BasicTracerProvider, Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; +import { Hub, makeMain } from '@sentry/core'; +import { addExtensionMethods, Span as SentrySpan, Transaction } from '@sentry/tracing'; + +import { SentrySpanProcessor } from '../src/spanprocessor'; + +// Integration Test of SentrySpanProcessor + +beforeAll(() => { + addExtensionMethods(); +}); + +describe('SentrySpanProcessor', () => { + let hub: Hub; + beforeEach(() => { + hub = new Hub(); + makeMain(hub); + + const provider = new BasicTracerProvider(); + provider.addSpanProcessor(new SentrySpanProcessor()); + provider.register(); + }); + + describe('onStart', () => { + it('create a transaction', () => { + const otelSpan = OpenTelemetry.trace.getTracer('default').startSpan('GET /users') as OtelSpan; + const sentrySpanTransaction = hub.getScope()?.getSpan() as Transaction; + expect(sentrySpanTransaction).toBeInstanceOf(Transaction); + + // Make sure name is set + expect(sentrySpanTransaction?.name).toBe('GET /users'); + + // Enforce we use otel timestamps + expect(sentrySpanTransaction.startTimestamp).toEqual(otelSpan.startTime[0]); + + // Check for otel trace context + expect(sentrySpanTransaction.traceId).toEqual(otelSpan.spanContext().traceId); + expect(sentrySpanTransaction.parentSpanId).toEqual(otelSpan.parentSpanId); + expect(sentrySpanTransaction.spanId).toEqual(otelSpan.spanContext().spanId); + }); + + it.only('creates a child span if there is a running transaction', () => { + const tracer = OpenTelemetry.trace.getTracer('default'); + + tracer.startActiveSpan('GET /users', parentOtelSpan => { + // console.log((parentOtelSpan as any).spanContext()); + // console.log(hub.getScope()?.getSpan()?.traceId); + tracer.startActiveSpan('SELECT * FROM users;', child => { + const childOtelSpan = child as OtelSpan; + + const sentrySpan = hub.getScope()?.getSpan(); + expect(sentrySpan).toBeInstanceOf(SentrySpan); + // console.log(hub.getScope()?.getSpan()?.traceId); + // console.log(sentrySpan); + + // Make sure name is set + expect(sentrySpan?.description).toBe('SELECT * FROM users;'); + + // Enforce we use otel timestamps + expect(sentrySpan?.startTimestamp).toEqual(childOtelSpan.startTime[0]); + + // Check for otel trace context + expect(sentrySpan?.spanId).toEqual(childOtelSpan.spanContext().spanId); + + childOtelSpan.end(); + }); + + parentOtelSpan.end(); + }); + }); + }); + + // it('Creates a transaction if there is no running ', () => { + // const otelSpan = OpenTelemetry.trace.getTracer('default').startSpan('GET /users') as OtelSpan; + // processor.onStart(otelSpan, OpenTelemetry.context.active()); + + // const sentrySpanTransaction = hub.getScope()?.getSpan() as Transaction; + // }); +});