@aidra/pmx-io 0.0.0-stage → 9.14.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 +69 -2
- package/bin/pmx-io-node.js +143 -0
- package/index.d.ts +381 -0
- package/index.js +27 -0
- package/index.mjs +47 -0
- package/lib/actions.js +120 -0
- package/lib/activate.js +402 -0
- package/lib/application.js +106 -0
- package/lib/bun.js +155 -0
- package/lib/config.js +67 -0
- package/lib/constants.js +17 -0
- package/lib/debug.js +48 -0
- package/lib/esm-hooks.mjs +111 -0
- package/lib/exit.js +79 -0
- package/lib/features/metrics.js +409 -0
- package/lib/features/notify.js +358 -0
- package/lib/features/profiling.js +208 -0
- package/lib/hooks.js +151 -0
- package/lib/inbound.js +174 -0
- package/lib/integrations.js +372 -0
- package/lib/itg/amqplib.js +169 -0
- package/lib/itg/bullmq.js +141 -0
- package/lib/itg/common.js +657 -0
- package/lib/itg/dbcomment.js +114 -0
- package/lib/itg/index.js +103 -0
- package/lib/itg/ioredis.js +212 -0
- package/lib/itg/kafkajs.js +205 -0
- package/lib/itg/mongodb.js +322 -0
- package/lib/itg/mysql2.js +205 -0
- package/lib/itg/nats.js +531 -0
- package/lib/itg/noderedis.js +236 -0
- package/lib/itg/pg.js +253 -0
- package/lib/itg/pgqueue.js +305 -0
- package/lib/itg/postgres.js +252 -0
- package/lib/itg/redis-common.js +265 -0
- package/lib/itg/sql.js +302 -0
- package/lib/metrics/service.js +286 -0
- package/lib/metrics/types.js +342 -0
- package/lib/modules.js +260 -0
- package/lib/node-options.js +62 -0
- package/lib/otel/bridge.js +797 -0
- package/lib/otel/env.js +114 -0
- package/lib/outbound.js +362 -0
- package/lib/pmx.js +757 -0
- package/lib/relay/hub.js +279 -0
- package/lib/relay/transport.js +253 -0
- package/lib/standalone/activate.js +307 -0
- package/lib/standalone/capture.js +55 -0
- package/lib/standalone/container.js +909 -0
- package/lib/standalone/transport.js +1023 -0
- package/lib/tracing/context.js +90 -0
- package/lib/tracing/control.js +124 -0
- package/lib/tracing/exporter.js +115 -0
- package/lib/tracing/index.js +242 -0
- package/lib/tracing/logs.js +176 -0
- package/lib/tracing/options.js +105 -0
- package/lib/tracing/paths.js +34 -0
- package/lib/tracing/tracer.js +850 -0
- package/lib/tracing/w3c.js +415 -0
- package/lib/transport/ipc.js +97 -0
- package/lib/transport/ws.js +511 -0
- package/lib/util.js +109 -0
- package/package.json +48 -4
- package/preload.cjs +26 -0
- package/preload.mjs +25 -0
- package/standalone.cjs +16 -0
- package/standalone.mjs +17 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Common rules of the database and broker integrations (82 §1, §2, §4; the Node counterpart of
|
|
5
|
+
// pmx-io's _msg.py / _db.py helpers): switches, CLIENT/PRODUCER/CONSUMER/receive span creation
|
|
6
|
+
// and parenting, error marking, promise/callback completion, header carriers, links for batches,
|
|
7
|
+
// iterator-style consumer spans and the exit flush of open iterator spans. The per-library modules
|
|
8
|
+
// (lib/itg/<library>.js) only map library calls onto these helpers.
|
|
9
|
+
//
|
|
10
|
+
// Everything here is defensive: a failure in pmx code is logged under `axm:tracing` and never
|
|
11
|
+
// reaches the app (ITG-003). Loaded only when an instrumented library is loaded with tracing on.
|
|
12
|
+
|
|
13
|
+
const ctx = require('../tracing/context')
|
|
14
|
+
const w3c = require('../tracing/w3c')
|
|
15
|
+
const debug = require('../debug')('axm:tracing')
|
|
16
|
+
|
|
17
|
+
const MODULES = Symbol.for('@pmx/io:modules')
|
|
18
|
+
const ERROR_MAX = 500
|
|
19
|
+
const TRACE_KEY = '__pmx_trace__' // pmx payload carrier (ITG-220)
|
|
20
|
+
const WRAPPED = Symbol.for('@pmx/io:itg-wrapped')
|
|
21
|
+
const ORIGINAL = Symbol.for('@pmx/io:itg-original')
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------------------------
|
|
24
|
+
// switches (ITG-001)
|
|
25
|
+
|
|
26
|
+
/** The tracer while tracing is enabled, else null (wrappers then pass straight through). */
|
|
27
|
+
function tracer () {
|
|
28
|
+
const r = globalThis[MODULES]
|
|
29
|
+
if (!r || !r.wants.tracing) return null
|
|
30
|
+
const f = r.tracing
|
|
31
|
+
if (!f || !f._enabled) return null
|
|
32
|
+
const t = f._tracer
|
|
33
|
+
return t && t.enabled ? t : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The tracer when database spans are wanted now, else null. */
|
|
37
|
+
function dbTracer () {
|
|
38
|
+
const t = tracer()
|
|
39
|
+
return t && t.db ? t : null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The tracer when broker spans are wanted now, else null. */
|
|
43
|
+
function msgTracer () {
|
|
44
|
+
const t = tracer()
|
|
45
|
+
return t && t.messaging ? t : null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The current span (a span of this process or, in bridge mode, of the app's SDK), or undefined. */
|
|
49
|
+
function currentSpan () {
|
|
50
|
+
const s = ctx.current()
|
|
51
|
+
return s ? s.span : undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// One logical operation, one span (ITG-005): an outer wrapper that created a span runs the inner
|
|
55
|
+
// layers with `depth` raised (synchronous nesting); asynchronous nesting is marked per library on
|
|
56
|
+
// the objects that pass between the layers.
|
|
57
|
+
let depth = 0
|
|
58
|
+
|
|
59
|
+
function nested () {
|
|
60
|
+
return depth > 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Call fn with the nesting marker raised (inner wrappers then do nothing). */
|
|
64
|
+
function outer (fn, thisArg, args) {
|
|
65
|
+
depth++
|
|
66
|
+
try {
|
|
67
|
+
return fn.apply(thisArg, args)
|
|
68
|
+
} finally {
|
|
69
|
+
depth--
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function disabled (lib, why) {
|
|
74
|
+
debug('%s: instrumentation disabled (%s)', lib, why)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------------------------
|
|
78
|
+
// spans
|
|
79
|
+
|
|
80
|
+
/** A CLIENT span under the current span, or null (no root CLIENT spans, ITG-010). */
|
|
81
|
+
function clientSpan (t, name, tags, kind) {
|
|
82
|
+
const parent = currentSpan()
|
|
83
|
+
if (!parent) return null
|
|
84
|
+
try {
|
|
85
|
+
return t.childOf(parent, name, kind || 'CLIENT', tags)
|
|
86
|
+
} catch (err) {
|
|
87
|
+
debug('span failed: %s', err && err.message)
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function msgTags (system, destination, operation, extra) {
|
|
93
|
+
const tags = { 'messaging.system': system }
|
|
94
|
+
if (destination) tags['messaging.destination.name'] = destination
|
|
95
|
+
if (operation) tags['messaging.operation'] = operation
|
|
96
|
+
if (extra) {
|
|
97
|
+
for (const k of Object.keys(extra)) {
|
|
98
|
+
if (extra[k] !== undefined && extra[k] !== null) tags[k] = extra[k]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return tags
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* ITG-020/021: `publish <destination>` (`request <destination>` for request/reply, kind CLIENT),
|
|
106
|
+
* a child of the current span; without one only with `messaging.rootProducers`. null when no span
|
|
107
|
+
* is to be created (nothing is injected then).
|
|
108
|
+
*/
|
|
109
|
+
function producer (system, destination, tags, kind, verb) {
|
|
110
|
+
const t = msgTracer()
|
|
111
|
+
if (!t) return null
|
|
112
|
+
kind = kind || 'PRODUCER'
|
|
113
|
+
try {
|
|
114
|
+
const name = (verb || 'publish') + ' ' + (destination || 'multiple')
|
|
115
|
+
const attrs = msgTags(system, destination, kind === 'PRODUCER' ? 'publish' : undefined, tags)
|
|
116
|
+
const parent = currentSpan()
|
|
117
|
+
if (parent) return t.childOf(parent, name, kind, attrs)
|
|
118
|
+
if (t.messaging.rootProducers) return t.startSpan(name, { kind, attributes: attrs, parent: w3c.NEW_TRACE })
|
|
119
|
+
} catch (err) {
|
|
120
|
+
debug('producer span failed: %s', err && err.message)
|
|
121
|
+
}
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* ITG-021: a CONSUMER `process <destination>` entry span; parent = the message's context, else
|
|
127
|
+
* the current span, else a new trace.
|
|
128
|
+
*/
|
|
129
|
+
function consumer (system, destination, context, tags) {
|
|
130
|
+
const t = msgTracer()
|
|
131
|
+
if (!t) return null
|
|
132
|
+
try {
|
|
133
|
+
const name = 'process ' + (destination || 'multiple')
|
|
134
|
+
const attrs = msgTags(system, destination, 'process', tags)
|
|
135
|
+
if (w3c.isSpanContext(context)) return t.startSpan(name, { kind: 'CONSUMER', attributes: attrs, parent: context })
|
|
136
|
+
const cur = currentSpan()
|
|
137
|
+
return t.startSpan(name, { kind: 'CONSUMER', attributes: attrs, parent: cur || w3c.NEW_TRACE })
|
|
138
|
+
} catch (err) {
|
|
139
|
+
debug('consumer span failed: %s', err && err.message)
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* ITG-023/024: a CONSUMER span handling several messages (`process` for a batch callback,
|
|
146
|
+
* `receive` for a batch pull): parent = the current span, else a new trace; one link per message
|
|
147
|
+
* carrying a context, at most `messaging.maxLinks`.
|
|
148
|
+
*/
|
|
149
|
+
function batchConsumer (system, destination, contexts, tags, operation) {
|
|
150
|
+
const t = msgTracer()
|
|
151
|
+
if (!t) return null
|
|
152
|
+
operation = operation || 'process'
|
|
153
|
+
try {
|
|
154
|
+
const name = operation + ' ' + (destination || 'multiple')
|
|
155
|
+
const attrs = msgTags(system, destination, operation, tags)
|
|
156
|
+
const cur = currentSpan()
|
|
157
|
+
const span = t.startSpan(name, { kind: 'CONSUMER', attributes: attrs, parent: cur || w3c.NEW_TRACE })
|
|
158
|
+
span.maxLinks = t.messaging.maxLinks
|
|
159
|
+
if (contexts) for (const c of contexts) addLink(span, c)
|
|
160
|
+
return span
|
|
161
|
+
} catch (err) {
|
|
162
|
+
debug('batch span failed: %s', err && err.message)
|
|
163
|
+
return null
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function addLink (span, c) {
|
|
168
|
+
if (span && w3c.isSpanContext(c)) span.addLink(c)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** ITG-013: `<error.name>: <first line of error.message>`, at most 500 characters. */
|
|
172
|
+
function errorText (err) {
|
|
173
|
+
let name
|
|
174
|
+
let msg
|
|
175
|
+
if (err && typeof err === 'object') {
|
|
176
|
+
name = typeof err.name === 'string' && err.name ? err.name : (err.constructor && err.constructor.name) || 'Error'
|
|
177
|
+
msg = typeof err.message === 'string' ? err.message : ''
|
|
178
|
+
} else {
|
|
179
|
+
name = 'Error'
|
|
180
|
+
msg = String(err)
|
|
181
|
+
}
|
|
182
|
+
const first = msg.split(/\r?\n/)[0]
|
|
183
|
+
const text = first ? name + ': ' + first : name
|
|
184
|
+
return text.length > ERROR_MAX ? text.slice(0, ERROR_MAX) : text
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function fail (span, err, code) {
|
|
188
|
+
if (!span) return
|
|
189
|
+
try {
|
|
190
|
+
span.setStatus('ERROR', errorText(err))
|
|
191
|
+
if (code !== undefined && code !== null && code !== '') span.setAttribute('db.response.status_code', String(code))
|
|
192
|
+
} catch (_) {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function end (span, err, code) {
|
|
196
|
+
if (!span) return
|
|
197
|
+
try {
|
|
198
|
+
if (err !== undefined && err !== null) fail(span, err, code)
|
|
199
|
+
span.end()
|
|
200
|
+
} catch (e) {
|
|
201
|
+
debug('span end failed: %s', e && e.message)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isThenable (v) {
|
|
206
|
+
return v !== null && (typeof v === 'object' || typeof v === 'function') && typeof v.then === 'function'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Ends `span` when `p` settles; returns a promise settling like `p` (a derived promise: an app
|
|
211
|
+
* that ignores a rejection still gets its unhandled-rejection report). `onValue(v)` may tag rows,
|
|
212
|
+
* `codeOf(err)` gives the server error code.
|
|
213
|
+
*/
|
|
214
|
+
function settle (p, span, onValue, codeOf) {
|
|
215
|
+
return p.then((v) => {
|
|
216
|
+
if (onValue) {
|
|
217
|
+
try {
|
|
218
|
+
onValue(v)
|
|
219
|
+
} catch (_) {}
|
|
220
|
+
}
|
|
221
|
+
end(span)
|
|
222
|
+
return v
|
|
223
|
+
}, (err) => {
|
|
224
|
+
end(span, err, codeOf ? safe(codeOf, err) : undefined)
|
|
225
|
+
throw err
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function safe (fn, arg) {
|
|
230
|
+
try {
|
|
231
|
+
return fn(arg)
|
|
232
|
+
} catch (_) {
|
|
233
|
+
return undefined
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Run fn with `span` current (and its baggage); returns fn's result. */
|
|
238
|
+
function withSpan (span, fn, thisArg, args) {
|
|
239
|
+
if (!span) return fn.apply(thisArg, args || [])
|
|
240
|
+
const store = ctx.current()
|
|
241
|
+
return ctx.run({ span, bag: span.bag, server: store && store.server }, fn, thisArg, args)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Callback style (ITG-023): run fn with `span` current; the span ends when fn returns, throws,
|
|
246
|
+
* or (for a thenable result) when that settles; errors mark it ERROR and propagate unchanged.
|
|
247
|
+
*/
|
|
248
|
+
function runConsumer (span, fn, thisArg, args) {
|
|
249
|
+
if (!span) return fn.apply(thisArg, args || [])
|
|
250
|
+
let r
|
|
251
|
+
try {
|
|
252
|
+
r = withSpan(span, fn, thisArg, args)
|
|
253
|
+
} catch (err) {
|
|
254
|
+
end(span, err)
|
|
255
|
+
throw err
|
|
256
|
+
}
|
|
257
|
+
if (isThenable(r)) {
|
|
258
|
+
try {
|
|
259
|
+
r.then(() => end(span), (err) => end(span, err))
|
|
260
|
+
} catch (err) {
|
|
261
|
+
end(span)
|
|
262
|
+
}
|
|
263
|
+
return r
|
|
264
|
+
}
|
|
265
|
+
end(span)
|
|
266
|
+
return r
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Bind a callback to the context current now (callbacks a library calls later, NIO-203). */
|
|
270
|
+
function bind (fn) {
|
|
271
|
+
return typeof fn === 'function' ? ctx.bind(fn) : fn
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------------------------
|
|
275
|
+
// carriers (ITG-022, ITG-220)
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* [[name, value]] of `traceparent` (+ `tracestate`, `baggage` when non-empty) of `span`; in 85
|
|
279
|
+
* bridge mode the headers the app's OpenTelemetry propagator writes (OTX-012, as pmx-io does).
|
|
280
|
+
*/
|
|
281
|
+
function traceHeaders (span) {
|
|
282
|
+
if (!span || !span.context) return []
|
|
283
|
+
try {
|
|
284
|
+
const b = bridgeNow()
|
|
285
|
+
if (b !== null) {
|
|
286
|
+
const pairs = b.headersFor(span)
|
|
287
|
+
if (pairs) return pairs
|
|
288
|
+
}
|
|
289
|
+
return w3c.headersFor(span.context, span.bag)
|
|
290
|
+
} catch (err) {
|
|
291
|
+
debug('trace headers failed: %s', err && err.message)
|
|
292
|
+
return []
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** A new plain object: `base` (never modified) plus the trace headers the app did not set. */
|
|
297
|
+
function headersObject (span, base, asBytes) {
|
|
298
|
+
const out = Object.assign({}, base || {})
|
|
299
|
+
const present = new Set(Object.keys(out).map((k) => k.toLowerCase()))
|
|
300
|
+
for (const [k, v] of traceHeaders(span)) {
|
|
301
|
+
if (!present.has(k)) out[k] = asBytes ? Buffer.from(v, 'utf8') : v
|
|
302
|
+
}
|
|
303
|
+
return out
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** The `__pmx_trace__` / `pmxTrace` carrier value of `span` (ITG-212, ITG-220). */
|
|
307
|
+
function traceCarrier (span) {
|
|
308
|
+
const o = {}
|
|
309
|
+
for (const [k, v] of traceHeaders(span)) o[k] = v
|
|
310
|
+
return o
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* The remote context in a carrier (object, Map, headers object, pairs), or null; in 85 bridge
|
|
315
|
+
* mode the app's OpenTelemetry propagator reads it (OTX-012, as pmx-io does).
|
|
316
|
+
*/
|
|
317
|
+
function extract (carrier) {
|
|
318
|
+
if (!carrier) return null
|
|
319
|
+
try {
|
|
320
|
+
const b = bridgeNow()
|
|
321
|
+
if (b !== null) {
|
|
322
|
+
const sc = b.extract(carrier)
|
|
323
|
+
if (sc !== undefined) return sc
|
|
324
|
+
}
|
|
325
|
+
return w3c.extract(carrier)
|
|
326
|
+
} catch (_) {
|
|
327
|
+
return null
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** ITG-025: bytes of a payload that already is bytes or a string (never serialised to measure). */
|
|
332
|
+
function bodySize (body) {
|
|
333
|
+
if (typeof body === 'string') return Buffer.byteLength(body, 'utf8')
|
|
334
|
+
if (body instanceof Uint8Array || body instanceof ArrayBuffer) return body.byteLength
|
|
335
|
+
return undefined
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ---------------------------------------------------------------------------------------------
|
|
339
|
+
// iterator style (ITG-023)
|
|
340
|
+
|
|
341
|
+
const openIters = new Set()
|
|
342
|
+
let exitHooked = false
|
|
343
|
+
|
|
344
|
+
function hookExit () {
|
|
345
|
+
if (exitHooked) return
|
|
346
|
+
exitHooked = true
|
|
347
|
+
// before the span sink's own exit flush, which was registered when tracing started
|
|
348
|
+
process.prependListener('exit', endOpenIterators)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Per-iterator state: a CONSUMER span starts when a message is handed to the app, is current while
|
|
353
|
+
* the loop body runs (an AsyncLocalStorage store entered synchronously inside the wrapped `next()`
|
|
354
|
+
* and filled in at delivery), and ends at the next request on the same iterator, at close or at
|
|
355
|
+
* exit flush (`pmx.end_inferred` for the last two). When iteration ends, the context of the code
|
|
356
|
+
* before the loop is restored.
|
|
357
|
+
*/
|
|
358
|
+
class IterState {
|
|
359
|
+
constructor () {
|
|
360
|
+
this.span = null
|
|
361
|
+
this.store = null
|
|
362
|
+
this.prev = undefined
|
|
363
|
+
this.started = false
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** The app asks for the next message (synchronously inside next()). */
|
|
367
|
+
nextRequested () {
|
|
368
|
+
this._finish(false)
|
|
369
|
+
if (!ctx.active()) return
|
|
370
|
+
const cur = ctx.current()
|
|
371
|
+
if (!this.started) {
|
|
372
|
+
this.started = true
|
|
373
|
+
this.prev = cur
|
|
374
|
+
}
|
|
375
|
+
const base = this.prev
|
|
376
|
+
const store = { span: base && base.span, bag: base ? base.bag : w3c.EMPTY_BAG, server: base && base.server }
|
|
377
|
+
try {
|
|
378
|
+
ctx.enter(store)
|
|
379
|
+
this.store = store
|
|
380
|
+
} catch (_) {
|
|
381
|
+
this.store = null
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** A message is handed to the app: `span` becomes current for the loop body. */
|
|
386
|
+
delivered (span) {
|
|
387
|
+
if (!span) return
|
|
388
|
+
this.span = span
|
|
389
|
+
if (this.store) {
|
|
390
|
+
this.store.span = span
|
|
391
|
+
this.store.bag = span.bag
|
|
392
|
+
}
|
|
393
|
+
openIters.add(this)
|
|
394
|
+
hookExit()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Iteration finished (done, return(), close): end the span and restore the earlier context. */
|
|
398
|
+
closed (inferred) {
|
|
399
|
+
this._finish(inferred !== false)
|
|
400
|
+
if (this.started) {
|
|
401
|
+
try {
|
|
402
|
+
ctx.enter(this.prev)
|
|
403
|
+
} catch (_) {}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
_finish (inferred) {
|
|
408
|
+
const span = this.span
|
|
409
|
+
this.span = null
|
|
410
|
+
if (this.store) {
|
|
411
|
+
// the loop body's store no longer names the span (late callbacks see the parent)
|
|
412
|
+
this.store.span = this.prev && this.prev.span
|
|
413
|
+
this.store.bag = this.prev ? this.prev.bag : w3c.EMPTY_BAG
|
|
414
|
+
}
|
|
415
|
+
openIters.delete(this)
|
|
416
|
+
if (span) {
|
|
417
|
+
if (inferred) span.setAttribute('pmx.end_inferred', 'true')
|
|
418
|
+
end(span)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
atExit () {
|
|
423
|
+
const span = this.span
|
|
424
|
+
this.span = null
|
|
425
|
+
if (span) {
|
|
426
|
+
span.setAttribute('pmx.end_inferred', 'true')
|
|
427
|
+
end(span)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function endOpenIterators () {
|
|
433
|
+
for (const it of Array.from(openIters)) {
|
|
434
|
+
try {
|
|
435
|
+
it.atExit()
|
|
436
|
+
} catch (_) {}
|
|
437
|
+
}
|
|
438
|
+
openIters.clear()
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const ITER = Symbol('pmx-iter')
|
|
442
|
+
|
|
443
|
+
/** The IterState attached to a library iterator/subscription object (created on demand). */
|
|
444
|
+
function iterState (obj) {
|
|
445
|
+
if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) return null
|
|
446
|
+
let st = obj[ITER]
|
|
447
|
+
if (st instanceof IterState) return st
|
|
448
|
+
st = new IterState()
|
|
449
|
+
try {
|
|
450
|
+
Object.defineProperty(obj, ITER, { value: st, configurable: true, enumerable: false, writable: true })
|
|
451
|
+
} catch (_) {
|
|
452
|
+
return null
|
|
453
|
+
}
|
|
454
|
+
return st
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ---------------------------------------------------------------------------------------------
|
|
458
|
+
// wrapping (ITG-003)
|
|
459
|
+
|
|
460
|
+
function copyMeta (fn, orig) {
|
|
461
|
+
try {
|
|
462
|
+
Object.defineProperty(fn, 'length', { value: orig.length, configurable: true })
|
|
463
|
+
Object.defineProperty(fn, 'name', { value: orig.name, configurable: true })
|
|
464
|
+
} catch (_) {}
|
|
465
|
+
try {
|
|
466
|
+
Object.defineProperty(fn, ORIGINAL, { value: orig })
|
|
467
|
+
Object.defineProperty(fn, WRAPPED, { value: true })
|
|
468
|
+
} catch (_) {}
|
|
469
|
+
// static properties (e.g. util.promisify.custom) stay reachable
|
|
470
|
+
try {
|
|
471
|
+
for (const k of Reflect.ownKeys(orig)) {
|
|
472
|
+
if (k === 'length' || k === 'name' || k === 'prototype' || k === WRAPPED || k === ORIGINAL) continue
|
|
473
|
+
if (!Object.prototype.hasOwnProperty.call(fn, k)) {
|
|
474
|
+
const d = Object.getOwnPropertyDescriptor(orig, k)
|
|
475
|
+
if (d) Object.defineProperty(fn, k, d)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch (_) {}
|
|
479
|
+
if (orig.prototype !== undefined) {
|
|
480
|
+
try {
|
|
481
|
+
fn.prototype = orig.prototype
|
|
482
|
+
} catch (_) {}
|
|
483
|
+
}
|
|
484
|
+
return fn
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Replace obj[name] (a function) by make(original) once. The replacement keeps `name`, `length`
|
|
489
|
+
* and the property's enumerability; pmx marks it with a private symbol (never `__wrapped`, which
|
|
490
|
+
* OpenTelemetry's shimmer uses, 85 OTX-020). Returns true when wrapped (or already wrapped).
|
|
491
|
+
* `opts.lib` names the package (default: the one being patched), `opts.track: false` keeps a
|
|
492
|
+
* wrapper of a per-instance object out of the per-library list.
|
|
493
|
+
*/
|
|
494
|
+
function wrap (obj, name, make, opts) {
|
|
495
|
+
if (!obj) return false
|
|
496
|
+
let desc
|
|
497
|
+
let owner = obj
|
|
498
|
+
while (owner && !(desc = Object.getOwnPropertyDescriptor(owner, name))) owner = Object.getPrototypeOf(owner)
|
|
499
|
+
if (!desc || typeof desc.value !== 'function') return false
|
|
500
|
+
const orig = desc.value
|
|
501
|
+
if (orig[WRAPPED]) return true
|
|
502
|
+
try {
|
|
503
|
+
const made = make(orig)
|
|
504
|
+
// objects wrapped at run time (kafkajs producers) name their library and are not tracked
|
|
505
|
+
const lib = (opts && opts.lib) || currentLib
|
|
506
|
+
// 85 OTX-020: in bridge mode, when an OpenTelemetry instrumentation covers this library (its
|
|
507
|
+
// shimmer wrapper, marked `__wrapped`, sits on this or another function pmx hooks in the
|
|
508
|
+
// library), pass straight through
|
|
509
|
+
const w = copyMeta(function () {
|
|
510
|
+
const b = bridgeNow()
|
|
511
|
+
if (b !== null && !b.disabledMarkerCheck && coveredByOtel(lib, this, name, w)) {
|
|
512
|
+
b.suppress(lib)
|
|
513
|
+
return orig.apply(this, arguments)
|
|
514
|
+
}
|
|
515
|
+
return made.apply(this, arguments)
|
|
516
|
+
}, orig)
|
|
517
|
+
Object.defineProperty(obj, name, { value: w, writable: desc.writable !== false, enumerable: desc.enumerable, configurable: true })
|
|
518
|
+
if (!(opts && opts.track === false)) hook(lib, obj, name, w, orig)
|
|
519
|
+
return true
|
|
520
|
+
} catch (err) {
|
|
521
|
+
debug('cannot wrap %s: %s', name, err && err.message)
|
|
522
|
+
return false
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// 85 OTX-020 per library: the functions pmx hooked (and functions pmx watches without hooking
|
|
527
|
+
// them, e.g. the MongoDB driver's Connection.command), by package name. An OpenTelemetry
|
|
528
|
+
// instrumentation rarely wraps exactly the functions pmx wraps (amqplib: `publish` but not
|
|
529
|
+
// `sendToQueue`; node-redis: commands but not `MULTI`; MongoDB: the connection, not the events
|
|
530
|
+
// pmx observes), so one OTel-wrapped function suppresses all of the library's pmx spans.
|
|
531
|
+
const hooks = new Map()
|
|
532
|
+
|
|
533
|
+
function hook (lib, obj, name, w, orig) {
|
|
534
|
+
let list = hooks.get(lib)
|
|
535
|
+
if (!list) hooks.set(lib, (list = []))
|
|
536
|
+
list.push({ obj, name, w, orig, get: null })
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Watch a function pmx does not wrap: `get()` returns [obj, name] (resolved lazily), or null. */
|
|
540
|
+
function watch (lib, get) {
|
|
541
|
+
let list = hooks.get(lib)
|
|
542
|
+
if (!list) hooks.set(lib, (list = []))
|
|
543
|
+
list.push({ obj: null, name: null, w: null, orig: null, get })
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function otelWrapped (fn, own) {
|
|
547
|
+
return Boolean(fn && fn !== own && fn.__wrapped === true)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* True when an OpenTelemetry instrumentation wrapped a function of `lib` that pmx hooks or
|
|
552
|
+
* watches: the method as the receiver sees it (OTel may wrap a subclass's prototype), the
|
|
553
|
+
* property pmx replaced (OTel wrapped pmx's wrapper), or the function pmx wrapped (OTel came first).
|
|
554
|
+
*/
|
|
555
|
+
function coveredByOtel (lib, self, name, w) {
|
|
556
|
+
if (self !== null && self !== undefined && name !== undefined) {
|
|
557
|
+
try {
|
|
558
|
+
if (otelWrapped(self[name], w)) return true
|
|
559
|
+
} catch (_) {}
|
|
560
|
+
}
|
|
561
|
+
const list = hooks.get(lib)
|
|
562
|
+
if (!list) return false
|
|
563
|
+
for (const h of list) {
|
|
564
|
+
try {
|
|
565
|
+
if (h.get) {
|
|
566
|
+
// a watched function is resolved once
|
|
567
|
+
const get = h.get
|
|
568
|
+
h.get = null
|
|
569
|
+
const r = get()
|
|
570
|
+
if (r) {
|
|
571
|
+
h.obj = r[0]
|
|
572
|
+
h.name = r[1]
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (!h.obj) continue
|
|
576
|
+
if (otelWrapped(h.obj[h.name], h.w) || otelWrapped(h.orig, null)) return true
|
|
577
|
+
} catch (_) {}
|
|
578
|
+
}
|
|
579
|
+
return false
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** In bridge mode: true (and `lib` reported in `suppressed`) when OpenTelemetry covers `lib`. */
|
|
583
|
+
function suppressedLib (lib) {
|
|
584
|
+
const b = bridgeNow()
|
|
585
|
+
if (b === null || b.disabledMarkerCheck || !coveredByOtel(lib)) return false
|
|
586
|
+
b.suppress(lib)
|
|
587
|
+
return true
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** The 85 bridge while tracing runs in bridge mode, else null. */
|
|
591
|
+
function bridgeNow () {
|
|
592
|
+
const r = globalThis[MODULES]
|
|
593
|
+
const t = r && r.tracing && r.tracing._tracer
|
|
594
|
+
return t && t.bridge ? t.bridge : null
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// the package being patched (set by lib/itg/index.js), reported in `suppressed` (OTX-004)
|
|
598
|
+
let currentLib = 'unknown'
|
|
599
|
+
function setLib (name) {
|
|
600
|
+
currentLib = name || 'unknown'
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function isWrapped (fn) {
|
|
604
|
+
return Boolean(fn && fn[WRAPPED])
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** The library version from its package.json next to `dir`, or '?'. */
|
|
608
|
+
function versionOf (dir) {
|
|
609
|
+
try {
|
|
610
|
+
return require(require('path').join(dir, 'package.json')).version || '?'
|
|
611
|
+
} catch (_) {
|
|
612
|
+
return '?'
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
module.exports = {
|
|
617
|
+
tracer,
|
|
618
|
+
dbTracer,
|
|
619
|
+
msgTracer,
|
|
620
|
+
currentSpan,
|
|
621
|
+
nested,
|
|
622
|
+
outer,
|
|
623
|
+
disabled,
|
|
624
|
+
clientSpan,
|
|
625
|
+
producer,
|
|
626
|
+
consumer,
|
|
627
|
+
batchConsumer,
|
|
628
|
+
addLink,
|
|
629
|
+
errorText,
|
|
630
|
+
fail,
|
|
631
|
+
end,
|
|
632
|
+
isThenable,
|
|
633
|
+
settle,
|
|
634
|
+
withSpan,
|
|
635
|
+
runConsumer,
|
|
636
|
+
bind,
|
|
637
|
+
traceHeaders,
|
|
638
|
+
headersObject,
|
|
639
|
+
traceCarrier,
|
|
640
|
+
extract,
|
|
641
|
+
bodySize,
|
|
642
|
+
IterState,
|
|
643
|
+
iterState,
|
|
644
|
+
endOpenIterators,
|
|
645
|
+
wrap,
|
|
646
|
+
watch,
|
|
647
|
+
suppressedLib,
|
|
648
|
+
coveredByOtel,
|
|
649
|
+
setLib,
|
|
650
|
+
bridgeNow,
|
|
651
|
+
copyMeta,
|
|
652
|
+
isWrapped,
|
|
653
|
+
versionOf,
|
|
654
|
+
TRACE_KEY,
|
|
655
|
+
WRAPPED,
|
|
656
|
+
ORIGINAL
|
|
657
|
+
}
|