@drawbridge/drawbridge-telemetry 0.0.1
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 +61 -0
- package/dist/bullmq.cjs +108 -0
- package/dist/bullmq.d.cts +101 -0
- package/dist/bullmq.d.ts +101 -0
- package/dist/bullmq.js +62 -0
- package/dist/chunk-SRV5HFQT.js +50 -0
- package/dist/express.cjs +66 -0
- package/dist/express.d.cts +53 -0
- package/dist/express.d.ts +53 -0
- package/dist/express.js +32 -0
- package/dist/index.cjs +85 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +10 -0
- package/dist/stream.cjs +39 -0
- package/dist/stream.d.cts +28 -0
- package/dist/stream.d.ts +28 -0
- package/dist/stream.js +15 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @drawbridge/drawbridge-telemetry
|
|
2
|
+
|
|
3
|
+
Shared observability helpers for the drawbridge-* monorepo. Provides Sentry scope correlation across HTTP, BullMQ workers, and MongoDB change streams. Pino logger + event emitter arrive in Stage 2; OpenTelemetry SDK in Stage 4.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install --save-exact @drawbridge/drawbridge-telemetry
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires `@sentry/core` >= 10 as a peer dependency. Backend services satisfy this via `@sentry/node`; Next.js apps via `@sentry/nextjs`.
|
|
12
|
+
|
|
13
|
+
## Subpaths
|
|
14
|
+
|
|
15
|
+
| Subpath | Purpose |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@drawbridge/drawbridge-telemetry` | Core: `attachProcessHandlers`, `withTraceScope`, `currentTraceId` |
|
|
18
|
+
| `@drawbridge/drawbridge-telemetry/express` | `expressContextMiddleware` for Express apps |
|
|
19
|
+
| `@drawbridge/drawbridge-telemetry/bullmq` | `wrapWorkerHandler`, `enqueueFromWorker`, `attachQueueEventsLogger` |
|
|
20
|
+
| `@drawbridge/drawbridge-telemetry/stream` | `enqueueWithTrace` for MongoDB change-stream handlers |
|
|
21
|
+
|
|
22
|
+
## Naming conventions
|
|
23
|
+
|
|
24
|
+
Codified in the plan at `~/.claude/plans/yes-lets-see-it-enumerated-bumblebee.md`. Summary:
|
|
25
|
+
|
|
26
|
+
- **Event names** — `<noun>.<verb>` past tense, lowercase, dot-separated. Verbs match drawbridge-api's action status vocabulary (`processing` / `succeeded` / `failed`; never `pending` / `completed`). Nouns are singular and match Mongo collection names.
|
|
27
|
+
- **Tag keys** — camelCase. Canonical `traceId` everywhere; `X-Request-Id` header stays for wire compat.
|
|
28
|
+
- **Event envelope** — `{ name, traceId, userId, organizationId, data: {...}, createdAt }`.
|
|
29
|
+
- **Reserved keys in `job.data`** — `__traceId` (double-underscore prefix marks it as infrastructure data, not domain data).
|
|
30
|
+
|
|
31
|
+
## TraceId flow
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
HTTP request Mongo change event
|
|
35
|
+
│ │
|
|
36
|
+
▼ req.id (randomUUID) ▼ _id._data (resume token)
|
|
37
|
+
expressContextMiddleware enqueueWithTrace
|
|
38
|
+
│ │
|
|
39
|
+
│ scope.setTag('traceId',_) │ job.data.__traceId
|
|
40
|
+
▼ ▼
|
|
41
|
+
Sentry events for request BullMQ worker
|
|
42
|
+
│ wrapWorkerHandler reads
|
|
43
|
+
│ job.data.__traceId
|
|
44
|
+
▼
|
|
45
|
+
scope.setTag('traceId',_)
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
Downstream enqueues
|
|
49
|
+
via enqueueFromWorker
|
|
50
|
+
forward active scope's traceId
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For cron-style repeatable BullMQ jobs (no parent request or change event), synthesize a `__traceId` at enqueue time — pass it via `data.__traceId` to `enqueueFromWorker`.
|
|
54
|
+
|
|
55
|
+
## Build
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npm run build
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Builds CJS + ESM bundles into `dist/` and publishes. Bump `version` in package.json before publishing.
|
package/dist/bullmq.cjs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// bullmq.js
|
|
30
|
+
var bullmq_exports = {};
|
|
31
|
+
__export(bullmq_exports, {
|
|
32
|
+
attachQueueEventsLogger: () => attachQueueEventsLogger,
|
|
33
|
+
enqueueFromWorker: () => enqueueFromWorker,
|
|
34
|
+
wrapWorkerHandler: () => wrapWorkerHandler
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(bullmq_exports);
|
|
37
|
+
var Sentry2 = __toESM(require("@sentry/core"), 1);
|
|
38
|
+
|
|
39
|
+
// index.js
|
|
40
|
+
var Sentry = __toESM(require("@sentry/core"), 1);
|
|
41
|
+
var withTraceScope = (traceId, fn) => Sentry.withScope((scope) => {
|
|
42
|
+
if (traceId) scope.setTag("traceId", traceId);
|
|
43
|
+
return fn(scope);
|
|
44
|
+
});
|
|
45
|
+
var currentTraceId = () => {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
const scope = Sentry.getCurrentScope();
|
|
48
|
+
const data = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
|
|
49
|
+
return (_b = data == null ? void 0 : data.tags) == null ? void 0 : _b.traceId;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// bullmq.js
|
|
53
|
+
var wrapWorkerHandler = (handler) => async (jobData, job) => {
|
|
54
|
+
var _a;
|
|
55
|
+
const traceId = (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId;
|
|
56
|
+
return withTraceScope(traceId, (scope) => {
|
|
57
|
+
if (job == null ? void 0 : job.queueName) {
|
|
58
|
+
scope.setTag("queue", job.queueName);
|
|
59
|
+
}
|
|
60
|
+
;
|
|
61
|
+
if (job == null ? void 0 : job.name) {
|
|
62
|
+
scope.setTag("jobName", job.name);
|
|
63
|
+
}
|
|
64
|
+
;
|
|
65
|
+
return handler(jobData, job);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
var enqueueFromWorker = async (queue, name, data, options = {}) => {
|
|
69
|
+
const traceId = currentTraceId();
|
|
70
|
+
return queue.add(
|
|
71
|
+
name,
|
|
72
|
+
{
|
|
73
|
+
...data,
|
|
74
|
+
__traceId: (data == null ? void 0 : data.__traceId) || traceId
|
|
75
|
+
},
|
|
76
|
+
options
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
var attachQueueEventsLogger = (queueEvents, queueName, logger) => {
|
|
80
|
+
if (!(logger == null ? void 0 : logger.event)) return;
|
|
81
|
+
queueEvents.on("completed", ({ jobId, returnvalue, prev }) => {
|
|
82
|
+
logger.event("job.completed", {
|
|
83
|
+
jobId,
|
|
84
|
+
queue: queueName,
|
|
85
|
+
prev
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
queueEvents.on("failed", ({ jobId, failedReason, prev }) => {
|
|
89
|
+
logger.event("job.failed", {
|
|
90
|
+
jobId,
|
|
91
|
+
queue: queueName,
|
|
92
|
+
failedReason,
|
|
93
|
+
prev
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
queueEvents.on("stalled", ({ jobId }) => {
|
|
97
|
+
logger.event("job.stalled", {
|
|
98
|
+
jobId,
|
|
99
|
+
queue: queueName
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
104
|
+
0 && (module.exports = {
|
|
105
|
+
attachQueueEventsLogger,
|
|
106
|
+
enqueueFromWorker,
|
|
107
|
+
wrapWorkerHandler
|
|
108
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { currentTraceId, withTraceScope } from './index.cjs';
|
|
2
|
+
import '@sentry/core';
|
|
3
|
+
|
|
4
|
+
// Wrap a BullMQ worker handler so it runs inside a Sentry scope tagged
|
|
5
|
+
// with the job's traceId, queue name, and job name. The wrapped handler
|
|
6
|
+
// keeps the same `( jobData, job )` signature the existing queue-factory
|
|
7
|
+
// uses, so worker code itself doesn't change.
|
|
8
|
+
//
|
|
9
|
+
// The traceId originates from one of two places:
|
|
10
|
+
// - HTTP request: the API's req.id is forwarded as job.data.__traceId
|
|
11
|
+
// - Change stream: the Mongo resume token (_id._data) is forwarded
|
|
12
|
+
// Either way, the wrapper just reads job.data.__traceId.
|
|
13
|
+
|
|
14
|
+
const wrapWorkerHandler = ( handler ) => async ( jobData, job ) => {
|
|
15
|
+
|
|
16
|
+
const traceId = job?.data?.__traceId;
|
|
17
|
+
|
|
18
|
+
return withTraceScope( traceId, ( scope ) => {
|
|
19
|
+
|
|
20
|
+
if( job?.queueName ){
|
|
21
|
+
|
|
22
|
+
scope.setTag( 'queue', job.queueName );
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
if( job?.name ){
|
|
26
|
+
|
|
27
|
+
scope.setTag( 'jobName', job.name );
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
return handler( jobData, job );
|
|
31
|
+
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Enqueue a downstream BullMQ job from inside a worker handler, forwarding
|
|
37
|
+
// the active scope's traceId as job.data.__traceId. Use this in place of
|
|
38
|
+
// `queue.add( ... )` inside any handler that creates more work.
|
|
39
|
+
//
|
|
40
|
+
// If no traceId is on the active scope (e.g. enqueuing outside any scope),
|
|
41
|
+
// the new job inherits no trace — downstream events won't correlate. This
|
|
42
|
+
// is the expected fallback for cron-style jobs; pass an explicit __traceId
|
|
43
|
+
// in `data` if you want to override the active scope's value.
|
|
44
|
+
|
|
45
|
+
const enqueueFromWorker = async ( queue, name, data, options = {} ) => {
|
|
46
|
+
|
|
47
|
+
const traceId = currentTraceId();
|
|
48
|
+
|
|
49
|
+
return queue.add(
|
|
50
|
+
name,
|
|
51
|
+
{
|
|
52
|
+
...data,
|
|
53
|
+
__traceId : data?.__traceId || traceId
|
|
54
|
+
},
|
|
55
|
+
options
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Attach a QueueEvents listener that emits structured `job.completed` and
|
|
61
|
+
// `job.failed` records to the active Sentry logger. Use one per queue.
|
|
62
|
+
// Stage 2 only — Stage 1 ships the helper but it's a no-op until the
|
|
63
|
+
// unified logger lands.
|
|
64
|
+
|
|
65
|
+
const attachQueueEventsLogger = ( queueEvents, queueName, logger ) => {
|
|
66
|
+
|
|
67
|
+
if( ! logger?.event ) return;
|
|
68
|
+
|
|
69
|
+
queueEvents.on( 'completed', ({ jobId, returnvalue, prev }) => {
|
|
70
|
+
|
|
71
|
+
logger.event( 'job.completed', {
|
|
72
|
+
jobId,
|
|
73
|
+
queue : queueName,
|
|
74
|
+
prev
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
queueEvents.on( 'failed', ({ jobId, failedReason, prev }) => {
|
|
80
|
+
|
|
81
|
+
logger.event( 'job.failed', {
|
|
82
|
+
jobId,
|
|
83
|
+
queue : queueName,
|
|
84
|
+
failedReason,
|
|
85
|
+
prev
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
queueEvents.on( 'stalled', ({ jobId }) => {
|
|
91
|
+
|
|
92
|
+
logger.event( 'job.stalled', {
|
|
93
|
+
jobId,
|
|
94
|
+
queue : queueName
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export { attachQueueEventsLogger, enqueueFromWorker, wrapWorkerHandler };
|
package/dist/bullmq.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { currentTraceId, withTraceScope } from './index.js';
|
|
2
|
+
import '@sentry/core';
|
|
3
|
+
|
|
4
|
+
// Wrap a BullMQ worker handler so it runs inside a Sentry scope tagged
|
|
5
|
+
// with the job's traceId, queue name, and job name. The wrapped handler
|
|
6
|
+
// keeps the same `( jobData, job )` signature the existing queue-factory
|
|
7
|
+
// uses, so worker code itself doesn't change.
|
|
8
|
+
//
|
|
9
|
+
// The traceId originates from one of two places:
|
|
10
|
+
// - HTTP request: the API's req.id is forwarded as job.data.__traceId
|
|
11
|
+
// - Change stream: the Mongo resume token (_id._data) is forwarded
|
|
12
|
+
// Either way, the wrapper just reads job.data.__traceId.
|
|
13
|
+
|
|
14
|
+
const wrapWorkerHandler = ( handler ) => async ( jobData, job ) => {
|
|
15
|
+
|
|
16
|
+
const traceId = job?.data?.__traceId;
|
|
17
|
+
|
|
18
|
+
return withTraceScope( traceId, ( scope ) => {
|
|
19
|
+
|
|
20
|
+
if( job?.queueName ){
|
|
21
|
+
|
|
22
|
+
scope.setTag( 'queue', job.queueName );
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
if( job?.name ){
|
|
26
|
+
|
|
27
|
+
scope.setTag( 'jobName', job.name );
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
return handler( jobData, job );
|
|
31
|
+
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Enqueue a downstream BullMQ job from inside a worker handler, forwarding
|
|
37
|
+
// the active scope's traceId as job.data.__traceId. Use this in place of
|
|
38
|
+
// `queue.add( ... )` inside any handler that creates more work.
|
|
39
|
+
//
|
|
40
|
+
// If no traceId is on the active scope (e.g. enqueuing outside any scope),
|
|
41
|
+
// the new job inherits no trace — downstream events won't correlate. This
|
|
42
|
+
// is the expected fallback for cron-style jobs; pass an explicit __traceId
|
|
43
|
+
// in `data` if you want to override the active scope's value.
|
|
44
|
+
|
|
45
|
+
const enqueueFromWorker = async ( queue, name, data, options = {} ) => {
|
|
46
|
+
|
|
47
|
+
const traceId = currentTraceId();
|
|
48
|
+
|
|
49
|
+
return queue.add(
|
|
50
|
+
name,
|
|
51
|
+
{
|
|
52
|
+
...data,
|
|
53
|
+
__traceId : data?.__traceId || traceId
|
|
54
|
+
},
|
|
55
|
+
options
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Attach a QueueEvents listener that emits structured `job.completed` and
|
|
61
|
+
// `job.failed` records to the active Sentry logger. Use one per queue.
|
|
62
|
+
// Stage 2 only — Stage 1 ships the helper but it's a no-op until the
|
|
63
|
+
// unified logger lands.
|
|
64
|
+
|
|
65
|
+
const attachQueueEventsLogger = ( queueEvents, queueName, logger ) => {
|
|
66
|
+
|
|
67
|
+
if( ! logger?.event ) return;
|
|
68
|
+
|
|
69
|
+
queueEvents.on( 'completed', ({ jobId, returnvalue, prev }) => {
|
|
70
|
+
|
|
71
|
+
logger.event( 'job.completed', {
|
|
72
|
+
jobId,
|
|
73
|
+
queue : queueName,
|
|
74
|
+
prev
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
queueEvents.on( 'failed', ({ jobId, failedReason, prev }) => {
|
|
80
|
+
|
|
81
|
+
logger.event( 'job.failed', {
|
|
82
|
+
jobId,
|
|
83
|
+
queue : queueName,
|
|
84
|
+
failedReason,
|
|
85
|
+
prev
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
queueEvents.on( 'stalled', ({ jobId }) => {
|
|
91
|
+
|
|
92
|
+
logger.event( 'job.stalled', {
|
|
93
|
+
jobId,
|
|
94
|
+
queue : queueName
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export { attachQueueEventsLogger, enqueueFromWorker, wrapWorkerHandler };
|
package/dist/bullmq.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
currentTraceId,
|
|
3
|
+
withTraceScope
|
|
4
|
+
} from "./chunk-SRV5HFQT.js";
|
|
5
|
+
|
|
6
|
+
// bullmq.js
|
|
7
|
+
import * as Sentry from "@sentry/core";
|
|
8
|
+
var wrapWorkerHandler = (handler) => async (jobData, job) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const traceId = (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId;
|
|
11
|
+
return withTraceScope(traceId, (scope) => {
|
|
12
|
+
if (job == null ? void 0 : job.queueName) {
|
|
13
|
+
scope.setTag("queue", job.queueName);
|
|
14
|
+
}
|
|
15
|
+
;
|
|
16
|
+
if (job == null ? void 0 : job.name) {
|
|
17
|
+
scope.setTag("jobName", job.name);
|
|
18
|
+
}
|
|
19
|
+
;
|
|
20
|
+
return handler(jobData, job);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
var enqueueFromWorker = async (queue, name, data, options = {}) => {
|
|
24
|
+
const traceId = currentTraceId();
|
|
25
|
+
return queue.add(
|
|
26
|
+
name,
|
|
27
|
+
{
|
|
28
|
+
...data,
|
|
29
|
+
__traceId: (data == null ? void 0 : data.__traceId) || traceId
|
|
30
|
+
},
|
|
31
|
+
options
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
var attachQueueEventsLogger = (queueEvents, queueName, logger) => {
|
|
35
|
+
if (!(logger == null ? void 0 : logger.event)) return;
|
|
36
|
+
queueEvents.on("completed", ({ jobId, returnvalue, prev }) => {
|
|
37
|
+
logger.event("job.completed", {
|
|
38
|
+
jobId,
|
|
39
|
+
queue: queueName,
|
|
40
|
+
prev
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
queueEvents.on("failed", ({ jobId, failedReason, prev }) => {
|
|
44
|
+
logger.event("job.failed", {
|
|
45
|
+
jobId,
|
|
46
|
+
queue: queueName,
|
|
47
|
+
failedReason,
|
|
48
|
+
prev
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
queueEvents.on("stalled", ({ jobId }) => {
|
|
52
|
+
logger.event("job.stalled", {
|
|
53
|
+
jobId,
|
|
54
|
+
queue: queueName
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
export {
|
|
59
|
+
attachQueueEventsLogger,
|
|
60
|
+
enqueueFromWorker,
|
|
61
|
+
wrapWorkerHandler
|
|
62
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// index.js
|
|
2
|
+
import * as Sentry from "@sentry/core";
|
|
3
|
+
var attached = false;
|
|
4
|
+
var attachProcessHandlers = () => {
|
|
5
|
+
if (attached) return;
|
|
6
|
+
attached = true;
|
|
7
|
+
process.on("uncaughtException", (error) => {
|
|
8
|
+
Sentry.captureException(error, {
|
|
9
|
+
extra: {
|
|
10
|
+
source: "uncaughtException"
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
process.on("unhandledRejection", (reason) => {
|
|
15
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
16
|
+
Sentry.captureException(error, {
|
|
17
|
+
extra: {
|
|
18
|
+
source: "unhandledRejection"
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
process.on("exit", (code) => {
|
|
23
|
+
console.error("[process exit] code=" + code);
|
|
24
|
+
});
|
|
25
|
+
process.on("beforeExit", (code) => {
|
|
26
|
+
console.error("[process beforeExit] code=" + code + " \u2014 event loop empty");
|
|
27
|
+
});
|
|
28
|
+
const signals = ["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT", "SIGUSR2"];
|
|
29
|
+
signals.forEach((signal) => {
|
|
30
|
+
process.on(signal, () => {
|
|
31
|
+
console.error("[process signal] " + signal + " received");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
var withTraceScope = (traceId, fn) => Sentry.withScope((scope) => {
|
|
36
|
+
if (traceId) scope.setTag("traceId", traceId);
|
|
37
|
+
return fn(scope);
|
|
38
|
+
});
|
|
39
|
+
var currentTraceId = () => {
|
|
40
|
+
var _a, _b;
|
|
41
|
+
const scope = Sentry.getCurrentScope();
|
|
42
|
+
const data = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
|
|
43
|
+
return (_b = data == null ? void 0 : data.tags) == null ? void 0 : _b.traceId;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
attachProcessHandlers,
|
|
48
|
+
withTraceScope,
|
|
49
|
+
currentTraceId
|
|
50
|
+
};
|
package/dist/express.cjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// express.js
|
|
30
|
+
var express_exports = {};
|
|
31
|
+
__export(express_exports, {
|
|
32
|
+
expressContextMiddleware: () => expressContextMiddleware
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(express_exports);
|
|
35
|
+
var Sentry = __toESM(require("@sentry/core"), 1);
|
|
36
|
+
var expressContextMiddleware = () => (req, res, next) => {
|
|
37
|
+
const scope = Sentry.getCurrentScope();
|
|
38
|
+
if (req == null ? void 0 : req.id) {
|
|
39
|
+
scope.setTag("traceId", req.id);
|
|
40
|
+
}
|
|
41
|
+
;
|
|
42
|
+
if (req == null ? void 0 : req.user) {
|
|
43
|
+
scope.setUser({
|
|
44
|
+
id: req.user.id || req.user._id,
|
|
45
|
+
email: req.user.email
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
;
|
|
49
|
+
if (req == null ? void 0 : req.organization) {
|
|
50
|
+
scope.setTag("organization", req.organization.id || req.organization._id);
|
|
51
|
+
}
|
|
52
|
+
;
|
|
53
|
+
if (req == null ? void 0 : req.method) {
|
|
54
|
+
scope.setTag("method", req.method);
|
|
55
|
+
}
|
|
56
|
+
;
|
|
57
|
+
if (req == null ? void 0 : req.path) {
|
|
58
|
+
scope.setTag("route", req.path);
|
|
59
|
+
}
|
|
60
|
+
;
|
|
61
|
+
next();
|
|
62
|
+
};
|
|
63
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
64
|
+
0 && (module.exports = {
|
|
65
|
+
expressContextMiddleware
|
|
66
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as Sentry from '@sentry/core';
|
|
2
|
+
|
|
3
|
+
// Express middleware that tags the active Sentry scope with the current
|
|
4
|
+
// user/organization/route/method/traceId. Mount AFTER auth resolution (so
|
|
5
|
+
// req.user and req.organization are populated) and AFTER any req.id
|
|
6
|
+
// middleware (so req.id exists to use as the traceId).
|
|
7
|
+
//
|
|
8
|
+
// Every Sentry event captured during this request — and any downstream
|
|
9
|
+
// BullMQ jobs that propagate the traceId via job.data.__traceId — is
|
|
10
|
+
// filterable by these tags. Without this, errors arrive in Sentry as
|
|
11
|
+
// anonymous stack traces.
|
|
12
|
+
|
|
13
|
+
const expressContextMiddleware = () => ( req, res, next ) => {
|
|
14
|
+
|
|
15
|
+
const scope = Sentry.getCurrentScope();
|
|
16
|
+
|
|
17
|
+
if( req?.id ){
|
|
18
|
+
|
|
19
|
+
scope.setTag( 'traceId', req.id );
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
if( req?.user ){
|
|
23
|
+
|
|
24
|
+
scope.setUser({
|
|
25
|
+
id : req.user.id || req.user._id,
|
|
26
|
+
email : req.user.email
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
if( req?.organization ){
|
|
31
|
+
|
|
32
|
+
scope.setTag( 'organization', req.organization.id || req.organization._id );
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
if( req?.method ){
|
|
36
|
+
|
|
37
|
+
scope.setTag( 'method', req.method );
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
// req.route is set by Express only AFTER route matching, which hasn't
|
|
41
|
+
// happened yet at middleware time. Use req.path as a best-effort route
|
|
42
|
+
// tag — it captures the URL hit; aggregation by route pattern is left
|
|
43
|
+
// to a future res.on('finish') refinement.
|
|
44
|
+
if( req?.path ){
|
|
45
|
+
|
|
46
|
+
scope.setTag( 'route', req.path );
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
next();
|
|
50
|
+
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export { expressContextMiddleware };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as Sentry from '@sentry/core';
|
|
2
|
+
|
|
3
|
+
// Express middleware that tags the active Sentry scope with the current
|
|
4
|
+
// user/organization/route/method/traceId. Mount AFTER auth resolution (so
|
|
5
|
+
// req.user and req.organization are populated) and AFTER any req.id
|
|
6
|
+
// middleware (so req.id exists to use as the traceId).
|
|
7
|
+
//
|
|
8
|
+
// Every Sentry event captured during this request — and any downstream
|
|
9
|
+
// BullMQ jobs that propagate the traceId via job.data.__traceId — is
|
|
10
|
+
// filterable by these tags. Without this, errors arrive in Sentry as
|
|
11
|
+
// anonymous stack traces.
|
|
12
|
+
|
|
13
|
+
const expressContextMiddleware = () => ( req, res, next ) => {
|
|
14
|
+
|
|
15
|
+
const scope = Sentry.getCurrentScope();
|
|
16
|
+
|
|
17
|
+
if( req?.id ){
|
|
18
|
+
|
|
19
|
+
scope.setTag( 'traceId', req.id );
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
if( req?.user ){
|
|
23
|
+
|
|
24
|
+
scope.setUser({
|
|
25
|
+
id : req.user.id || req.user._id,
|
|
26
|
+
email : req.user.email
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
if( req?.organization ){
|
|
31
|
+
|
|
32
|
+
scope.setTag( 'organization', req.organization.id || req.organization._id );
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
if( req?.method ){
|
|
36
|
+
|
|
37
|
+
scope.setTag( 'method', req.method );
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
// req.route is set by Express only AFTER route matching, which hasn't
|
|
41
|
+
// happened yet at middleware time. Use req.path as a best-effort route
|
|
42
|
+
// tag — it captures the URL hit; aggregation by route pattern is left
|
|
43
|
+
// to a future res.on('finish') refinement.
|
|
44
|
+
if( req?.path ){
|
|
45
|
+
|
|
46
|
+
scope.setTag( 'route', req.path );
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
next();
|
|
50
|
+
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export { expressContextMiddleware };
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// express.js
|
|
2
|
+
import * as Sentry from "@sentry/core";
|
|
3
|
+
var expressContextMiddleware = () => (req, res, next) => {
|
|
4
|
+
const scope = Sentry.getCurrentScope();
|
|
5
|
+
if (req == null ? void 0 : req.id) {
|
|
6
|
+
scope.setTag("traceId", req.id);
|
|
7
|
+
}
|
|
8
|
+
;
|
|
9
|
+
if (req == null ? void 0 : req.user) {
|
|
10
|
+
scope.setUser({
|
|
11
|
+
id: req.user.id || req.user._id,
|
|
12
|
+
email: req.user.email
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
;
|
|
16
|
+
if (req == null ? void 0 : req.organization) {
|
|
17
|
+
scope.setTag("organization", req.organization.id || req.organization._id);
|
|
18
|
+
}
|
|
19
|
+
;
|
|
20
|
+
if (req == null ? void 0 : req.method) {
|
|
21
|
+
scope.setTag("method", req.method);
|
|
22
|
+
}
|
|
23
|
+
;
|
|
24
|
+
if (req == null ? void 0 : req.path) {
|
|
25
|
+
scope.setTag("route", req.path);
|
|
26
|
+
}
|
|
27
|
+
;
|
|
28
|
+
next();
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
expressContextMiddleware
|
|
32
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// index.js
|
|
30
|
+
var index_exports = {};
|
|
31
|
+
__export(index_exports, {
|
|
32
|
+
attachProcessHandlers: () => attachProcessHandlers,
|
|
33
|
+
currentTraceId: () => currentTraceId,
|
|
34
|
+
withTraceScope: () => withTraceScope
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var Sentry = __toESM(require("@sentry/core"), 1);
|
|
38
|
+
var attached = false;
|
|
39
|
+
var attachProcessHandlers = () => {
|
|
40
|
+
if (attached) return;
|
|
41
|
+
attached = true;
|
|
42
|
+
process.on("uncaughtException", (error) => {
|
|
43
|
+
Sentry.captureException(error, {
|
|
44
|
+
extra: {
|
|
45
|
+
source: "uncaughtException"
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
process.on("unhandledRejection", (reason) => {
|
|
50
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
51
|
+
Sentry.captureException(error, {
|
|
52
|
+
extra: {
|
|
53
|
+
source: "unhandledRejection"
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
process.on("exit", (code) => {
|
|
58
|
+
console.error("[process exit] code=" + code);
|
|
59
|
+
});
|
|
60
|
+
process.on("beforeExit", (code) => {
|
|
61
|
+
console.error("[process beforeExit] code=" + code + " \u2014 event loop empty");
|
|
62
|
+
});
|
|
63
|
+
const signals = ["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT", "SIGUSR2"];
|
|
64
|
+
signals.forEach((signal) => {
|
|
65
|
+
process.on(signal, () => {
|
|
66
|
+
console.error("[process signal] " + signal + " received");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
var withTraceScope = (traceId, fn) => Sentry.withScope((scope) => {
|
|
71
|
+
if (traceId) scope.setTag("traceId", traceId);
|
|
72
|
+
return fn(scope);
|
|
73
|
+
});
|
|
74
|
+
var currentTraceId = () => {
|
|
75
|
+
var _a, _b;
|
|
76
|
+
const scope = Sentry.getCurrentScope();
|
|
77
|
+
const data = (_a = scope == null ? void 0 : scope.getScopeData) == null ? void 0 : _a.call(scope);
|
|
78
|
+
return (_b = data == null ? void 0 : data.tags) == null ? void 0 : _b.traceId;
|
|
79
|
+
};
|
|
80
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
81
|
+
0 && (module.exports = {
|
|
82
|
+
attachProcessHandlers,
|
|
83
|
+
currentTraceId,
|
|
84
|
+
withTraceScope
|
|
85
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as Sentry from '@sentry/core';
|
|
2
|
+
|
|
3
|
+
// Diagnostic process-level handlers. Surfaces unhandled async errors to
|
|
4
|
+
// Sentry (without these, modern Node defaults silently exit) and logs
|
|
5
|
+
// exit-path signals to stderr so an operator can diagnose crashes.
|
|
6
|
+
// Does NOT trigger shutdown — each service owns its shutdown logic.
|
|
7
|
+
//
|
|
8
|
+
// Idempotent: calling twice is a no-op so accidental double-registration
|
|
9
|
+
// during hot reloads or test setups doesn't stack listeners.
|
|
10
|
+
|
|
11
|
+
let attached = false;
|
|
12
|
+
|
|
13
|
+
const attachProcessHandlers = () => {
|
|
14
|
+
|
|
15
|
+
if( attached ) return;
|
|
16
|
+
|
|
17
|
+
attached = true;
|
|
18
|
+
|
|
19
|
+
process.on( 'uncaughtException', ( error ) => {
|
|
20
|
+
|
|
21
|
+
Sentry.captureException( error, {
|
|
22
|
+
extra : {
|
|
23
|
+
source : 'uncaughtException'
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
process.on( 'unhandledRejection', ( reason ) => {
|
|
30
|
+
|
|
31
|
+
const error = reason instanceof Error ? reason : new Error( String( reason ) );
|
|
32
|
+
|
|
33
|
+
Sentry.captureException( error, {
|
|
34
|
+
extra : {
|
|
35
|
+
source : 'unhandledRejection'
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Sentry can't flush from here — V8 is tearing down. stderr only.
|
|
42
|
+
process.on( 'exit', ( code ) => {
|
|
43
|
+
|
|
44
|
+
console.error( '[process exit] code=' + code );
|
|
45
|
+
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
process.on( 'beforeExit', ( code ) => {
|
|
49
|
+
|
|
50
|
+
console.error( '[process beforeExit] code=' + code + ' — event loop empty' );
|
|
51
|
+
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const signals = [ 'SIGTERM', 'SIGINT', 'SIGHUP', 'SIGQUIT', 'SIGUSR2' ];
|
|
55
|
+
|
|
56
|
+
signals.forEach( ( signal ) => {
|
|
57
|
+
|
|
58
|
+
process.on( signal, () => {
|
|
59
|
+
|
|
60
|
+
console.error( '[process signal] ' + signal + ' received' );
|
|
61
|
+
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Run fn inside a Sentry scope with traceId tag set. The callback receives
|
|
69
|
+
// the forked scope so callers can attach additional tags without re-reading
|
|
70
|
+
// the active scope. Returns whatever fn returns (including promises).
|
|
71
|
+
|
|
72
|
+
const withTraceScope = ( traceId, fn ) => Sentry.withScope( ( scope ) => {
|
|
73
|
+
|
|
74
|
+
if( traceId ) scope.setTag( 'traceId', traceId );
|
|
75
|
+
|
|
76
|
+
return fn( scope );
|
|
77
|
+
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
// Read the active scope's traceId. Used by helpers that need to forward
|
|
81
|
+
// the current request/job's trace into downstream work (e.g. enqueuing a
|
|
82
|
+
// new BullMQ job from inside a worker handler).
|
|
83
|
+
|
|
84
|
+
const currentTraceId = () => {
|
|
85
|
+
|
|
86
|
+
const scope = Sentry.getCurrentScope();
|
|
87
|
+
const data = scope?.getScopeData?.();
|
|
88
|
+
|
|
89
|
+
return data?.tags?.traceId;
|
|
90
|
+
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { attachProcessHandlers, currentTraceId, withTraceScope };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as Sentry from '@sentry/core';
|
|
2
|
+
|
|
3
|
+
// Diagnostic process-level handlers. Surfaces unhandled async errors to
|
|
4
|
+
// Sentry (without these, modern Node defaults silently exit) and logs
|
|
5
|
+
// exit-path signals to stderr so an operator can diagnose crashes.
|
|
6
|
+
// Does NOT trigger shutdown — each service owns its shutdown logic.
|
|
7
|
+
//
|
|
8
|
+
// Idempotent: calling twice is a no-op so accidental double-registration
|
|
9
|
+
// during hot reloads or test setups doesn't stack listeners.
|
|
10
|
+
|
|
11
|
+
let attached = false;
|
|
12
|
+
|
|
13
|
+
const attachProcessHandlers = () => {
|
|
14
|
+
|
|
15
|
+
if( attached ) return;
|
|
16
|
+
|
|
17
|
+
attached = true;
|
|
18
|
+
|
|
19
|
+
process.on( 'uncaughtException', ( error ) => {
|
|
20
|
+
|
|
21
|
+
Sentry.captureException( error, {
|
|
22
|
+
extra : {
|
|
23
|
+
source : 'uncaughtException'
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
process.on( 'unhandledRejection', ( reason ) => {
|
|
30
|
+
|
|
31
|
+
const error = reason instanceof Error ? reason : new Error( String( reason ) );
|
|
32
|
+
|
|
33
|
+
Sentry.captureException( error, {
|
|
34
|
+
extra : {
|
|
35
|
+
source : 'unhandledRejection'
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Sentry can't flush from here — V8 is tearing down. stderr only.
|
|
42
|
+
process.on( 'exit', ( code ) => {
|
|
43
|
+
|
|
44
|
+
console.error( '[process exit] code=' + code );
|
|
45
|
+
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
process.on( 'beforeExit', ( code ) => {
|
|
49
|
+
|
|
50
|
+
console.error( '[process beforeExit] code=' + code + ' — event loop empty' );
|
|
51
|
+
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const signals = [ 'SIGTERM', 'SIGINT', 'SIGHUP', 'SIGQUIT', 'SIGUSR2' ];
|
|
55
|
+
|
|
56
|
+
signals.forEach( ( signal ) => {
|
|
57
|
+
|
|
58
|
+
process.on( signal, () => {
|
|
59
|
+
|
|
60
|
+
console.error( '[process signal] ' + signal + ' received' );
|
|
61
|
+
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Run fn inside a Sentry scope with traceId tag set. The callback receives
|
|
69
|
+
// the forked scope so callers can attach additional tags without re-reading
|
|
70
|
+
// the active scope. Returns whatever fn returns (including promises).
|
|
71
|
+
|
|
72
|
+
const withTraceScope = ( traceId, fn ) => Sentry.withScope( ( scope ) => {
|
|
73
|
+
|
|
74
|
+
if( traceId ) scope.setTag( 'traceId', traceId );
|
|
75
|
+
|
|
76
|
+
return fn( scope );
|
|
77
|
+
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
// Read the active scope's traceId. Used by helpers that need to forward
|
|
81
|
+
// the current request/job's trace into downstream work (e.g. enqueuing a
|
|
82
|
+
// new BullMQ job from inside a worker handler).
|
|
83
|
+
|
|
84
|
+
const currentTraceId = () => {
|
|
85
|
+
|
|
86
|
+
const scope = Sentry.getCurrentScope();
|
|
87
|
+
const data = scope?.getScopeData?.();
|
|
88
|
+
|
|
89
|
+
return data?.tags?.traceId;
|
|
90
|
+
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { attachProcessHandlers, currentTraceId, withTraceScope };
|
package/dist/index.js
ADDED
package/dist/stream.cjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// stream.js
|
|
20
|
+
var stream_exports = {};
|
|
21
|
+
__export(stream_exports, {
|
|
22
|
+
enqueueWithTrace: () => enqueueWithTrace
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(stream_exports);
|
|
25
|
+
var enqueueWithTrace = async (queue, name, data, resumeToken, options = {}) => {
|
|
26
|
+
const traceId = (resumeToken == null ? void 0 : resumeToken._data) || String(resumeToken);
|
|
27
|
+
return queue.add(
|
|
28
|
+
name,
|
|
29
|
+
{
|
|
30
|
+
...data,
|
|
31
|
+
__traceId: traceId
|
|
32
|
+
},
|
|
33
|
+
options
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
37
|
+
0 && (module.exports = {
|
|
38
|
+
enqueueWithTrace
|
|
39
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Enqueue a BullMQ job from a change-stream handler, forwarding the
|
|
2
|
+
// MongoDB change-event resume token as the __traceId. The resume token
|
|
3
|
+
// (_id._data) is identical for every cursor observing the same oplog
|
|
4
|
+
// event, so all sync replicas compute the same traceId — combined with
|
|
5
|
+
// the BullMQ jobId derived from the same token, this gives both per-event
|
|
6
|
+
// dedup (jobId) AND per-event trace correlation (traceId) from a single
|
|
7
|
+
// source of truth.
|
|
8
|
+
//
|
|
9
|
+
// Callers retain control of the jobId via `options` — the resume-token-as-
|
|
10
|
+
// jobId convention lives in stream.js, not here, because it depends on the
|
|
11
|
+
// caller's choice of collection/operation namespace.
|
|
12
|
+
|
|
13
|
+
const enqueueWithTrace = async ( queue, name, data, resumeToken, options = {} ) => {
|
|
14
|
+
|
|
15
|
+
const traceId = resumeToken?._data || String( resumeToken );
|
|
16
|
+
|
|
17
|
+
return queue.add(
|
|
18
|
+
name,
|
|
19
|
+
{
|
|
20
|
+
...data,
|
|
21
|
+
__traceId : traceId
|
|
22
|
+
},
|
|
23
|
+
options
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { enqueueWithTrace };
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Enqueue a BullMQ job from a change-stream handler, forwarding the
|
|
2
|
+
// MongoDB change-event resume token as the __traceId. The resume token
|
|
3
|
+
// (_id._data) is identical for every cursor observing the same oplog
|
|
4
|
+
// event, so all sync replicas compute the same traceId — combined with
|
|
5
|
+
// the BullMQ jobId derived from the same token, this gives both per-event
|
|
6
|
+
// dedup (jobId) AND per-event trace correlation (traceId) from a single
|
|
7
|
+
// source of truth.
|
|
8
|
+
//
|
|
9
|
+
// Callers retain control of the jobId via `options` — the resume-token-as-
|
|
10
|
+
// jobId convention lives in stream.js, not here, because it depends on the
|
|
11
|
+
// caller's choice of collection/operation namespace.
|
|
12
|
+
|
|
13
|
+
const enqueueWithTrace = async ( queue, name, data, resumeToken, options = {} ) => {
|
|
14
|
+
|
|
15
|
+
const traceId = resumeToken?._data || String( resumeToken );
|
|
16
|
+
|
|
17
|
+
return queue.add(
|
|
18
|
+
name,
|
|
19
|
+
{
|
|
20
|
+
...data,
|
|
21
|
+
__traceId : traceId
|
|
22
|
+
},
|
|
23
|
+
options
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { enqueueWithTrace };
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// stream.js
|
|
2
|
+
var enqueueWithTrace = async (queue, name, data, resumeToken, options = {}) => {
|
|
3
|
+
const traceId = (resumeToken == null ? void 0 : resumeToken._data) || String(resumeToken);
|
|
4
|
+
return queue.add(
|
|
5
|
+
name,
|
|
6
|
+
{
|
|
7
|
+
...data,
|
|
8
|
+
__traceId: traceId
|
|
9
|
+
},
|
|
10
|
+
options
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
enqueueWithTrace
|
|
15
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"dependencies": {
|
|
4
|
+
"tsup": "8.5.1",
|
|
5
|
+
"typescript": "5.9.3"
|
|
6
|
+
},
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"@sentry/core": ">=10"
|
|
9
|
+
},
|
|
10
|
+
"peerDependenciesMeta": {
|
|
11
|
+
"@sentry/core": {
|
|
12
|
+
"optional": false
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@sentry/core": "10.27.0"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./express": {
|
|
25
|
+
"types": "./dist/express.d.ts",
|
|
26
|
+
"import": "./dist/express.js",
|
|
27
|
+
"require": "./dist/express.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./bullmq": {
|
|
30
|
+
"types": "./dist/bullmq.d.ts",
|
|
31
|
+
"import": "./dist/bullmq.js",
|
|
32
|
+
"require": "./dist/bullmq.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./stream": {
|
|
35
|
+
"types": "./dist/stream.d.ts",
|
|
36
|
+
"import": "./dist/stream.js",
|
|
37
|
+
"require": "./dist/stream.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist"
|
|
42
|
+
],
|
|
43
|
+
"license": "ISC",
|
|
44
|
+
"main": "dist/index.cjs",
|
|
45
|
+
"module": "dist/index.js",
|
|
46
|
+
"name": "@drawbridge/drawbridge-telemetry",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"sync": ". \"$HOME/.nvm/nvm.sh\" && nvm use && npm prune && npm install",
|
|
52
|
+
"build": "tsup && npm publish"
|
|
53
|
+
},
|
|
54
|
+
"types": "dist/index.d.ts",
|
|
55
|
+
"version": "0.0.1"
|
|
56
|
+
}
|