Skip to content

Commit 80a699d

Browse files
committed
feat(otel): Convert exception otel events to sentry errors
1 parent 2bdda84 commit 80a699d

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

packages/opentelemetry-node/src/spanprocessor.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import type { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@open
44
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
55
import { Transaction } from '@sentry/tracing';
66
import type { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types';
7-
import { logger } from '@sentry/utils';
7+
import { logger, isString } from '@sentry/utils';
8+
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
89

910
import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants';
1011
import { isSentryRequestSpan } from './utils/is-sentry-request';
@@ -93,6 +94,12 @@ export class SentrySpanProcessor implements OtelSpanProcessor {
9394
* @inheritDoc
9495
*/
9596
public onEnd(otelSpan: OtelSpan): void {
97+
const hub = getCurrentHub();
98+
if (!hub) {
99+
__DEBUG_BUILD__ && logger.error('SentrySpanProcessor has triggered onEnd before a hub has been setup.');
100+
return;
101+
}
102+
96103
const otelSpanId = otelSpan.spanContext().spanId;
97104
const sentrySpan = SENTRY_SPAN_PROCESSOR_MAP.get(otelSpanId);
98105

@@ -112,6 +119,45 @@ export class SentrySpanProcessor implements OtelSpanProcessor {
112119
return;
113120
}
114121

122+
otelSpan.events.forEach(event => {
123+
if (event.name !== 'exception') {
124+
return;
125+
}
126+
127+
const attributes = event.attributes;
128+
if (!attributes) {
129+
return;
130+
}
131+
132+
const message = attributes[SemanticAttributes.EXCEPTION_MESSAGE];
133+
if (!isString(message)) {
134+
return;
135+
}
136+
const syntheticError = new Error(message);
137+
138+
const stack = attributes[SemanticAttributes.EXCEPTION_STACKTRACE];
139+
if (isString(stack)) {
140+
syntheticError.stack = stack;
141+
}
142+
143+
const type = attributes[SemanticAttributes.EXCEPTION_TYPE];
144+
if (isString(type)) {
145+
syntheticError.name = type;
146+
}
147+
148+
hub.captureException(syntheticError, {
149+
captureContext: {
150+
contexts: {
151+
trace: {
152+
trace_id: otelSpan.spanContext().traceId,
153+
span_id: otelSpan.spanContext().spanId,
154+
parent_span_id: otelSpan.parentSpanId,
155+
},
156+
},
157+
},
158+
});
159+
});
160+
115161
if (sentrySpan instanceof Transaction) {
116162
updateTransactionWithOtelData(sentrySpan, otelSpan);
117163
} else {

packages/opentelemetry-node/test/spanprocessor.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,46 @@ describe('SentrySpanProcessor', () => {
750750
trace_id: otelSpan.spanContext().traceId,
751751
});
752752
});
753+
754+
it('generates Sentry errors from opentelemetry span exception events', () => {
755+
let sentryEvent: any;
756+
let otelSpan: any;
757+
758+
client = new NodeClient({
759+
...DEFAULT_NODE_CLIENT_OPTIONS,
760+
beforeSend: event => {
761+
sentryEvent = event;
762+
return null;
763+
},
764+
});
765+
hub = new Hub(client);
766+
makeMain(hub);
767+
768+
const tracer = provider.getTracer('default');
769+
770+
tracer.startActiveSpan('GET /users', parentOtelSpan => {
771+
tracer.startActiveSpan('SELECT * FROM users;', child => {
772+
child.recordException(new Error('this is an otel error!'));
773+
otelSpan = child as OtelSpan;
774+
child.end();
775+
});
776+
777+
parentOtelSpan.end();
778+
});
779+
780+
expect(sentryEvent).toBeDefined();
781+
expect(sentryEvent.exception).toBeDefined();
782+
expect(sentryEvent.exception.values[0]).toEqual({
783+
mechanism: expect.any(Object),
784+
type: 'Error',
785+
value: 'this is an otel error!',
786+
});
787+
expect(sentryEvent.contexts.trace).toEqual({
788+
parent_span_id: otelSpan.parentSpanId,
789+
span_id: otelSpan.spanContext().spanId,
790+
trace_id: otelSpan.spanContext().traceId,
791+
});
792+
});
753793
});
754794

755795
// OTEL expects a custom date format

0 commit comments

Comments
 (0)