autotel-sentry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # autotel-sentry
2
+
3
+ Bridge **OpenTelemetry (Autotel)** traces to **Sentry** so you can keep instrumenting with Autotel and send the same traces to Sentry for performance monitoring and error linking.
4
+
5
+ This package is for the **Sentry SDK + OpenTelemetry in the same service** scenario. For OTel-only backends (no Sentry SDK), see [Sentry OTLP](https://docs.sentry.io/concepts/otlp/).
6
+
7
+ This package implements Sentry's [OpenTelemetry traces integration](https://develop.sentry.dev/sdk/telemetry/traces/opentelemetry/) via a `SpanProcessor` and optional `SentryPropagator` for `sentry-trace` and `baggage` headers. The implementation follows that spec and may be updated if Sentry changes it.
8
+
9
+ ## Prerequisites
10
+
11
+ - **Sentry must be initialized before** the OpenTelemetry SDK (and before `init()` from Autotel).
12
+ - Set **`instrumenter: 'otel'`** in `Sentry.init()` so Sentry does not double-instrument. All span/transaction creation is driven by OpenTelemetry; Sentry only consumes them.
13
+ - For linking errors to the active trace, the Sentry SDK should expose `addGlobalEventProcessor`. If it does not (e.g. some SDK versions), the processor still sends spans/transactions; error events may not get trace context attached.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add autotel autotel-sentry @sentry/node
19
+ ```
20
+
21
+ ## Minimal setup
22
+
23
+ 1. Initialize Sentry first (with `instrumenter: 'otel'`).
24
+ 2. Call Autotel `init()` and pass `SentrySpanProcessor` in `spanProcessors`.
25
+
26
+ ```typescript
27
+ import * as Sentry from '@sentry/node';
28
+ import { init } from 'autotel';
29
+ import { createSentrySpanProcessor } from 'autotel-sentry';
30
+
31
+ Sentry.init({
32
+ dsn: process.env.SENTRY_DSN,
33
+ tracesSampleRate: 1.0,
34
+ instrumenter: 'otel',
35
+ });
36
+
37
+ init({
38
+ service: 'my-app',
39
+ spanProcessors: [createSentrySpanProcessor(Sentry)],
40
+ });
41
+ ```
42
+
43
+ Errors captured by Sentry will be linked to the active OpenTelemetry span (trace/span IDs). Spans created by Autotel (e.g. via `autotel-hono`, `autotel-plugins/drizzle`) are sent to Sentry as transactions and child spans. Spans for requests to Sentry's ingestion endpoint are not sent to Sentry.
44
+
45
+ ## Optional: sentry-trace and baggage propagation
46
+
47
+ For cross-service trace continuity and dynamic sampling, register the **SentryPropagator** so `sentry-trace` and `baggage` headers are injected and extracted. Combine it with your existing propagators (e.g. W3C Trace Context and Baggage) using a composite propagator from your OpenTelemetry setup so that outbound requests carry Sentry headers and incoming requests restore them into context.
48
+
49
+ ### Example: Using SentryPropagator with Composite Propagator
50
+
51
+ ```typescript
52
+ import * as Sentry from '@sentry/node';
53
+ import { init } from 'autotel';
54
+ import { createSentrySpanProcessor, SentryPropagator } from 'autotel-sentry';
55
+ import { CompositePropagator } from '@opentelemetry/core';
56
+ import { W3CTraceContextPropagator, W3CBaggagePropagator } from '@opentelemetry/core';
57
+
58
+ // 1. Initialize Sentry first
59
+ Sentry.init({
60
+ dsn: process.env.SENTRY_DSN,
61
+ tracesSampleRate: 1.0,
62
+ instrumenter: 'otel',
63
+ });
64
+
65
+ // 2. Initialize Autotel with Sentry processor AND propagator
66
+ init({
67
+ service: 'my-app',
68
+ spanProcessors: [createSentrySpanProcessor(Sentry)],
69
+
70
+ // Register SentryPropagator alongside W3C propagators
71
+ propagator: new CompositePropagator({
72
+ propagators: [
73
+ new W3CTraceContextPropagator(), // traceparent header
74
+ new W3CBaggagePropagator(), // W3C baggage header
75
+ new SentryPropagator(), // sentry-trace + baggage headers
76
+ ],
77
+ }),
78
+ });
79
+ ```
80
+
81
+ **What this does:**
82
+
83
+ - **Outbound requests** (fetch, http.request, etc.) get `traceparent`, `baggage`, and `sentry-trace` headers injected automatically
84
+ - **Incoming requests** restore trace context from all three header types
85
+ - Sentry's dynamic sampling decisions propagate across services via `baggage` header
86
+ - Full distributed tracing works across services using different backends (OTel collector + Sentry)
87
+
88
+ **When to use:**
89
+
90
+ - Multi-service architecture where some services send to Sentry, others to OTel collectors
91
+ - You want Sentry's dynamic sampling to work across service boundaries
92
+ - You need both W3C Trace Context (for OTel) and Sentry-specific headers
93
+
94
+ ## API
95
+
96
+ - **`createSentrySpanProcessor(sentry)`** – Returns a `SentrySpanProcessor` instance. Pass the `@sentry/node` module (or any object implementing the minimal hub/transaction/span interface).
97
+ - **`SentrySpanProcessor`** – Class implementing OpenTelemetry's `SpanProcessor`. Converts OTel spans to Sentry transactions/spans and turns OTel exception events into Sentry errors.
98
+ - **`SentryPropagator`** – Class implementing OpenTelemetry's `TextMapPropagator` for `sentry-trace` and `baggage` headers.
99
+ - **`SENTRY_PROPAGATION_KEY`** – Context key under which extracted Sentry propagation data is stored (for advanced use).
100
+
101
+ ## References
102
+
103
+ - [Sentry: OpenTelemetry traces](https://develop.sentry.dev/sdk/telemetry/traces/opentelemetry/) – spec this implementation follows
104
+ - [Sentry OTLP](https://docs.sentry.io/concepts/otlp/) – when to use OTLP direct vs SDK + OTel
105
+ - [Autotel init](https://github.com/jagreehal/autotel) – `spanProcessors` and configuration
package/dist/index.cjs ADDED
@@ -0,0 +1,330 @@
1
+ 'use strict';
2
+
3
+ var api = require('@opentelemetry/api');
4
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
5
+
6
+ // src/processor.ts
7
+ function convertOtelTimeToSeconds(time) {
8
+ if (Array.isArray(time)) {
9
+ return time[0] + time[1] / 1e9;
10
+ }
11
+ return time / 1e9;
12
+ }
13
+ var CANONICAL_HTTP_MAP = {
14
+ "400": "failed_precondition",
15
+ "401": "unauthenticated",
16
+ "403": "permission_denied",
17
+ "404": "not_found",
18
+ "409": "aborted",
19
+ "429": "resource_exhausted",
20
+ "499": "cancelled",
21
+ "500": "internal_error",
22
+ "501": "unimplemented",
23
+ "503": "unavailable",
24
+ "504": "deadline_exceeded"
25
+ };
26
+ var CANONICAL_GRPC_MAP = {
27
+ "1": "cancelled",
28
+ "2": "unknown_error",
29
+ "3": "invalid_argument",
30
+ "4": "deadline_exceeded",
31
+ "5": "not_found",
32
+ "6": "already_exists",
33
+ "7": "permission_denied",
34
+ "8": "resource_exhausted",
35
+ "9": "failed_precondition",
36
+ "10": "aborted",
37
+ "11": "out_of_range",
38
+ "12": "unimplemented",
39
+ "13": "internal_error",
40
+ "14": "unavailable",
41
+ "15": "data_loss",
42
+ "16": "unauthenticated"
43
+ };
44
+ function mapOtelStatus(otelSpan) {
45
+ const { status, attributes } = otelSpan;
46
+ const code = status.code;
47
+ if (code !== void 0 && (code < 0 || code > 2)) {
48
+ return "unknown_error";
49
+ }
50
+ if (code === 0 || code === 1) {
51
+ return "ok";
52
+ }
53
+ const httpCode = attributes[semanticConventions.SEMATTRS_HTTP_STATUS_CODE];
54
+ const grpcCode = attributes[semanticConventions.SEMATTRS_RPC_GRPC_STATUS_CODE];
55
+ if (typeof httpCode === "string") {
56
+ const sentryStatus = CANONICAL_HTTP_MAP[httpCode];
57
+ if (sentryStatus) return sentryStatus;
58
+ }
59
+ if (typeof httpCode === "number") {
60
+ const sentryStatus = CANONICAL_HTTP_MAP[String(httpCode)];
61
+ if (sentryStatus) return sentryStatus;
62
+ }
63
+ if (typeof grpcCode === "string") {
64
+ const sentryStatus = CANONICAL_GRPC_MAP[grpcCode];
65
+ if (sentryStatus) return sentryStatus;
66
+ }
67
+ return "unknown_error";
68
+ }
69
+ function parseSpanDescription(otelSpan) {
70
+ const { name, kind, attributes } = otelSpan;
71
+ const description = name || "unknown";
72
+ const spanKind = kind ?? 0;
73
+ const isHttp = spanKind === 2 || attributes["http.method"] != null;
74
+ const isDb = attributes["db.system"] != null || attributes["db.operation"] != null || attributes["db.statement"] != null;
75
+ let op = "default";
76
+ if (isHttp) {
77
+ op = "http.client";
78
+ const method = attributes["http.method"];
79
+ const route = attributes["http.route"] ?? attributes["http.target"];
80
+ if (method && route) op = "http.server";
81
+ } else if (isDb) {
82
+ op = "db.query";
83
+ }
84
+ return { op, description };
85
+ }
86
+ function getTraceData(otelSpan) {
87
+ const ctx = otelSpan.spanContext();
88
+ return {
89
+ traceId: ctx.traceId,
90
+ spanId: ctx.spanId,
91
+ parentSpanId: otelSpan.parentSpanContext?.spanId
92
+ };
93
+ }
94
+ function isSentryRequestSpan(otelSpan, getDsnHost) {
95
+ const httpUrl = otelSpan.attributes[semanticConventions.SEMATTRS_HTTP_URL];
96
+ if (httpUrl == null) return false;
97
+ const url = typeof httpUrl === "string" ? httpUrl : String(httpUrl);
98
+ const host = getDsnHost();
99
+ if (!host) return false;
100
+ return url.includes(host);
101
+ }
102
+ function getOtelContextFromSpan(otelSpan) {
103
+ const attributes = {};
104
+ for (const [k, v] of Object.entries(otelSpan.attributes)) {
105
+ attributes[k] = v;
106
+ }
107
+ const resource = {};
108
+ const res = otelSpan.resource;
109
+ if (res && res.attributes) {
110
+ for (const [k, v] of Object.entries(res.attributes)) {
111
+ resource[k] = v;
112
+ }
113
+ }
114
+ return { attributes, resource };
115
+ }
116
+ function updateSpanWithOtelData(sentrySpan, otelSpan) {
117
+ const status = mapOtelStatus(otelSpan);
118
+ sentrySpan.setStatus({ status });
119
+ sentrySpan.setData("otel.kind", otelSpan.kind ?? 0);
120
+ for (const [key, value] of Object.entries(otelSpan.attributes)) {
121
+ sentrySpan.setData(key, value);
122
+ }
123
+ const { op, description } = parseSpanDescription(otelSpan);
124
+ sentrySpan.op = op;
125
+ sentrySpan.description = description;
126
+ }
127
+ function updateTransactionWithOtelData(transaction, otelSpan) {
128
+ const status = mapOtelStatus(otelSpan);
129
+ transaction.setStatus({ status });
130
+ const { op, description } = parseSpanDescription(otelSpan);
131
+ transaction.op = op;
132
+ transaction.name = description;
133
+ }
134
+ function finishTransactionWithContextFromOtelData(transaction, otelSpan) {
135
+ const { attributes, resource } = getOtelContextFromSpan(otelSpan);
136
+ transaction.setContext("otel", { attributes, resource });
137
+ transaction.finish(convertOtelTimeToSeconds(otelSpan.endTime));
138
+ }
139
+ function generateSentryErrorsFromOtelSpan(otelSpan, captureException) {
140
+ const events = otelSpan.events ?? [];
141
+ for (const event of events) {
142
+ if (event.name !== "exception") continue;
143
+ const attrs = event.attributes ?? {};
144
+ const message = attrs[semanticConventions.SEMATTRS_EXCEPTION_MESSAGE] ?? "Unknown error";
145
+ const stack = attrs[semanticConventions.SEMATTRS_EXCEPTION_STACKTRACE] ?? "";
146
+ const type = attrs[semanticConventions.SEMATTRS_EXCEPTION_TYPE] ?? "Error";
147
+ const synthetic = new Error(message);
148
+ synthetic.name = type;
149
+ if (stack) synthetic.stack = stack;
150
+ const { attributes, resource } = getOtelContextFromSpan(otelSpan);
151
+ captureException(synthetic, {
152
+ contexts: {
153
+ otel: { attributes, resource },
154
+ trace: {
155
+ trace_id: otelSpan.spanContext().traceId,
156
+ span_id: otelSpan.spanContext().spanId,
157
+ parent_span_id: otelSpan.parentSpanContext?.spanId
158
+ }
159
+ }
160
+ });
161
+ }
162
+ }
163
+
164
+ // src/processor.ts
165
+ var INSTRUMENTER_OTEL = "otel";
166
+ var SentrySpanProcessor = class {
167
+ sentry;
168
+ map = /* @__PURE__ */ new Map();
169
+ constructor(sentry) {
170
+ this.sentry = sentry;
171
+ if (typeof sentry.addGlobalEventProcessor === "function") {
172
+ sentry.addGlobalEventProcessor((event) => {
173
+ const e = event;
174
+ const otelSpan = api.trace.getActiveSpan();
175
+ if (!otelSpan) return e;
176
+ if (e.contexts && e.contexts.trace) {
177
+ return e;
178
+ }
179
+ const ctx = otelSpan.spanContext();
180
+ e.contexts = {
181
+ ...e.contexts,
182
+ trace: {
183
+ trace_id: ctx.traceId,
184
+ span_id: ctx.spanId
185
+ }
186
+ };
187
+ return e;
188
+ });
189
+ }
190
+ }
191
+ getDsnHost() {
192
+ const hub = this.sentry.getCurrentHub();
193
+ const client = hub.getClient?.();
194
+ return client?.getDsn()?.host;
195
+ }
196
+ onStart(span, _parentContext) {
197
+ const hub = this.sentry.getCurrentHub();
198
+ if (!hub) return;
199
+ const spanContext = span.spanContext();
200
+ const otelSpanId = spanContext.spanId;
201
+ const parentSpanId = span.parentSpanContext?.spanId;
202
+ const parentSentry = parentSpanId ? this.map.get(parentSpanId) : void 0;
203
+ const startTimestamp = convertOtelTimeToSeconds(span.startTime);
204
+ if (parentSentry && "startChild" in parentSentry) {
205
+ const child = parentSentry.startChild({
206
+ description: span.name,
207
+ instrumenter: INSTRUMENTER_OTEL,
208
+ startTimestamp,
209
+ spanId: otelSpanId
210
+ });
211
+ this.map.set(otelSpanId, child);
212
+ } else {
213
+ const traceData = getTraceData(span);
214
+ const transaction = hub.startTransaction({
215
+ name: span.name,
216
+ traceId: traceData.traceId,
217
+ spanId: traceData.spanId,
218
+ parentSpanId: traceData.parentSpanId,
219
+ startTimestamp,
220
+ instrumenter: INSTRUMENTER_OTEL
221
+ });
222
+ if (transaction) {
223
+ this.map.set(otelSpanId, transaction);
224
+ }
225
+ }
226
+ }
227
+ onEnd(span) {
228
+ const otelSpanId = span.spanContext().spanId;
229
+ const sentrySpan = this.map.get(otelSpanId);
230
+ if (isSentryRequestSpan(span, () => this.getDsnHost())) {
231
+ this.map.delete(otelSpanId);
232
+ return;
233
+ }
234
+ generateSentryErrorsFromOtelSpan(
235
+ span,
236
+ (err, opts) => this.sentry.captureException(err, opts)
237
+ );
238
+ if (!sentrySpan) return;
239
+ const isTransaction = "setContext" in sentrySpan && "finish" in sentrySpan;
240
+ if (isTransaction) {
241
+ updateTransactionWithOtelData(sentrySpan, span);
242
+ finishTransactionWithContextFromOtelData(
243
+ sentrySpan,
244
+ span
245
+ );
246
+ } else {
247
+ updateSpanWithOtelData(sentrySpan, span);
248
+ sentrySpan.finish(convertOtelTimeToSeconds(span.endTime));
249
+ }
250
+ this.map.delete(otelSpanId);
251
+ }
252
+ forceFlush() {
253
+ return Promise.resolve();
254
+ }
255
+ shutdown() {
256
+ this.map.clear();
257
+ return Promise.resolve();
258
+ }
259
+ };
260
+ function createSentrySpanProcessor(sentry) {
261
+ return new SentrySpanProcessor(sentry);
262
+ }
263
+ var SENTRY_PROPAGATION_KEY = /* @__PURE__ */ Symbol.for("autotel.sentry.propagation");
264
+ var TRACE_FLAGS_SAMPLED = 1;
265
+ function getSpanContextFromContext(context) {
266
+ const span = api.trace.getSpan(context);
267
+ if (!span) return null;
268
+ const ctx = span.spanContext();
269
+ return { traceId: ctx.traceId, spanId: ctx.spanId, traceFlags: ctx.traceFlags };
270
+ }
271
+ function formatSentryTrace(traceId, spanId, traceFlags) {
272
+ const sampled = (traceFlags & TRACE_FLAGS_SAMPLED) === TRACE_FLAGS_SAMPLED ? "1" : "0";
273
+ return `${traceId}-${spanId}-${sampled}`;
274
+ }
275
+ function serializeBaggage(context) {
276
+ const baggage = api.propagation.getBaggage(context);
277
+ if (!baggage) return void 0;
278
+ const entries = [];
279
+ const allEntries = baggage.getAllEntries();
280
+ for (const [key, entry] of allEntries) {
281
+ if (entry?.value !== void 0) {
282
+ const encoded = encodeURIComponent(key) + "=" + encodeURIComponent(entry.value);
283
+ entries.push(encoded);
284
+ }
285
+ }
286
+ return entries.length > 0 ? entries.join("; ") : void 0;
287
+ }
288
+ var SentryPropagator = class {
289
+ inject(context, carrier, setter) {
290
+ const spanCtx = getSpanContextFromContext(context);
291
+ if (!spanCtx) return;
292
+ const sentryTrace = formatSentryTrace(
293
+ spanCtx.traceId,
294
+ spanCtx.spanId,
295
+ spanCtx.traceFlags
296
+ );
297
+ setter.set(carrier, "sentry-trace", sentryTrace);
298
+ const baggageStr = serializeBaggage(context);
299
+ if (baggageStr) {
300
+ setter.set(carrier, "baggage", baggageStr);
301
+ }
302
+ }
303
+ extract(context, carrier, getter) {
304
+ const keys = getter.keys(carrier);
305
+ let sentryTrace;
306
+ let baggageStr;
307
+ for (const key of keys) {
308
+ const lower = key.toLowerCase();
309
+ const value = getter.get(carrier, key);
310
+ const str = Array.isArray(value) ? value[0] : value;
311
+ if (lower === "sentry-trace" && typeof str === "string") sentryTrace = str;
312
+ if (lower === "baggage" && typeof str === "string") baggageStr = str;
313
+ }
314
+ const data = {};
315
+ if (sentryTrace) data.sentryTrace = sentryTrace;
316
+ if (baggageStr) data.baggage = baggageStr;
317
+ if (Object.keys(data).length === 0) return context;
318
+ return context.setValue(SENTRY_PROPAGATION_KEY, data);
319
+ }
320
+ fields() {
321
+ return ["sentry-trace", "baggage"];
322
+ }
323
+ };
324
+
325
+ exports.SENTRY_PROPAGATION_KEY = SENTRY_PROPAGATION_KEY;
326
+ exports.SentryPropagator = SentryPropagator;
327
+ exports.SentrySpanProcessor = SentrySpanProcessor;
328
+ exports.createSentrySpanProcessor = createSentrySpanProcessor;
329
+ //# sourceMappingURL=index.cjs.map
330
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helpers.ts","../src/processor.ts","../src/propagator.ts"],"names":["SEMATTRS_HTTP_STATUS_CODE","SEMATTRS_RPC_GRPC_STATUS_CODE","SEMATTRS_HTTP_URL","SEMATTRS_EXCEPTION_MESSAGE","SEMATTRS_EXCEPTION_STACKTRACE","SEMATTRS_EXCEPTION_TYPE","trace","propagation"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Helpers for mapping OpenTelemetry spans to Sentry transactions/spans and context.\n * Aligned with Sentry's OpenTelemetry integration spec.\n */\n\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n SEMATTRS_HTTP_URL,\n SEMATTRS_HTTP_STATUS_CODE,\n SEMATTRS_RPC_GRPC_STATUS_CODE,\n SEMATTRS_EXCEPTION_MESSAGE,\n SEMATTRS_EXCEPTION_STACKTRACE,\n SEMATTRS_EXCEPTION_TYPE,\n} from '@opentelemetry/semantic-conventions';\n\n/** Sentry span status strings per their spec (maps to gRPC-style codes). */\nexport type SentrySpanStatus =\n | 'ok'\n | 'cancelled'\n | 'unknown_error'\n | 'invalid_argument'\n | 'deadline_exceeded'\n | 'not_found'\n | 'already_exists'\n | 'permission_denied'\n | 'resource_exhausted'\n | 'failed_precondition'\n | 'aborted'\n | 'out_of_range'\n | 'unimplemented'\n | 'internal_error'\n | 'unavailable'\n | 'data_loss'\n | 'unauthenticated';\n\nexport interface ParsedSpanDescription {\n op: string;\n description: string;\n}\n\n/** OTel timestamps are typically in nanoseconds (HrTime or number). */\nexport function convertOtelTimeToSeconds(time: number | [number, number]): number {\n if (Array.isArray(time)) {\n return time[0] + time[1] / 1e9;\n }\n return time / 1e9;\n}\n\nconst CANONICAL_HTTP_MAP: Record<string, SentrySpanStatus> = {\n '400': 'failed_precondition',\n '401': 'unauthenticated',\n '403': 'permission_denied',\n '404': 'not_found',\n '409': 'aborted',\n '429': 'resource_exhausted',\n '499': 'cancelled',\n '500': 'internal_error',\n '501': 'unimplemented',\n '503': 'unavailable',\n '504': 'deadline_exceeded',\n};\n\nconst CANONICAL_GRPC_MAP: Record<string, SentrySpanStatus> = {\n '1': 'cancelled',\n '2': 'unknown_error',\n '3': 'invalid_argument',\n '4': 'deadline_exceeded',\n '5': 'not_found',\n '6': 'already_exists',\n '7': 'permission_denied',\n '8': 'resource_exhausted',\n '9': 'failed_precondition',\n '10': 'aborted',\n '11': 'out_of_range',\n '12': 'unimplemented',\n '13': 'internal_error',\n '14': 'unavailable',\n '15': 'data_loss',\n '16': 'unauthenticated',\n};\n\nexport function mapOtelStatus(otelSpan: ReadableSpan): SentrySpanStatus {\n const { status, attributes } = otelSpan;\n const code = status.code;\n\n if (code !== undefined && (code < 0 || code > 2)) {\n return 'unknown_error';\n }\n\n if (code === 0 || code === 1) {\n return 'ok';\n }\n\n const httpCode = attributes[SEMATTRS_HTTP_STATUS_CODE];\n const grpcCode = attributes[SEMATTRS_RPC_GRPC_STATUS_CODE];\n\n if (typeof httpCode === 'string') {\n const sentryStatus = CANONICAL_HTTP_MAP[httpCode];\n if (sentryStatus) return sentryStatus;\n }\n if (typeof httpCode === 'number') {\n const sentryStatus = CANONICAL_HTTP_MAP[String(httpCode)];\n if (sentryStatus) return sentryStatus;\n }\n\n if (typeof grpcCode === 'string') {\n const sentryStatus = CANONICAL_GRPC_MAP[grpcCode];\n if (sentryStatus) return sentryStatus;\n }\n\n return 'unknown_error';\n}\n\n/** Derive Sentry op and description from OTel span name, kind, and attributes. */\nexport function parseSpanDescription(otelSpan: ReadableSpan): ParsedSpanDescription {\n const { name, kind, attributes } = otelSpan;\n const description = name || 'unknown';\n\n const spanKind = kind ?? 0;\n const isHttp = spanKind === 2 || attributes['http.method'] != null;\n const isDb =\n attributes['db.system'] != null ||\n attributes['db.operation'] != null ||\n attributes['db.statement'] != null;\n\n let op = 'default';\n if (isHttp) {\n op = 'http.client';\n const method = attributes['http.method'];\n const route = attributes['http.route'] ?? attributes['http.target'];\n if (method && route) op = 'http.server';\n } else if (isDb) {\n op = 'db.query';\n }\n\n return { op, description };\n}\n\nexport interface TraceData {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n}\n\nexport function getTraceData(otelSpan: ReadableSpan): TraceData {\n const ctx = otelSpan.spanContext();\n return {\n traceId: ctx.traceId,\n spanId: ctx.spanId,\n parentSpanId: otelSpan.parentSpanContext?.spanId,\n };\n}\n\n/** Check if the span represents a request to Sentry (ingestion); such spans must not be sent to Sentry. */\nexport function isSentryRequestSpan(\n otelSpan: ReadableSpan,\n getDsnHost: () => string | undefined,\n): boolean {\n const httpUrl = otelSpan.attributes[SEMATTRS_HTTP_URL];\n if (httpUrl == null) return false;\n const url = typeof httpUrl === 'string' ? httpUrl : String(httpUrl);\n const host = getDsnHost();\n if (!host) return false;\n return url.includes(host);\n}\n\n/** Attributes and resource for Sentry's otel context. */\nexport function getOtelContextFromSpan(otelSpan: ReadableSpan): {\n attributes: Record<string, unknown>;\n resource: Record<string, unknown>;\n} {\n const attributes: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(otelSpan.attributes)) {\n attributes[k] = v;\n }\n const resource: Record<string, unknown> = {};\n const res = otelSpan.resource;\n if (res && res.attributes) {\n for (const [k, v] of Object.entries(res.attributes)) {\n resource[k] = v;\n }\n }\n return { attributes, resource };\n}\n\n/** Update a Sentry span with OTel span data (status, op, description, data). */\nexport function updateSpanWithOtelData(\n sentrySpan: { setStatus: (s: { status?: string }) => void; setData: (k: string, v: unknown) => void; op?: string; description?: string },\n otelSpan: ReadableSpan,\n): void {\n const status = mapOtelStatus(otelSpan);\n sentrySpan.setStatus({ status });\n sentrySpan.setData('otel.kind', otelSpan.kind ?? 0);\n for (const [key, value] of Object.entries(otelSpan.attributes)) {\n sentrySpan.setData(key, value);\n }\n const { op, description } = parseSpanDescription(otelSpan);\n sentrySpan.op = op;\n sentrySpan.description = description;\n}\n\n/** Update a Sentry transaction with OTel span data. */\nexport function updateTransactionWithOtelData(\n transaction: { setStatus: (s: { status?: string }) => void; op?: string; name?: string },\n otelSpan: ReadableSpan,\n): void {\n const status = mapOtelStatus(otelSpan);\n transaction.setStatus({ status });\n const { op, description } = parseSpanDescription(otelSpan);\n transaction.op = op;\n transaction.name = description;\n}\n\n/** Set otel context on transaction and finish it. */\nexport function finishTransactionWithContextFromOtelData(\n transaction: {\n setContext: (name: string, ctx: { attributes?: Record<string, unknown>; resource?: Record<string, unknown> }) => void;\n finish: (endTime?: number) => void;\n },\n otelSpan: ReadableSpan,\n): void {\n const { attributes, resource } = getOtelContextFromSpan(otelSpan);\n transaction.setContext('otel', { attributes, resource });\n transaction.finish(convertOtelTimeToSeconds(otelSpan.endTime));\n}\n\n/** Build synthetic Error from OTel exception event attributes and capture with Sentry. */\nexport function generateSentryErrorsFromOtelSpan(\n otelSpan: ReadableSpan,\n captureException: (error: Error, options?: { contexts?: Record<string, unknown> }) => void,\n): void {\n const events = otelSpan.events ?? [];\n for (const event of events) {\n if (event.name !== 'exception') continue;\n const attrs = event.attributes ?? {};\n const message = (attrs[SEMATTRS_EXCEPTION_MESSAGE] as string) ?? 'Unknown error';\n const stack = (attrs[SEMATTRS_EXCEPTION_STACKTRACE] as string) ?? '';\n const type = (attrs[SEMATTRS_EXCEPTION_TYPE] as string) ?? 'Error';\n\n const synthetic = new Error(message);\n synthetic.name = type;\n if (stack) synthetic.stack = stack;\n\n const { attributes, resource } = getOtelContextFromSpan(otelSpan);\n captureException(synthetic, {\n contexts: {\n otel: { attributes, resource },\n trace: {\n trace_id: otelSpan.spanContext().traceId,\n span_id: otelSpan.spanContext().spanId,\n parent_span_id: otelSpan.parentSpanContext?.spanId,\n },\n },\n });\n }\n}\n","/**\n * SentrySpanProcessor: converts OpenTelemetry spans to Sentry transactions/spans.\n * Register with init({ spanProcessors: [new SentrySpanProcessor(Sentry)] }).\n */\n\nimport { trace } from '@opentelemetry/api';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n convertOtelTimeToSeconds,\n getTraceData,\n isSentryRequestSpan,\n updateSpanWithOtelData,\n updateTransactionWithOtelData,\n finishTransactionWithContextFromOtelData,\n generateSentryErrorsFromOtelSpan,\n} from './helpers';\n\n/** Minimal Sentry hub interface for creating transactions and spans. */\nexport interface SentryHubLike {\n startTransaction(ctx: SentryTransactionContextLike): SentryTransactionLike | undefined;\n getSpan(): SentrySpanLike | undefined;\n}\n\n/** Context passed to startTransaction. */\nexport interface SentryTransactionContextLike {\n name: string;\n traceId?: string;\n spanId?: string;\n parentSpanId?: string;\n startTimestamp?: number;\n instrumenter?: string;\n}\n\n/** Sentry transaction (root span). */\nexport interface SentryTransactionLike {\n startChild(ctx: SentrySpanContextLike): SentrySpanLike;\n setStatus(s: { status?: string }): void;\n setContext(name: string, ctx: Record<string, unknown>): void;\n finish(endTime?: number): void;\n name?: string;\n op?: string;\n}\n\n/** Context passed to startChild. */\nexport interface SentrySpanContextLike {\n description?: string;\n instrumenter?: string;\n startTimestamp?: number;\n spanId?: string;\n}\n\n/** Sentry span (child or transaction). */\nexport interface SentrySpanLike {\n setStatus(s: { status?: string }): void;\n setData(key: string, value: unknown): void;\n finish(endTime?: number): void;\n op?: string;\n description?: string;\n}\n\n/** Minimal Sentry SDK interface used by the processor. */\nexport interface SentryLike {\n getCurrentHub(): SentryHubLike;\n addGlobalEventProcessor(callback: (event: unknown) => unknown): void;\n captureException(error: Error, options?: { contexts?: Record<string, unknown> }): void;\n}\n\n/** Client with getDsn() for isSentryRequest detection. */\ninterface SentryClientLike {\n getDsn(): { host: string } | undefined;\n}\n\nconst INSTRUMENTER_OTEL = 'otel';\n\nexport class SentrySpanProcessor implements SpanProcessor {\n private readonly sentry: SentryLike;\n private readonly map = new Map<string, SentrySpanLike | SentryTransactionLike>();\n\n constructor(sentry: SentryLike) {\n this.sentry = sentry;\n\n if (typeof sentry.addGlobalEventProcessor === 'function') {\n sentry.addGlobalEventProcessor((event: unknown) => {\n const e = event as { contexts?: Record<string, unknown> };\n const otelSpan = trace.getActiveSpan();\n if (!otelSpan) return e;\n\n if (e.contexts && (e.contexts as Record<string, unknown>).trace) {\n return e;\n }\n\n const ctx = otelSpan.spanContext();\n e.contexts = {\n ...e.contexts,\n trace: {\n trace_id: ctx.traceId,\n span_id: ctx.spanId,\n },\n };\n return e;\n });\n }\n }\n\n private getDsnHost(): string | undefined {\n const hub = this.sentry.getCurrentHub();\n const client = (hub as unknown as { getClient?(): SentryClientLike }).getClient?.();\n return client?.getDsn()?.host;\n }\n\n onStart(span: Span, _parentContext: Context): void {\n const hub = this.sentry.getCurrentHub();\n if (!hub) return;\n\n const spanContext = span.spanContext();\n const otelSpanId = spanContext.spanId;\n const parentSpanId = span.parentSpanContext?.spanId;\n const parentSentry = parentSpanId ? this.map.get(parentSpanId) : undefined;\n\n const startTimestamp = convertOtelTimeToSeconds(span.startTime);\n\n if (parentSentry && 'startChild' in parentSentry) {\n const child = parentSentry.startChild({\n description: span.name,\n instrumenter: INSTRUMENTER_OTEL,\n startTimestamp,\n spanId: otelSpanId,\n });\n this.map.set(otelSpanId, child);\n } else {\n const traceData = getTraceData(span as unknown as ReadableSpan);\n const transaction = hub.startTransaction({\n name: span.name,\n traceId: traceData.traceId,\n spanId: traceData.spanId,\n parentSpanId: traceData.parentSpanId,\n startTimestamp,\n instrumenter: INSTRUMENTER_OTEL,\n });\n if (transaction) {\n this.map.set(otelSpanId, transaction);\n }\n }\n }\n\n onEnd(span: ReadableSpan): void {\n const otelSpanId = span.spanContext().spanId;\n const sentrySpan = this.map.get(otelSpanId);\n\n if (isSentryRequestSpan(span, () => this.getDsnHost())) {\n this.map.delete(otelSpanId);\n return;\n }\n\n generateSentryErrorsFromOtelSpan(span, (err, opts) =>\n this.sentry.captureException(err, opts),\n );\n\n if (!sentrySpan) return;\n\n const isTransaction = 'setContext' in sentrySpan && 'finish' in sentrySpan;\n\n if (isTransaction) {\n updateTransactionWithOtelData(sentrySpan as SentryTransactionLike, span);\n finishTransactionWithContextFromOtelData(\n sentrySpan as SentryTransactionLike,\n span,\n );\n } else {\n updateSpanWithOtelData(sentrySpan as SentrySpanLike, span);\n sentrySpan.finish(convertOtelTimeToSeconds(span.endTime));\n }\n\n this.map.delete(otelSpanId);\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n this.map.clear();\n return Promise.resolve();\n }\n}\n\nexport function createSentrySpanProcessor(sentry: SentryLike): SentrySpanProcessor {\n return new SentrySpanProcessor(sentry);\n}\n","/**\n * SentryPropagator: injects and extracts sentry-trace and baggage headers\n * for trace propagation and dynamic sampling with Sentry.\n */\n\nimport {\n trace,\n propagation,\n type Context,\n type TextMapPropagator,\n type TextMapSetter,\n type TextMapGetter,\n} from '@opentelemetry/api';\n\n/** Context key for stored Sentry propagation data (sentry-trace, baggage). */\nexport const SENTRY_PROPAGATION_KEY = Symbol.for('autotel.sentry.propagation');\n\nexport interface SentryPropagationData {\n sentryTrace?: string;\n baggage?: string;\n}\n\n/** Sentry trace header format: traceid-spanid-sampled (sampled 1 or 0). */\nconst TRACE_FLAGS_SAMPLED = 0x1;\n\nfunction getSpanContextFromContext(context: Context): { traceId: string; spanId: string; traceFlags: number } | null {\n const span = trace.getSpan(context);\n if (!span) return null;\n const ctx = span.spanContext();\n return { traceId: ctx.traceId, spanId: ctx.spanId, traceFlags: ctx.traceFlags };\n}\n\nfunction formatSentryTrace(traceId: string, spanId: string, traceFlags: number): string {\n const sampled = (traceFlags & TRACE_FLAGS_SAMPLED) === TRACE_FLAGS_SAMPLED ? '1' : '0';\n return `${traceId}-${spanId}-${sampled}`;\n}\n\n/** Serialize OTel baggage to header value (key=value; key2=value2). */\nfunction serializeBaggage(context: Context): string | undefined {\n const baggage = propagation.getBaggage(context);\n if (!baggage) return undefined;\n const entries: string[] = [];\n const allEntries = baggage.getAllEntries();\n for (const [key, entry] of allEntries) {\n if (entry?.value !== undefined) {\n const encoded = encodeURIComponent(key) + '=' + encodeURIComponent(entry.value);\n entries.push(encoded);\n }\n }\n return entries.length > 0 ? entries.join('; ') : undefined;\n}\n\nexport class SentryPropagator implements TextMapPropagator {\n inject(context: Context, carrier: unknown, setter: TextMapSetter): void {\n const spanCtx = getSpanContextFromContext(context);\n if (!spanCtx) return;\n\n const sentryTrace = formatSentryTrace(\n spanCtx.traceId,\n spanCtx.spanId,\n spanCtx.traceFlags,\n );\n setter.set(carrier, 'sentry-trace', sentryTrace);\n\n const baggageStr = serializeBaggage(context);\n if (baggageStr) {\n setter.set(carrier, 'baggage', baggageStr);\n }\n }\n\n extract(context: Context, carrier: unknown, getter: TextMapGetter): Context {\n const keys = getter.keys(carrier);\n let sentryTrace: string | undefined;\n let baggageStr: string | undefined;\n\n for (const key of keys) {\n const lower = key.toLowerCase();\n const value = getter.get(carrier, key);\n const str = Array.isArray(value) ? value[0] : value;\n if (lower === 'sentry-trace' && typeof str === 'string') sentryTrace = str;\n if (lower === 'baggage' && typeof str === 'string') baggageStr = str;\n }\n\n const data: SentryPropagationData = {};\n if (sentryTrace) data.sentryTrace = sentryTrace;\n if (baggageStr) data.baggage = baggageStr;\n\n if (Object.keys(data).length === 0) return context;\n\n return context.setValue(SENTRY_PROPAGATION_KEY, data);\n }\n\n fields(): string[] {\n return ['sentry-trace', 'baggage'];\n }\n}\n"]}
@@ -0,0 +1,88 @@
1
+ import { Context, TextMapPropagator, TextMapSetter, TextMapGetter } from '@opentelemetry/api';
2
+ import { SpanProcessor, Span, ReadableSpan } from '@opentelemetry/sdk-trace-base';
3
+
4
+ /**
5
+ * SentrySpanProcessor: converts OpenTelemetry spans to Sentry transactions/spans.
6
+ * Register with init({ spanProcessors: [new SentrySpanProcessor(Sentry)] }).
7
+ */
8
+
9
+ /** Minimal Sentry hub interface for creating transactions and spans. */
10
+ interface SentryHubLike {
11
+ startTransaction(ctx: SentryTransactionContextLike): SentryTransactionLike | undefined;
12
+ getSpan(): SentrySpanLike | undefined;
13
+ }
14
+ /** Context passed to startTransaction. */
15
+ interface SentryTransactionContextLike {
16
+ name: string;
17
+ traceId?: string;
18
+ spanId?: string;
19
+ parentSpanId?: string;
20
+ startTimestamp?: number;
21
+ instrumenter?: string;
22
+ }
23
+ /** Sentry transaction (root span). */
24
+ interface SentryTransactionLike {
25
+ startChild(ctx: SentrySpanContextLike): SentrySpanLike;
26
+ setStatus(s: {
27
+ status?: string;
28
+ }): void;
29
+ setContext(name: string, ctx: Record<string, unknown>): void;
30
+ finish(endTime?: number): void;
31
+ name?: string;
32
+ op?: string;
33
+ }
34
+ /** Context passed to startChild. */
35
+ interface SentrySpanContextLike {
36
+ description?: string;
37
+ instrumenter?: string;
38
+ startTimestamp?: number;
39
+ spanId?: string;
40
+ }
41
+ /** Sentry span (child or transaction). */
42
+ interface SentrySpanLike {
43
+ setStatus(s: {
44
+ status?: string;
45
+ }): void;
46
+ setData(key: string, value: unknown): void;
47
+ finish(endTime?: number): void;
48
+ op?: string;
49
+ description?: string;
50
+ }
51
+ /** Minimal Sentry SDK interface used by the processor. */
52
+ interface SentryLike {
53
+ getCurrentHub(): SentryHubLike;
54
+ addGlobalEventProcessor(callback: (event: unknown) => unknown): void;
55
+ captureException(error: Error, options?: {
56
+ contexts?: Record<string, unknown>;
57
+ }): void;
58
+ }
59
+ declare class SentrySpanProcessor implements SpanProcessor {
60
+ private readonly sentry;
61
+ private readonly map;
62
+ constructor(sentry: SentryLike);
63
+ private getDsnHost;
64
+ onStart(span: Span, _parentContext: Context): void;
65
+ onEnd(span: ReadableSpan): void;
66
+ forceFlush(): Promise<void>;
67
+ shutdown(): Promise<void>;
68
+ }
69
+ declare function createSentrySpanProcessor(sentry: SentryLike): SentrySpanProcessor;
70
+
71
+ /**
72
+ * SentryPropagator: injects and extracts sentry-trace and baggage headers
73
+ * for trace propagation and dynamic sampling with Sentry.
74
+ */
75
+
76
+ /** Context key for stored Sentry propagation data (sentry-trace, baggage). */
77
+ declare const SENTRY_PROPAGATION_KEY: unique symbol;
78
+ interface SentryPropagationData {
79
+ sentryTrace?: string;
80
+ baggage?: string;
81
+ }
82
+ declare class SentryPropagator implements TextMapPropagator {
83
+ inject(context: Context, carrier: unknown, setter: TextMapSetter): void;
84
+ extract(context: Context, carrier: unknown, getter: TextMapGetter): Context;
85
+ fields(): string[];
86
+ }
87
+
88
+ export { SENTRY_PROPAGATION_KEY, type SentryLike, type SentryPropagationData, SentryPropagator, SentrySpanProcessor, createSentrySpanProcessor };
@@ -0,0 +1,88 @@
1
+ import { Context, TextMapPropagator, TextMapSetter, TextMapGetter } from '@opentelemetry/api';
2
+ import { SpanProcessor, Span, ReadableSpan } from '@opentelemetry/sdk-trace-base';
3
+
4
+ /**
5
+ * SentrySpanProcessor: converts OpenTelemetry spans to Sentry transactions/spans.
6
+ * Register with init({ spanProcessors: [new SentrySpanProcessor(Sentry)] }).
7
+ */
8
+
9
+ /** Minimal Sentry hub interface for creating transactions and spans. */
10
+ interface SentryHubLike {
11
+ startTransaction(ctx: SentryTransactionContextLike): SentryTransactionLike | undefined;
12
+ getSpan(): SentrySpanLike | undefined;
13
+ }
14
+ /** Context passed to startTransaction. */
15
+ interface SentryTransactionContextLike {
16
+ name: string;
17
+ traceId?: string;
18
+ spanId?: string;
19
+ parentSpanId?: string;
20
+ startTimestamp?: number;
21
+ instrumenter?: string;
22
+ }
23
+ /** Sentry transaction (root span). */
24
+ interface SentryTransactionLike {
25
+ startChild(ctx: SentrySpanContextLike): SentrySpanLike;
26
+ setStatus(s: {
27
+ status?: string;
28
+ }): void;
29
+ setContext(name: string, ctx: Record<string, unknown>): void;
30
+ finish(endTime?: number): void;
31
+ name?: string;
32
+ op?: string;
33
+ }
34
+ /** Context passed to startChild. */
35
+ interface SentrySpanContextLike {
36
+ description?: string;
37
+ instrumenter?: string;
38
+ startTimestamp?: number;
39
+ spanId?: string;
40
+ }
41
+ /** Sentry span (child or transaction). */
42
+ interface SentrySpanLike {
43
+ setStatus(s: {
44
+ status?: string;
45
+ }): void;
46
+ setData(key: string, value: unknown): void;
47
+ finish(endTime?: number): void;
48
+ op?: string;
49
+ description?: string;
50
+ }
51
+ /** Minimal Sentry SDK interface used by the processor. */
52
+ interface SentryLike {
53
+ getCurrentHub(): SentryHubLike;
54
+ addGlobalEventProcessor(callback: (event: unknown) => unknown): void;
55
+ captureException(error: Error, options?: {
56
+ contexts?: Record<string, unknown>;
57
+ }): void;
58
+ }
59
+ declare class SentrySpanProcessor implements SpanProcessor {
60
+ private readonly sentry;
61
+ private readonly map;
62
+ constructor(sentry: SentryLike);
63
+ private getDsnHost;
64
+ onStart(span: Span, _parentContext: Context): void;
65
+ onEnd(span: ReadableSpan): void;
66
+ forceFlush(): Promise<void>;
67
+ shutdown(): Promise<void>;
68
+ }
69
+ declare function createSentrySpanProcessor(sentry: SentryLike): SentrySpanProcessor;
70
+
71
+ /**
72
+ * SentryPropagator: injects and extracts sentry-trace and baggage headers
73
+ * for trace propagation and dynamic sampling with Sentry.
74
+ */
75
+
76
+ /** Context key for stored Sentry propagation data (sentry-trace, baggage). */
77
+ declare const SENTRY_PROPAGATION_KEY: unique symbol;
78
+ interface SentryPropagationData {
79
+ sentryTrace?: string;
80
+ baggage?: string;
81
+ }
82
+ declare class SentryPropagator implements TextMapPropagator {
83
+ inject(context: Context, carrier: unknown, setter: TextMapSetter): void;
84
+ extract(context: Context, carrier: unknown, getter: TextMapGetter): Context;
85
+ fields(): string[];
86
+ }
87
+
88
+ export { SENTRY_PROPAGATION_KEY, type SentryLike, type SentryPropagationData, SentryPropagator, SentrySpanProcessor, createSentrySpanProcessor };