From 44b37e37dc35c6e98c22910026d6e3997ede2f2e Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 13 Feb 2023 18:26:31 +0100 Subject: [PATCH] feat(otel): Convert exception otel events to sentry errors always set message oof prettier add otel context --- .../opentelemetry-node/src/spanprocessor.ts | 49 ++++++++++++++++++- .../test/spanprocessor.test.ts | 40 +++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index f2ebcd6af12e..b96cc424fb0d 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -1,10 +1,11 @@ import type { Context } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import type { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; import { Transaction } from '@sentry/tracing'; import type { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { isString, logger } from '@sentry/utils'; import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; import { isSentryRequestSpan } from './utils/is-sentry-request'; @@ -93,6 +94,12 @@ export class SentrySpanProcessor implements OtelSpanProcessor { * @inheritDoc */ public onEnd(otelSpan: OtelSpan): void { + const hub = getCurrentHub(); + if (!hub) { + __DEBUG_BUILD__ && logger.error('SentrySpanProcessor has triggered onEnd before a hub has been setup.'); + return; + } + const otelSpanId = otelSpan.spanContext().spanId; const sentrySpan = SENTRY_SPAN_PROCESSOR_MAP.get(otelSpanId); @@ -112,6 +119,46 @@ export class SentrySpanProcessor implements OtelSpanProcessor { return; } + otelSpan.events.forEach(event => { + if (event.name !== 'exception') { + return; + } + + const attributes = event.attributes; + if (!attributes) { + return; + } + + const message = attributes[SemanticAttributes.EXCEPTION_MESSAGE]; + const syntheticError = new Error(message as string | undefined); + + const stack = attributes[SemanticAttributes.EXCEPTION_STACKTRACE]; + if (isString(stack)) { + syntheticError.stack = stack; + } + + const type = attributes[SemanticAttributes.EXCEPTION_TYPE]; + if (isString(type)) { + syntheticError.name = type; + } + + hub.captureException(syntheticError, { + captureContext: { + contexts: { + otel: { + attributes: otelSpan.attributes, + resource: otelSpan.resource.attributes, + }, + trace: { + trace_id: otelSpan.spanContext().traceId, + span_id: otelSpan.spanContext().spanId, + parent_span_id: otelSpan.parentSpanId, + }, + }, + }, + }); + }); + if (sentrySpan instanceof Transaction) { updateTransactionWithOtelData(sentrySpan, otelSpan); } else { diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index d07c0b950f64..27a72a1943d9 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -750,6 +750,46 @@ describe('SentrySpanProcessor', () => { trace_id: otelSpan.spanContext().traceId, }); }); + + it('generates Sentry errors from opentelemetry span exception events', () => { + let sentryEvent: any; + let otelSpan: any; + + client = new NodeClient({ + ...DEFAULT_NODE_CLIENT_OPTIONS, + beforeSend: event => { + sentryEvent = event; + return null; + }, + }); + hub = new Hub(client); + makeMain(hub); + + const tracer = provider.getTracer('default'); + + tracer.startActiveSpan('GET /users', parentOtelSpan => { + tracer.startActiveSpan('SELECT * FROM users;', child => { + child.recordException(new Error('this is an otel error!')); + otelSpan = child as OtelSpan; + child.end(); + }); + + parentOtelSpan.end(); + }); + + expect(sentryEvent).toBeDefined(); + expect(sentryEvent.exception).toBeDefined(); + expect(sentryEvent.exception.values[0]).toEqual({ + mechanism: expect.any(Object), + type: 'Error', + value: 'this is an otel error!', + }); + expect(sentryEvent.contexts.trace).toEqual({ + parent_span_id: otelSpan.parentSpanId, + span_id: otelSpan.spanContext().spanId, + trace_id: otelSpan.spanContext().traceId, + }); + }); }); // OTEL expects a custom date format