@drawbridge/drawbridge-telemetry 0.0.8 → 0.0.9

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/dist/bullmq.cjs CHANGED
@@ -35,17 +35,18 @@ __export(bullmq_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(bullmq_exports);
37
37
  var import_crypto = require("crypto");
38
+ var Sentry2 = __toESM(require("@sentry/node"), 1);
38
39
 
39
40
  // index.js
40
41
  var Sentry = __toESM(require("@sentry/node"), 1);
41
42
  var import_pino = __toESM(require("pino"), 1);
42
- var withTraceScope = (traceId, fn) => Sentry.withScope((scope) => {
43
+ var withTraceScope = (traceId, fn) => Sentry.withIsolationScope((scope) => {
43
44
  if (traceId) scope.setTag("traceId", traceId);
44
45
  return fn(scope);
45
46
  });
46
47
  var currentTraceId = () => {
47
48
  var _a, _b;
48
- const scope = Sentry.getCurrentScope();
49
+ const scope = Sentry.getIsolationScope();
49
50
  const data = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
50
51
  return (_b = data == null ? void 0 : data.tags) == null ? void 0 : _b.traceId;
51
52
  };
@@ -80,41 +81,61 @@ var enqueueFromWorker = async (queue, name, data, options = {}) => {
80
81
  var attachQueueEventsLogger = (queue, queueEvents, logger2) => {
81
82
  if (!(logger2 == null ? void 0 : logger2.emit)) return;
82
83
  const queueName = queue == null ? void 0 : queue.name;
84
+ const emitInScope = (name, data, traceId) => Sentry2.withIsolationScope((scope) => {
85
+ if (traceId) scope.setTag("traceId", traceId);
86
+ if (queueName) scope.setTag("queue", queueName);
87
+ return logger2.emit(name, data);
88
+ });
83
89
  queueEvents.on("completed", async ({ jobId }) => {
90
+ var _a;
84
91
  try {
85
92
  const job = await queue.getJob(jobId).catch(() => null);
86
93
  const duration = (job == null ? void 0 : job.finishedOn) && (job == null ? void 0 : job.processedOn) ? job.finishedOn - job.processedOn : null;
87
- await logger2.emit("job.completed", {
88
- jobId,
89
- queue: queueName,
90
- name: job == null ? void 0 : job.name,
91
- duration,
92
- attemptsMade: job == null ? void 0 : job.attemptsMade
93
- });
94
+ await emitInScope(
95
+ "job.completed",
96
+ {
97
+ jobId,
98
+ queue: queueName,
99
+ name: job == null ? void 0 : job.name,
100
+ duration,
101
+ attemptsMade: job == null ? void 0 : job.attemptsMade
102
+ },
103
+ (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId
104
+ );
94
105
  } catch {
95
106
  }
96
107
  });
97
108
  queueEvents.on("failed", async ({ jobId, failedReason }) => {
109
+ var _a;
98
110
  try {
99
111
  const job = await queue.getJob(jobId).catch(() => null);
100
- await logger2.emit("job.failed", {
101
- jobId,
102
- queue: queueName,
103
- name: job == null ? void 0 : job.name,
104
- failedReason,
105
- attemptsMade: job == null ? void 0 : job.attemptsMade
106
- });
112
+ await emitInScope(
113
+ "job.failed",
114
+ {
115
+ jobId,
116
+ queue: queueName,
117
+ name: job == null ? void 0 : job.name,
118
+ failedReason,
119
+ attemptsMade: job == null ? void 0 : job.attemptsMade
120
+ },
121
+ (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId
122
+ );
107
123
  } catch {
108
124
  }
109
125
  });
110
126
  queueEvents.on("stalled", async ({ jobId }) => {
127
+ var _a;
111
128
  try {
112
129
  const job = await queue.getJob(jobId).catch(() => null);
113
- await logger2.emit("job.stalled", {
114
- jobId,
115
- queue: queueName,
116
- name: job == null ? void 0 : job.name
117
- });
130
+ await emitInScope(
131
+ "job.stalled",
132
+ {
133
+ jobId,
134
+ queue: queueName,
135
+ name: job == null ? void 0 : job.name
136
+ },
137
+ (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId
138
+ );
118
139
  } catch {
119
140
  }
120
141
  });
package/dist/bullmq.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'crypto';
2
+ import * as Sentry from '@sentry/node';
2
3
  import { currentTraceId, withTraceScope } from './index.cjs';
3
- import '@sentry/node';
4
4
  import 'pino';
5
5
 
6
6
  // Wrap a BullMQ worker handler so it runs inside a Sentry scope tagged
@@ -82,6 +82,22 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
82
82
 
83
83
  const queueName = queue?.name;
84
84
 
85
+ // QueueEvents listeners fire as plain event-emitter callbacks — they
86
+ // don't run inside any wrapWorkerHandler scope, so without this
87
+ // withIsolationScope wrapper the emit's envelope traceId would be
88
+ // null. Wrap each handler so the emitted job.* row carries the job's
89
+ // own __traceId (set by enqueueWithTrace / enqueueFromWorker at
90
+ // enqueue time).
91
+
92
+ const emitInScope = ( name, data, traceId ) => Sentry.withIsolationScope( ( scope ) => {
93
+
94
+ if( traceId ) scope.setTag( 'traceId', traceId );
95
+ if( queueName ) scope.setTag( 'queue', queueName );
96
+
97
+ return logger.emit( name, data );
98
+
99
+ } );
100
+
85
101
  queueEvents.on( 'completed', async ({ jobId }) => {
86
102
 
87
103
  try {
@@ -92,13 +108,17 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
92
108
  ? job.finishedOn - job.processedOn
93
109
  : null;
94
110
 
95
- await logger.emit( 'job.completed', {
96
- jobId,
97
- queue : queueName,
98
- name : job?.name,
99
- duration,
100
- attemptsMade : job?.attemptsMade
101
- });
111
+ await emitInScope(
112
+ 'job.completed',
113
+ {
114
+ jobId,
115
+ queue : queueName,
116
+ name : job?.name,
117
+ duration,
118
+ attemptsMade : job?.attemptsMade
119
+ },
120
+ job?.data?.__traceId
121
+ );
102
122
 
103
123
  } catch {}
104
124
 
@@ -110,13 +130,17 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
110
130
 
111
131
  const job = await queue.getJob( jobId ).catch( () => null );
112
132
 
113
- await logger.emit( 'job.failed', {
114
- jobId,
115
- queue : queueName,
116
- name : job?.name,
117
- failedReason,
118
- attemptsMade : job?.attemptsMade
119
- });
133
+ await emitInScope(
134
+ 'job.failed',
135
+ {
136
+ jobId,
137
+ queue : queueName,
138
+ name : job?.name,
139
+ failedReason,
140
+ attemptsMade : job?.attemptsMade
141
+ },
142
+ job?.data?.__traceId
143
+ );
120
144
 
121
145
  } catch {}
122
146
 
@@ -128,11 +152,15 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
128
152
 
129
153
  const job = await queue.getJob( jobId ).catch( () => null );
130
154
 
131
- await logger.emit( 'job.stalled', {
132
- jobId,
133
- queue : queueName,
134
- name : job?.name
135
- });
155
+ await emitInScope(
156
+ 'job.stalled',
157
+ {
158
+ jobId,
159
+ queue : queueName,
160
+ name : job?.name
161
+ },
162
+ job?.data?.__traceId
163
+ );
136
164
 
137
165
  } catch {}
138
166
 
package/dist/bullmq.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'crypto';
2
+ import * as Sentry from '@sentry/node';
2
3
  import { currentTraceId, withTraceScope } from './index.js';
3
- import '@sentry/node';
4
4
  import 'pino';
5
5
 
6
6
  // Wrap a BullMQ worker handler so it runs inside a Sentry scope tagged
@@ -82,6 +82,22 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
82
82
 
83
83
  const queueName = queue?.name;
84
84
 
85
+ // QueueEvents listeners fire as plain event-emitter callbacks — they
86
+ // don't run inside any wrapWorkerHandler scope, so without this
87
+ // withIsolationScope wrapper the emit's envelope traceId would be
88
+ // null. Wrap each handler so the emitted job.* row carries the job's
89
+ // own __traceId (set by enqueueWithTrace / enqueueFromWorker at
90
+ // enqueue time).
91
+
92
+ const emitInScope = ( name, data, traceId ) => Sentry.withIsolationScope( ( scope ) => {
93
+
94
+ if( traceId ) scope.setTag( 'traceId', traceId );
95
+ if( queueName ) scope.setTag( 'queue', queueName );
96
+
97
+ return logger.emit( name, data );
98
+
99
+ } );
100
+
85
101
  queueEvents.on( 'completed', async ({ jobId }) => {
86
102
 
87
103
  try {
@@ -92,13 +108,17 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
92
108
  ? job.finishedOn - job.processedOn
93
109
  : null;
94
110
 
95
- await logger.emit( 'job.completed', {
96
- jobId,
97
- queue : queueName,
98
- name : job?.name,
99
- duration,
100
- attemptsMade : job?.attemptsMade
101
- });
111
+ await emitInScope(
112
+ 'job.completed',
113
+ {
114
+ jobId,
115
+ queue : queueName,
116
+ name : job?.name,
117
+ duration,
118
+ attemptsMade : job?.attemptsMade
119
+ },
120
+ job?.data?.__traceId
121
+ );
102
122
 
103
123
  } catch {}
104
124
 
@@ -110,13 +130,17 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
110
130
 
111
131
  const job = await queue.getJob( jobId ).catch( () => null );
112
132
 
113
- await logger.emit( 'job.failed', {
114
- jobId,
115
- queue : queueName,
116
- name : job?.name,
117
- failedReason,
118
- attemptsMade : job?.attemptsMade
119
- });
133
+ await emitInScope(
134
+ 'job.failed',
135
+ {
136
+ jobId,
137
+ queue : queueName,
138
+ name : job?.name,
139
+ failedReason,
140
+ attemptsMade : job?.attemptsMade
141
+ },
142
+ job?.data?.__traceId
143
+ );
120
144
 
121
145
  } catch {}
122
146
 
@@ -128,11 +152,15 @@ const attachQueueEventsLogger = ( queue, queueEvents, logger ) => {
128
152
 
129
153
  const job = await queue.getJob( jobId ).catch( () => null );
130
154
 
131
- await logger.emit( 'job.stalled', {
132
- jobId,
133
- queue : queueName,
134
- name : job?.name
135
- });
155
+ await emitInScope(
156
+ 'job.stalled',
157
+ {
158
+ jobId,
159
+ queue : queueName,
160
+ name : job?.name
161
+ },
162
+ job?.data?.__traceId
163
+ );
136
164
 
137
165
  } catch {}
138
166
 
package/dist/bullmq.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  currentTraceId,
3
3
  withTraceScope
4
- } from "./chunk-CODZHH5I.js";
4
+ } from "./chunk-WUUTBLMK.js";
5
5
 
6
6
  // bullmq.js
7
7
  import { randomUUID } from "crypto";
8
+ import * as Sentry from "@sentry/node";
8
9
  var wrapWorkerHandler = (handler) => async (job) => {
9
10
  var _a;
10
11
  const traceId = ((_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId) || randomUUID();
@@ -34,41 +35,61 @@ var enqueueFromWorker = async (queue, name, data, options = {}) => {
34
35
  var attachQueueEventsLogger = (queue, queueEvents, logger) => {
35
36
  if (!(logger == null ? void 0 : logger.emit)) return;
36
37
  const queueName = queue == null ? void 0 : queue.name;
38
+ const emitInScope = (name, data, traceId) => Sentry.withIsolationScope((scope) => {
39
+ if (traceId) scope.setTag("traceId", traceId);
40
+ if (queueName) scope.setTag("queue", queueName);
41
+ return logger.emit(name, data);
42
+ });
37
43
  queueEvents.on("completed", async ({ jobId }) => {
44
+ var _a;
38
45
  try {
39
46
  const job = await queue.getJob(jobId).catch(() => null);
40
47
  const duration = (job == null ? void 0 : job.finishedOn) && (job == null ? void 0 : job.processedOn) ? job.finishedOn - job.processedOn : null;
41
- await logger.emit("job.completed", {
42
- jobId,
43
- queue: queueName,
44
- name: job == null ? void 0 : job.name,
45
- duration,
46
- attemptsMade: job == null ? void 0 : job.attemptsMade
47
- });
48
+ await emitInScope(
49
+ "job.completed",
50
+ {
51
+ jobId,
52
+ queue: queueName,
53
+ name: job == null ? void 0 : job.name,
54
+ duration,
55
+ attemptsMade: job == null ? void 0 : job.attemptsMade
56
+ },
57
+ (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId
58
+ );
48
59
  } catch {
49
60
  }
50
61
  });
51
62
  queueEvents.on("failed", async ({ jobId, failedReason }) => {
63
+ var _a;
52
64
  try {
53
65
  const job = await queue.getJob(jobId).catch(() => null);
54
- await logger.emit("job.failed", {
55
- jobId,
56
- queue: queueName,
57
- name: job == null ? void 0 : job.name,
58
- failedReason,
59
- attemptsMade: job == null ? void 0 : job.attemptsMade
60
- });
66
+ await emitInScope(
67
+ "job.failed",
68
+ {
69
+ jobId,
70
+ queue: queueName,
71
+ name: job == null ? void 0 : job.name,
72
+ failedReason,
73
+ attemptsMade: job == null ? void 0 : job.attemptsMade
74
+ },
75
+ (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId
76
+ );
61
77
  } catch {
62
78
  }
63
79
  });
64
80
  queueEvents.on("stalled", async ({ jobId }) => {
81
+ var _a;
65
82
  try {
66
83
  const job = await queue.getJob(jobId).catch(() => null);
67
- await logger.emit("job.stalled", {
68
- jobId,
69
- queue: queueName,
70
- name: job == null ? void 0 : job.name
71
- });
84
+ await emitInScope(
85
+ "job.stalled",
86
+ {
87
+ jobId,
88
+ queue: queueName,
89
+ name: job == null ? void 0 : job.name
90
+ },
91
+ (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId
92
+ );
72
93
  } catch {
73
94
  }
74
95
  });
@@ -46,12 +46,12 @@ var createLogger = ({ eventWriter, level = "info" } = {}) => {
46
46
  }
47
47
  };
48
48
  const emit = async (name, data = {}) => {
49
- var _a, _b, _c, _d;
49
+ var _a, _b, _c, _d, _e;
50
50
  const scope = Sentry.getIsolationScope();
51
51
  const scopeData = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
52
- const traceId = (_b = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _b.traceId;
53
- const userId = (_c = scopeData == null ? void 0 : scopeData.user) == null ? void 0 : _c.id;
54
- const organizationId = (_d = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _d.organization;
52
+ const traceId = ((_b = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _b.traceId) || ((_c = data == null ? void 0 : data.telemetry) == null ? void 0 : _c.id) || null;
53
+ const userId = ((_d = scopeData == null ? void 0 : scopeData.user) == null ? void 0 : _d.id) || (data == null ? void 0 : data.userId) || (data == null ? void 0 : data.user) || null;
54
+ const organizationId = ((_e = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _e.organization) || (data == null ? void 0 : data.organizationId) || (data == null ? void 0 : data.organization) || null;
55
55
  log.info(
56
56
  { event: name, traceId, userId, organizationId, ...data },
57
57
  "event:" + name
@@ -99,13 +99,13 @@ var attachProcessHandlers = () => {
99
99
  });
100
100
  });
101
101
  };
102
- var withTraceScope = (traceId, fn) => Sentry.withScope((scope) => {
102
+ var withTraceScope = (traceId, fn) => Sentry.withIsolationScope((scope) => {
103
103
  if (traceId) scope.setTag("traceId", traceId);
104
104
  return fn(scope);
105
105
  });
106
106
  var currentTraceId = () => {
107
107
  var _a, _b;
108
- const scope = Sentry.getCurrentScope();
108
+ const scope = Sentry.getIsolationScope();
109
109
  const data = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
110
110
  return (_b = data == null ? void 0 : data.tags) == null ? void 0 : _b.traceId;
111
111
  };
package/dist/index.cjs CHANGED
@@ -87,12 +87,12 @@ var createLogger = ({ eventWriter, level = "info" } = {}) => {
87
87
  }
88
88
  };
89
89
  const emit = async (name, data = {}) => {
90
- var _a, _b, _c, _d;
90
+ var _a, _b, _c, _d, _e;
91
91
  const scope = Sentry.getIsolationScope();
92
92
  const scopeData = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
93
- const traceId = (_b = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _b.traceId;
94
- const userId = (_c = scopeData == null ? void 0 : scopeData.user) == null ? void 0 : _c.id;
95
- const organizationId = (_d = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _d.organization;
93
+ const traceId = ((_b = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _b.traceId) || ((_c = data == null ? void 0 : data.telemetry) == null ? void 0 : _c.id) || null;
94
+ const userId = ((_d = scopeData == null ? void 0 : scopeData.user) == null ? void 0 : _d.id) || (data == null ? void 0 : data.userId) || (data == null ? void 0 : data.user) || null;
95
+ const organizationId = ((_e = scopeData == null ? void 0 : scopeData.tags) == null ? void 0 : _e.organization) || (data == null ? void 0 : data.organizationId) || (data == null ? void 0 : data.organization) || null;
96
96
  log.info(
97
97
  { event: name, traceId, userId, organizationId, ...data },
98
98
  "event:" + name
@@ -140,13 +140,13 @@ var attachProcessHandlers = () => {
140
140
  });
141
141
  });
142
142
  };
143
- var withTraceScope = (traceId, fn) => Sentry.withScope((scope) => {
143
+ var withTraceScope = (traceId, fn) => Sentry.withIsolationScope((scope) => {
144
144
  if (traceId) scope.setTag("traceId", traceId);
145
145
  return fn(scope);
146
146
  });
147
147
  var currentTraceId = () => {
148
148
  var _a, _b;
149
- const scope = Sentry.getCurrentScope();
149
+ const scope = Sentry.getIsolationScope();
150
150
  const data = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
151
151
  return (_b = data == null ? void 0 : data.tags) == null ? void 0 : _b.traceId;
152
152
  };
package/dist/index.d.cts CHANGED
@@ -115,9 +115,26 @@ const createLogger = ({ eventWriter, level = 'info' } = {}) => {
115
115
  const scope = Sentry.getIsolationScope();
116
116
  const scopeData = scope?.getScopeData?.();
117
117
 
118
- const traceId = scopeData?.tags?.traceId;
119
- const userId = scopeData?.user?.id;
120
- const organizationId = scopeData?.tags?.organization;
118
+ // Envelope fields prefer the active Sentry isolation scope (set by
119
+ // expressContextMiddleware in Express requests, by withTraceScope in
120
+ // BullMQ workers). Change-stream-driven emits frequently don't have
121
+ // an org/user on the scope but DO carry them in the data payload —
122
+ // fall back to those so the envelope stays populated and admin
123
+ // queries by `organizationId`/`userId` still match.
124
+
125
+ const traceId = scopeData?.tags?.traceId
126
+ || data?.telemetry?.id
127
+ || null;
128
+
129
+ const userId = scopeData?.user?.id
130
+ || data?.userId
131
+ || data?.user
132
+ || null;
133
+
134
+ const organizationId = scopeData?.tags?.organization
135
+ || data?.organizationId
136
+ || data?.organization
137
+ || null;
121
138
 
122
139
  log.info(
123
140
  { event : name, traceId, userId, organizationId, ...data },
@@ -210,7 +227,16 @@ const attachProcessHandlers = () => {
210
227
  // the forked scope so callers can attach additional tags without re-reading
211
228
  // the active scope. Returns whatever fn returns (including promises).
212
229
 
213
- const withTraceScope = ( traceId, fn ) => Sentry.withScope( ( scope ) => {
230
+ // Runs `fn` inside a forked isolation scope tagged with `traceId`.
231
+ // Important: must use withIsolationScope, NOT withScope. `Sentry.withScope`
232
+ // forks the *current* scope, which is invisible to consumers that read
233
+ // from `Sentry.getIsolationScope()` — including our own logger.emit and
234
+ // the pinoIntegration that ships log lines with isolation-scope tags
235
+ // attached. Using the wrong scope was the original cause of every
236
+ // telemetry row showing traceId: null even when wrapWorkerHandler had
237
+ // "set" the tag.
238
+
239
+ const withTraceScope = ( traceId, fn ) => Sentry.withIsolationScope( ( scope ) => {
214
240
 
215
241
  if( traceId ) scope.setTag( 'traceId', traceId );
216
242
 
@@ -224,7 +250,12 @@ const withTraceScope = ( traceId, fn ) => Sentry.withScope( ( scope ) => {
224
250
 
225
251
  const currentTraceId = () => {
226
252
 
227
- const scope = Sentry.getCurrentScope();
253
+ // Read from the isolation scope that's where withTraceScope and
254
+ // expressContextMiddleware both write traceId. Falling back to
255
+ // current scope would only pick up tags set inside an explicit
256
+ // Sentry.withScope block, which nothing in this codebase does.
257
+
258
+ const scope = Sentry.getIsolationScope();
228
259
  const data = scope?.getScopeData?.();
229
260
 
230
261
  return data?.tags?.traceId;
package/dist/index.d.ts CHANGED
@@ -115,9 +115,26 @@ const createLogger = ({ eventWriter, level = 'info' } = {}) => {
115
115
  const scope = Sentry.getIsolationScope();
116
116
  const scopeData = scope?.getScopeData?.();
117
117
 
118
- const traceId = scopeData?.tags?.traceId;
119
- const userId = scopeData?.user?.id;
120
- const organizationId = scopeData?.tags?.organization;
118
+ // Envelope fields prefer the active Sentry isolation scope (set by
119
+ // expressContextMiddleware in Express requests, by withTraceScope in
120
+ // BullMQ workers). Change-stream-driven emits frequently don't have
121
+ // an org/user on the scope but DO carry them in the data payload —
122
+ // fall back to those so the envelope stays populated and admin
123
+ // queries by `organizationId`/`userId` still match.
124
+
125
+ const traceId = scopeData?.tags?.traceId
126
+ || data?.telemetry?.id
127
+ || null;
128
+
129
+ const userId = scopeData?.user?.id
130
+ || data?.userId
131
+ || data?.user
132
+ || null;
133
+
134
+ const organizationId = scopeData?.tags?.organization
135
+ || data?.organizationId
136
+ || data?.organization
137
+ || null;
121
138
 
122
139
  log.info(
123
140
  { event : name, traceId, userId, organizationId, ...data },
@@ -210,7 +227,16 @@ const attachProcessHandlers = () => {
210
227
  // the forked scope so callers can attach additional tags without re-reading
211
228
  // the active scope. Returns whatever fn returns (including promises).
212
229
 
213
- const withTraceScope = ( traceId, fn ) => Sentry.withScope( ( scope ) => {
230
+ // Runs `fn` inside a forked isolation scope tagged with `traceId`.
231
+ // Important: must use withIsolationScope, NOT withScope. `Sentry.withScope`
232
+ // forks the *current* scope, which is invisible to consumers that read
233
+ // from `Sentry.getIsolationScope()` — including our own logger.emit and
234
+ // the pinoIntegration that ships log lines with isolation-scope tags
235
+ // attached. Using the wrong scope was the original cause of every
236
+ // telemetry row showing traceId: null even when wrapWorkerHandler had
237
+ // "set" the tag.
238
+
239
+ const withTraceScope = ( traceId, fn ) => Sentry.withIsolationScope( ( scope ) => {
214
240
 
215
241
  if( traceId ) scope.setTag( 'traceId', traceId );
216
242
 
@@ -224,7 +250,12 @@ const withTraceScope = ( traceId, fn ) => Sentry.withScope( ( scope ) => {
224
250
 
225
251
  const currentTraceId = () => {
226
252
 
227
- const scope = Sentry.getCurrentScope();
253
+ // Read from the isolation scope that's where withTraceScope and
254
+ // expressContextMiddleware both write traceId. Falling back to
255
+ // current scope would only pick up tags set inside an explicit
256
+ // Sentry.withScope block, which nothing in this codebase does.
257
+
258
+ const scope = Sentry.getIsolationScope();
228
259
  const data = scope?.getScopeData?.();
229
260
 
230
261
  return data?.tags?.traceId;
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  init,
9
9
  logger,
10
10
  withTraceScope
11
- } from "./chunk-CODZHH5I.js";
11
+ } from "./chunk-WUUTBLMK.js";
12
12
  export {
13
13
  Sentry,
14
14
  attachProcessHandlers,
package/package.json CHANGED
@@ -60,5 +60,5 @@
60
60
  "build": "tsup && npm publish"
61
61
  },
62
62
  "types": "dist/index.d.ts",
63
- "version": "0.0.8"
63
+ "version": "0.0.9"
64
64
  }