@aidra/pmx-io 0.0.0-stage → 9.13.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,850 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// The native tracer (80 B.8, NIO-200..NIO-211; parity with pmx-io's C.10 / C.12, Part G):
|
|
5
|
+
// spans of kind INTERNAL/SERVER/CLIENT/PRODUCER/CONSUMER, parent-based trace-id-ratio sampling,
|
|
6
|
+
// the duration floor with its exemptions, the export rate limit, links and the Zipkin v2 export
|
|
7
|
+
// shape of 40 §7.11. No OpenTelemetry package is ever loaded.
|
|
8
|
+
|
|
9
|
+
const path = require('path')
|
|
10
|
+
const ctx = require('./context')
|
|
11
|
+
const w3c = require('./w3c')
|
|
12
|
+
const { normalizePath, rawPath } = require('./paths')
|
|
13
|
+
const { VERSION } = require('../constants')
|
|
14
|
+
const debug = require('../debug')('axm:tracing')
|
|
15
|
+
|
|
16
|
+
const KINDS = ['INTERNAL', 'SERVER', 'CLIENT', 'PRODUCER', 'CONSUMER']
|
|
17
|
+
const DEFAULT_IGNORE_INCOMING = [/(.*).js/, /(.*).css/, /(.*).png/, /(.*).ico/, /(.*).svg/, /webpack/]
|
|
18
|
+
const DEFAULT_SAMPLE_RATIO = 1
|
|
19
|
+
const DEFAULT_MAX_SPANS_PER_SECOND = 2000
|
|
20
|
+
const DEFAULT_MIN_DURATION_US = 1000
|
|
21
|
+
const MAX_LINKS = 128
|
|
22
|
+
const MAX_LINKS_BYTES = 16384
|
|
23
|
+
const TWO_63 = 2 ** 63
|
|
24
|
+
const OTEL_API = Symbol.for('opentelemetry.js.api.1')
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------------------------
|
|
27
|
+
// ids and clocks
|
|
28
|
+
|
|
29
|
+
// /dev/urandom where it exists: loading crypto costs ~5 ms of start-up (NIO-024)
|
|
30
|
+
function fillRandom (buf) {
|
|
31
|
+
const fs = require('fs')
|
|
32
|
+
try {
|
|
33
|
+
const fd = fs.openSync('/dev/urandom', 'r')
|
|
34
|
+
try {
|
|
35
|
+
if (fs.readSync(fd, buf, 0, buf.length, null) === buf.length) return
|
|
36
|
+
} finally {
|
|
37
|
+
fs.closeSync(fd)
|
|
38
|
+
}
|
|
39
|
+
} catch (_) {}
|
|
40
|
+
require('crypto').randomFillSync(buf)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const POOL = Buffer.allocUnsafe(4096)
|
|
44
|
+
let poolOff = POOL.length
|
|
45
|
+
|
|
46
|
+
function randomHex (bytes) {
|
|
47
|
+
for (;;) {
|
|
48
|
+
if (poolOff + bytes > POOL.length) {
|
|
49
|
+
fillRandom(POOL)
|
|
50
|
+
poolOff = 0
|
|
51
|
+
}
|
|
52
|
+
const s = POOL.toString('hex', poolOff, poolOff + bytes)
|
|
53
|
+
poolOff += bytes
|
|
54
|
+
if (!/^0+$/.test(s)) return s
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// resolved on first use: reading globalThis.performance loads the perf_hooks internals (NIO-024)
|
|
59
|
+
let perfObj = null
|
|
60
|
+
let ORIGIN = 0
|
|
61
|
+
function perfNow () {
|
|
62
|
+
if (perfObj === null) {
|
|
63
|
+
perfObj = globalThis.performance || require('perf_hooks').performance
|
|
64
|
+
ORIGIN = perfObj.timeOrigin
|
|
65
|
+
}
|
|
66
|
+
return perfObj.now()
|
|
67
|
+
}
|
|
68
|
+
const perf = { now: perfNow }
|
|
69
|
+
|
|
70
|
+
function nowUs () {
|
|
71
|
+
const t = perfNow()
|
|
72
|
+
return Math.round((ORIGIN + t) * 1000)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function toUs (t) {
|
|
76
|
+
if (t instanceof Date) return t.getTime() * 1000
|
|
77
|
+
if (typeof t === 'number' && Number.isFinite(t)) return Math.round(t * 1000)
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------------------------
|
|
82
|
+
// kinds
|
|
83
|
+
|
|
84
|
+
function normKind (kind) {
|
|
85
|
+
if (kind === undefined || kind === null) return 'INTERNAL'
|
|
86
|
+
if (typeof kind === 'number' && Number.isInteger(kind) && kind >= 0 && kind <= 4) return KINDS[kind]
|
|
87
|
+
if (typeof kind === 'string') {
|
|
88
|
+
const k = kind.toUpperCase()
|
|
89
|
+
if (KINDS.includes(k)) return k
|
|
90
|
+
}
|
|
91
|
+
throw new TypeError('invalid span kind: ' + String(kind))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------------------------
|
|
95
|
+
// tags
|
|
96
|
+
|
|
97
|
+
function tagValue (v) {
|
|
98
|
+
if (typeof v === 'string') return v
|
|
99
|
+
if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') return String(v)
|
|
100
|
+
try {
|
|
101
|
+
const s = JSON.stringify(v)
|
|
102
|
+
return s === undefined ? String(v) : s
|
|
103
|
+
} catch (_) {
|
|
104
|
+
return String(v)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** JSON with non-ASCII escaped as \uXXXX (Python's json.dumps default), compact. */
|
|
109
|
+
function asciiJson (v) {
|
|
110
|
+
return JSON.stringify(v).replace(/[\u007f-]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 40 §7.11.1: the `pmx.links` tag value and the number of links it had to leave out. */
|
|
114
|
+
function encodeLinks (links, alreadyDropped) {
|
|
115
|
+
let dropped = alreadyDropped || 0
|
|
116
|
+
if (!links || links.length === 0) return { value: undefined, dropped }
|
|
117
|
+
const items = links.map((l) => {
|
|
118
|
+
const o = { traceId: l.context.traceId, spanId: l.context.spanId }
|
|
119
|
+
if (l.attributes) {
|
|
120
|
+
const a = {}
|
|
121
|
+
let any = false
|
|
122
|
+
for (const k of Object.keys(l.attributes)) {
|
|
123
|
+
const v = l.attributes[k]
|
|
124
|
+
if (v === undefined || v === null) continue
|
|
125
|
+
a[k] = tagValue(v)
|
|
126
|
+
any = true
|
|
127
|
+
}
|
|
128
|
+
if (any) o.attributes = a
|
|
129
|
+
}
|
|
130
|
+
return o
|
|
131
|
+
})
|
|
132
|
+
let value = asciiJson(items)
|
|
133
|
+
while (items.length > 0 && Buffer.byteLength(value, 'utf8') > MAX_LINKS_BYTES) {
|
|
134
|
+
items.pop()
|
|
135
|
+
dropped++
|
|
136
|
+
value = asciiJson(items)
|
|
137
|
+
}
|
|
138
|
+
return { value: items.length ? value : undefined, dropped }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function linkOf (el) {
|
|
142
|
+
if (w3c.isSpanContext(el)) return { context: el, attributes: undefined }
|
|
143
|
+
if (el && typeof el === 'object' && w3c.isSpanContext(el.context)) {
|
|
144
|
+
const attrs = el.attributes && typeof el.attributes === 'object' ? el.attributes : undefined
|
|
145
|
+
return { context: el.context, attributes: attrs }
|
|
146
|
+
}
|
|
147
|
+
if (el instanceof Span) return { context: el.context, attributes: undefined }
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------------------------
|
|
152
|
+
// spans
|
|
153
|
+
|
|
154
|
+
class Span {
|
|
155
|
+
constructor (tracer, name, kind, traceId, parentId, sampled, traceState, attributes, bag, remoteParent, localParent, startUs) {
|
|
156
|
+
this._tracer = tracer
|
|
157
|
+
this.name = String(name)
|
|
158
|
+
this.kind = kind
|
|
159
|
+
this.traceId = traceId
|
|
160
|
+
this.spanId = randomHex(8)
|
|
161
|
+
this.parentId = parentId || null
|
|
162
|
+
this.sampled = sampled
|
|
163
|
+
this.traceState = traceState || null
|
|
164
|
+
this.bag = bag || w3c.EMPTY_BAG
|
|
165
|
+
this.remoteParent = remoteParent === true
|
|
166
|
+
this.localParent = localParent || null
|
|
167
|
+
this.childExported = false
|
|
168
|
+
this.attributes = {}
|
|
169
|
+
if (attributes && typeof attributes === 'object') Object.assign(this.attributes, attributes)
|
|
170
|
+
this.events = null
|
|
171
|
+
this.links = null
|
|
172
|
+
this.droppedLinks = 0
|
|
173
|
+
this.maxLinks = MAX_LINKS
|
|
174
|
+
this.status = null
|
|
175
|
+
this.error = null
|
|
176
|
+
this.ended = false
|
|
177
|
+
this._ctx = null
|
|
178
|
+
if (startUs !== undefined) {
|
|
179
|
+
this.startUs = startUs
|
|
180
|
+
this._t0 = null
|
|
181
|
+
} else {
|
|
182
|
+
this.startUs = nowUs()
|
|
183
|
+
this._t0 = perf.now()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
get context () {
|
|
188
|
+
if (!this._ctx) this._ctx = w3c.makeContext(this.traceId, this.spanId, this.sampled, this.traceState, this.bag, false)
|
|
189
|
+
return this._ctx
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** OpenTelemetry API compatibility. */
|
|
193
|
+
spanContext () {
|
|
194
|
+
return { traceId: this.traceId, spanId: this.spanId, traceFlags: this.sampled ? 1 : 0, isRemote: false }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
isRecording () {
|
|
198
|
+
return !this.ended
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
setAttribute (key, value) {
|
|
202
|
+
if (!this.ended && typeof key === 'string') this.attributes[key] = value
|
|
203
|
+
return this
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
setAttributes (obj) {
|
|
207
|
+
if (!this.ended && obj && typeof obj === 'object') {
|
|
208
|
+
for (const k of Object.keys(obj)) this.attributes[k] = obj[k]
|
|
209
|
+
}
|
|
210
|
+
return this
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
updateName (name) {
|
|
214
|
+
if (!this.ended) this.name = String(name)
|
|
215
|
+
return this
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
addEvent (name, attrsOrTime, time) {
|
|
219
|
+
if (this.ended) return this
|
|
220
|
+
let t = toUs(typeof attrsOrTime === 'number' || attrsOrTime instanceof Date ? attrsOrTime : time)
|
|
221
|
+
if (t === undefined) t = nowUs()
|
|
222
|
+
const ev = { timestamp: t, value: String(name) }
|
|
223
|
+
if (this.events) this.events.push(ev)
|
|
224
|
+
else this.events = [ev]
|
|
225
|
+
return this
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
addLink (spanContext, attributes) {
|
|
229
|
+
if (this.ended) return this
|
|
230
|
+
const l = linkOf(attributes === undefined ? spanContext : { context: spanContext, attributes })
|
|
231
|
+
if (!l) return this
|
|
232
|
+
if (!this.links) this.links = []
|
|
233
|
+
if (this.links.length >= this.maxLinks) this.droppedLinks++
|
|
234
|
+
else this.links.push(l)
|
|
235
|
+
return this
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** OpenTelemetry API compatibility. */
|
|
239
|
+
addLinks (links) {
|
|
240
|
+
if (Array.isArray(links)) for (const l of links) this.addLink(l)
|
|
241
|
+
return this
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
recordException (err) {
|
|
245
|
+
if (this.ended) return this
|
|
246
|
+
this.status = 'ERROR'
|
|
247
|
+
let msg
|
|
248
|
+
if (err && typeof err === 'object' && typeof err.message === 'string') msg = err.message || err.name || 'Error'
|
|
249
|
+
else msg = String(err)
|
|
250
|
+
this.error = msg
|
|
251
|
+
return this
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
setStatus (status, message) {
|
|
255
|
+
if (this.ended) return this
|
|
256
|
+
let code = status
|
|
257
|
+
let msg = message
|
|
258
|
+
if (status && typeof status === 'object') {
|
|
259
|
+
code = status.code
|
|
260
|
+
if (msg === undefined) msg = status.message
|
|
261
|
+
}
|
|
262
|
+
if (code === 0 || (typeof code === 'string' && code.toUpperCase() === 'UNSET')) {
|
|
263
|
+
this.status = null
|
|
264
|
+
return this
|
|
265
|
+
}
|
|
266
|
+
if (code === 1 || (typeof code === 'string' && code.toUpperCase() === 'OK')) this.status = 'OK'
|
|
267
|
+
else if (code === 2 || (typeof code === 'string' && code.toUpperCase() === 'ERROR')) this.status = 'ERROR'
|
|
268
|
+
else return this
|
|
269
|
+
if (typeof msg === 'string' && msg !== '') this.error = msg
|
|
270
|
+
return this
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
traceparent () {
|
|
274
|
+
return `00-${this.traceId}-${this.spanId}-${this.sampled ? '01' : '00'}`
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
end (endTime) {
|
|
278
|
+
if (this.ended) return
|
|
279
|
+
this.ended = true
|
|
280
|
+
let duration
|
|
281
|
+
const endUs = toUs(endTime)
|
|
282
|
+
if (endUs !== undefined) duration = Math.max(0, endUs - this.startUs)
|
|
283
|
+
else if (this._t0 !== null) duration = Math.max(0, Math.round((perf.now() - this._t0) * 1000))
|
|
284
|
+
else duration = Math.max(0, nowUs() - this.startUs)
|
|
285
|
+
try {
|
|
286
|
+
if (!this._noExport) this._tracer._export(this, duration)
|
|
287
|
+
} catch (err) {
|
|
288
|
+
debug('span export failed: %s', err && err.message)
|
|
289
|
+
}
|
|
290
|
+
this._tracer._spanEnded(this)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------------------------
|
|
295
|
+
// no-op tracer (tracing disabled, NIO-207)
|
|
296
|
+
|
|
297
|
+
const noopSpan = Object.freeze({
|
|
298
|
+
context: null,
|
|
299
|
+
name: '',
|
|
300
|
+
ended: true,
|
|
301
|
+
sampled: false,
|
|
302
|
+
spanContext: () => ({ traceId: '00000000000000000000000000000000', spanId: '0000000000000000', traceFlags: 0, isRemote: false }),
|
|
303
|
+
isRecording: () => false,
|
|
304
|
+
setAttribute () { return this },
|
|
305
|
+
setAttributes () { return this },
|
|
306
|
+
updateName () { return this },
|
|
307
|
+
addEvent () { return this },
|
|
308
|
+
addLink () { return this },
|
|
309
|
+
addLinks () { return this },
|
|
310
|
+
recordException () { return this },
|
|
311
|
+
setStatus () { return this },
|
|
312
|
+
traceparent: () => null,
|
|
313
|
+
end () {}
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
function splitActiveArgs (args) {
|
|
317
|
+
const fn = args[args.length - 1]
|
|
318
|
+
if (typeof fn !== 'function') throw new TypeError('startActiveSpan needs a function')
|
|
319
|
+
const options = args.length >= 2 && args[0] && typeof args[0] === 'object' ? args[0] : undefined
|
|
320
|
+
return { options, fn }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const noopTracer = Object.freeze({
|
|
324
|
+
enabled: false,
|
|
325
|
+
startSpan (name, options) {
|
|
326
|
+
normKind(options && options.kind)
|
|
327
|
+
return noopSpan
|
|
328
|
+
},
|
|
329
|
+
startActiveSpan (name, ...args) {
|
|
330
|
+
const { options, fn } = splitActiveArgs(args)
|
|
331
|
+
normKind(options && options.kind)
|
|
332
|
+
return fn(noopSpan)
|
|
333
|
+
},
|
|
334
|
+
withSpan (span, fn) {
|
|
335
|
+
return fn(span)
|
|
336
|
+
},
|
|
337
|
+
currentSpan () {
|
|
338
|
+
return undefined
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// ---------------------------------------------------------------------------------------------
|
|
343
|
+
// tracer
|
|
344
|
+
|
|
345
|
+
function num (v, what) {
|
|
346
|
+
if (v === undefined || v === null || typeof v === 'boolean') {
|
|
347
|
+
if (typeof v === 'boolean') debug('ignoring non-numeric %s: %s', what, v)
|
|
348
|
+
return undefined
|
|
349
|
+
}
|
|
350
|
+
const n = typeof v === 'number' ? v : Number(v)
|
|
351
|
+
if (typeof v === 'string' && v.trim() === '') return undefined
|
|
352
|
+
if (!Number.isFinite(n)) {
|
|
353
|
+
debug('ignoring non-numeric %s: %s', what, v)
|
|
354
|
+
return undefined
|
|
355
|
+
}
|
|
356
|
+
return n
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function testEnv () {
|
|
360
|
+
return process.env.NODE_ENV === 'test' || process.env.PMX_IO_ENV === 'test'
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function matches (rules, url, request) {
|
|
364
|
+
for (const r of rules) {
|
|
365
|
+
try {
|
|
366
|
+
if (typeof r === 'string' && url.includes(r)) return true
|
|
367
|
+
if (r instanceof RegExp && r.test(url)) return true
|
|
368
|
+
if (typeof r === 'function' && r(url, request)) return true
|
|
369
|
+
} catch (_) {}
|
|
370
|
+
}
|
|
371
|
+
return false
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** True when trace id `traceId` is sampled at `ratio` (PYI-096; same answer as pmx-io). */
|
|
375
|
+
function sampledFor (traceId, ratio) {
|
|
376
|
+
if (ratio >= 1) return true
|
|
377
|
+
if (ratio <= 0) return false
|
|
378
|
+
return (BigInt('0x' + traceId.slice(16)) >> 1n) < ratio * TWO_63
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* `tracing.db` (NIO-200, 82 ITG-001/ITG-014/ITG-015/ITG-016): null when off, else
|
|
383
|
+
* { statement, poolSpans, comment ('on' | 'off' | 'force', 96 SVM-500), applicationName }.
|
|
384
|
+
*/
|
|
385
|
+
function dbOptions (v) {
|
|
386
|
+
if (v === false || (v && typeof v === 'object' && v.enabled === false)) return null
|
|
387
|
+
const o = v && typeof v === 'object' ? v : {}
|
|
388
|
+
const st = typeof o.statement === 'string' ? o.statement.toLowerCase() : 'sanitized'
|
|
389
|
+
return {
|
|
390
|
+
statement: st === 'raw' || st === 'off' ? st : 'sanitized',
|
|
391
|
+
poolSpans: o.poolSpans !== false,
|
|
392
|
+
comment: require('../itg/dbcomment').normMode(o.comment),
|
|
393
|
+
applicationName: o.applicationName !== false && o.application_name !== false
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** `tracing.messaging` (82 ITG-001): null when off, else the resolved object. */
|
|
398
|
+
function messagingOptions (v) {
|
|
399
|
+
if (v === false || (v && typeof v === 'object' && v.enabled === false)) return null
|
|
400
|
+
const o = v && typeof v === 'object' ? v : {}
|
|
401
|
+
let maxLinks = Number(o.maxLinks)
|
|
402
|
+
if (!Number.isFinite(maxLinks) || o.maxLinks === undefined || o.maxLinks === null) maxLinks = MAX_LINKS
|
|
403
|
+
maxLinks = Math.max(0, Math.min(1000, Math.floor(maxLinks)))
|
|
404
|
+
return {
|
|
405
|
+
rootProducers: o.rootProducers === true,
|
|
406
|
+
redisStreamFields: o.redisStreamFields === true,
|
|
407
|
+
payloadCarrier: o.payloadCarrier === true,
|
|
408
|
+
maxLinks
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function serviceName (opts, config) {
|
|
413
|
+
if (typeof opts.serviceName === 'string' && opts.serviceName) return opts.serviceName
|
|
414
|
+
if (process.env.OTEL_SERVICE_NAME) return process.env.OTEL_SERVICE_NAME
|
|
415
|
+
const apm = config && config.apmOptions
|
|
416
|
+
if (apm && typeof apm.appName === 'string' && apm.appName) return apm.appName
|
|
417
|
+
if (process.env.name) return process.env.name
|
|
418
|
+
const script = process.argv[1]
|
|
419
|
+
if (script) return path.basename(script).replace(/\.[^.]*$/, '') || 'node'
|
|
420
|
+
return 'node'
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
class Tracer {
|
|
424
|
+
/**
|
|
425
|
+
* @param {object} opts resolved `tracing` options (NIO-200)
|
|
426
|
+
* @param {{add: Function}} sink receives finished Zipkin spans
|
|
427
|
+
* @param {object} [extra] { config, onDrop(n) }
|
|
428
|
+
*/
|
|
429
|
+
constructor (opts, sink, extra) {
|
|
430
|
+
this._open = 0
|
|
431
|
+
this.stopping = false
|
|
432
|
+
extra = extra || {}
|
|
433
|
+
this.enabled = true
|
|
434
|
+
this.opts = opts
|
|
435
|
+
this._sink = sink
|
|
436
|
+
this._onDrop = extra.onDrop || null
|
|
437
|
+
// called before each span is made (outbound wrapping on Node < 22.12), or null
|
|
438
|
+
this._onStart = typeof extra.onStart === 'function' ? extra.onStart : null
|
|
439
|
+
// 85 bridge mode: set once an app OpenTelemetry SDK is detected (one-way, OTX-003)
|
|
440
|
+
this.bridge = null
|
|
441
|
+
this._bridgeEnv = extra.bridge || null
|
|
442
|
+
this.serviceName = serviceName(opts, extra.config)
|
|
443
|
+
this.ignoreIncoming = Array.isArray(opts.ignoreIncomingPaths) ? opts.ignoreIncomingPaths.slice() : DEFAULT_IGNORE_INCOMING
|
|
444
|
+
this.ignoreOutgoing = Array.isArray(opts.ignoreOutgoingUrls) ? opts.ignoreOutgoingUrls.slice() : []
|
|
445
|
+
const ratio = num(opts.sampleRatio, 'sampleRatio')
|
|
446
|
+
this.sampleRatio = ratio === undefined ? DEFAULT_SAMPLE_RATIO : Math.min(1, Math.max(0, ratio))
|
|
447
|
+
this.exportUnsampledErrors = opts.exportUnsampledErrors !== false
|
|
448
|
+
const rate = num(opts.maxSpansPerSecond, 'maxSpansPerSecond')
|
|
449
|
+
this.maxSpansPerSecond = rate === undefined ? DEFAULT_MAX_SPANS_PER_SECOND : Math.max(0, rate)
|
|
450
|
+
const floor = num(opts.minDurationUs, 'minDurationUs')
|
|
451
|
+
this.minDurationUs = floor === undefined ? (testEnv() ? 0 : DEFAULT_MIN_DURATION_US) : Math.max(0, floor)
|
|
452
|
+
this.outbound = opts.outbound !== false
|
|
453
|
+
this.normalizePaths = opts.normalizePaths !== false
|
|
454
|
+
this.baggageTags = Array.isArray(opts.baggageTags) ? opts.baggageTags.map(String) : []
|
|
455
|
+
this.db = dbOptions(opts.db)
|
|
456
|
+
this.messaging = messagingOptions(opts.messaging)
|
|
457
|
+
const lc = opts.logCorrelation
|
|
458
|
+
this.logCorrelation = lc !== false && !(lc && typeof lc === 'object' && lc.enabled === false)
|
|
459
|
+
this.consoleCorrelation = this.logCorrelation && Boolean(lc && typeof lc === 'object' && lc.console === true)
|
|
460
|
+
this._tokens = this.maxSpansPerSecond
|
|
461
|
+
this._refill = null // first export (keeps perf_hooks off the start-up path)
|
|
462
|
+
this.dropped = 0
|
|
463
|
+
this._dropLogged = 0
|
|
464
|
+
this._nextTraceId = null
|
|
465
|
+
const bun = typeof process.versions.bun === 'string'
|
|
466
|
+
this.resource = {
|
|
467
|
+
'service.name': this.serviceName,
|
|
468
|
+
'process.pid': String(process.pid),
|
|
469
|
+
'process.runtime.name': bun ? 'bun' : 'nodejs',
|
|
470
|
+
'process.runtime.version': bun ? process.versions.bun : process.versions.node,
|
|
471
|
+
'telemetry.sdk.name': '@pmx/io',
|
|
472
|
+
'telemetry.sdk.language': 'nodejs',
|
|
473
|
+
'telemetry.sdk.version': VERSION,
|
|
474
|
+
'telemetry.distro.name': 'pmx'
|
|
475
|
+
}
|
|
476
|
+
// pmx 97 PAP-051: `pmx.application` always; `service.namespace` = the application when it
|
|
477
|
+
// isn't `default`, else a non-default pm2 namespace
|
|
478
|
+
const appl = require('../application')
|
|
479
|
+
const application = appl.current()
|
|
480
|
+
this.resource['pmx.application'] = application
|
|
481
|
+
const ns = appl.serviceNamespace(application, process.env.namespace)
|
|
482
|
+
if (ns) this.resource['service.namespace'] = ns
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// -- construction ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
_newTrace () {
|
|
488
|
+
let traceId = this._nextTraceId
|
|
489
|
+
this._nextTraceId = null
|
|
490
|
+
if (!traceId) traceId = randomHex(16)
|
|
491
|
+
return { traceId, sampled: sampledFor(traceId, this.sampleRatio) }
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
_make (name, kind, traceId, parentId, sampled, traceState, attributes, bag, remote, local, startUs) {
|
|
495
|
+
// NIO-298: while tracing is being switched off, spans still work for their callers but are
|
|
496
|
+
// unsampled and never exported (no DB tags either); spans started before keep their export
|
|
497
|
+
if (this.stopping) return this._makeDrained(name, kind, traceId, parentId, traceState, attributes, bag, remote, local, startUs)
|
|
498
|
+
this._open++
|
|
499
|
+
const span = this._makeSpan(name, kind, traceId, parentId, sampled, traceState, attributes, bag, remote, local, startUs)
|
|
500
|
+
span._counted = true
|
|
501
|
+
return span
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
_makeDrained (name, kind, traceId, parentId, traceState, attributes, bag, remote, local, startUs) {
|
|
505
|
+
const span = this._makeSpan(name, kind, traceId, parentId, false, traceState, attributes, bag, remote, local, startUs)
|
|
506
|
+
span._noExport = true
|
|
507
|
+
return span
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** a span ended: one fewer in flight (drain of NIO-298) */
|
|
511
|
+
_spanEnded (span) {
|
|
512
|
+
if (span._counted) {
|
|
513
|
+
span._counted = false
|
|
514
|
+
this._open--
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
_makeSpan (name, kind, traceId, parentId, sampled, traceState, attributes, bag, remote, local, startUs) {
|
|
519
|
+
if (this._onStart !== null) {
|
|
520
|
+
try {
|
|
521
|
+
this._onStart()
|
|
522
|
+
} catch (_) {}
|
|
523
|
+
}
|
|
524
|
+
const span = new Span(this, name, kind, traceId, parentId, sampled, traceState, attributes, bag, remote, local, startUs)
|
|
525
|
+
if (this.baggageTags.length && span.bag.length) {
|
|
526
|
+
for (const key of this.baggageTags) {
|
|
527
|
+
const e = span.bag.find((x) => x[0] === key)
|
|
528
|
+
if (e) span.attributes['baggage.' + key] = e[1]
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return span
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** A span of this process under the local span `parent` (baggage: the current one). */
|
|
535
|
+
childOf (parent, name, kind, attributes) {
|
|
536
|
+
if (this.bridge) return this.bridge.child(parent, name, kind, attributes)
|
|
537
|
+
const store = ctx.current()
|
|
538
|
+
const bag = store && store.bag ? store.bag : parent.bag
|
|
539
|
+
return this._make(name, kind, parent.traceId, parent.spanId, parent.sampled, parent.traceState, attributes, bag, false, parent)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// -- public API (NIO-207) -------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* OTX-002/003: switch to bridge mode when the app registered an OpenTelemetry SDK. Called at
|
|
546
|
+
* activation and before entry spans; a property lookup when there is no OTel API global.
|
|
547
|
+
*/
|
|
548
|
+
checkBridge () {
|
|
549
|
+
if (this.bridge) return this.bridge
|
|
550
|
+
if (globalThis[OTEL_API] === undefined || !this._bridgeEnv) return null
|
|
551
|
+
let b = null
|
|
552
|
+
try {
|
|
553
|
+
const mod = require('../otel/bridge')
|
|
554
|
+
const provider = mod.detect()
|
|
555
|
+
if (!provider) return null
|
|
556
|
+
b = new mod.Bridge(this, provider, this._bridgeEnv)
|
|
557
|
+
this.bridge = b
|
|
558
|
+
ctx.setBridge(b)
|
|
559
|
+
b.start()
|
|
560
|
+
} catch (err) {
|
|
561
|
+
debug('OpenTelemetry bridge failed: %s', err && err.message)
|
|
562
|
+
}
|
|
563
|
+
return this.bridge
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
startSpan (name, options) {
|
|
567
|
+
const o = options && typeof options === 'object' ? options : {}
|
|
568
|
+
const kind = normKind(o.kind)
|
|
569
|
+
const b = this.bridge || this.checkBridge()
|
|
570
|
+
if (b) {
|
|
571
|
+
const parent = o.parent === noopSpan || o.parent === null ? undefined : o.parent
|
|
572
|
+
if (parent !== undefined && parent !== w3c.NEW_TRACE && !(parent instanceof Span) && !w3c.isSpanContext(parent) && !(parent && parent.foreign)) {
|
|
573
|
+
throw new TypeError('parent must be undefined, a Span, a SpanContext or NEW_TRACE')
|
|
574
|
+
}
|
|
575
|
+
return b.startSpan(name, kind, { parent, links: o.links, attributes: o.attributes, startUs: toUs(o.startTime) }, ctx.current())
|
|
576
|
+
}
|
|
577
|
+
const startUs = toUs(o.startTime)
|
|
578
|
+
let parent = o.parent
|
|
579
|
+
if (parent === noopSpan || parent === null) parent = undefined
|
|
580
|
+
let span
|
|
581
|
+
if (parent === undefined) {
|
|
582
|
+
const store = ctx.current()
|
|
583
|
+
const cur = store && store.span
|
|
584
|
+
if (cur && cur !== noopSpan) {
|
|
585
|
+
const bag = store.bag || cur.bag
|
|
586
|
+
span = this._make(name, kind, cur.traceId, cur.spanId, cur.sampled, cur.traceState, o.attributes, bag, false, cur, startUs)
|
|
587
|
+
} else {
|
|
588
|
+
const t = this._newTrace()
|
|
589
|
+
span = this._make(name, kind, t.traceId, null, t.sampled, null, o.attributes, store && store.bag, false, null, startUs)
|
|
590
|
+
}
|
|
591
|
+
} else if (parent === w3c.NEW_TRACE) {
|
|
592
|
+
const store = ctx.current()
|
|
593
|
+
const t = this._newTrace()
|
|
594
|
+
span = this._make(name, kind, t.traceId, null, t.sampled, null, o.attributes, store && store.bag, false, null, startUs)
|
|
595
|
+
} else if (parent instanceof Span) {
|
|
596
|
+
span = this._make(name, kind, parent.traceId, parent.spanId, parent.sampled, parent.traceState, o.attributes, parent.bag, false, parent, startUs)
|
|
597
|
+
} else if (w3c.isSpanContext(parent)) {
|
|
598
|
+
span = this._make(name, kind, parent.traceId, parent.spanId, parent.sampled, parent.traceState, o.attributes, w3c.contextBag(parent), parent.isRemote, null, startUs)
|
|
599
|
+
} else {
|
|
600
|
+
throw new TypeError('parent must be undefined, a Span, a SpanContext or NEW_TRACE')
|
|
601
|
+
}
|
|
602
|
+
if (Array.isArray(o.links)) for (const l of o.links) span.addLink(l)
|
|
603
|
+
return span
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
startActiveSpan (name, ...args) {
|
|
607
|
+
const { options, fn } = splitActiveArgs(args)
|
|
608
|
+
const span = this.startSpan(name, options)
|
|
609
|
+
return runActive(span, fn)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
withSpan (span, fn) {
|
|
613
|
+
if (!(span instanceof Span)) return fn(span)
|
|
614
|
+
const store = ctx.current()
|
|
615
|
+
return ctx.run({ span, bag: span.bag, server: store && store.server }, fn, undefined, [span])
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
currentSpan () {
|
|
619
|
+
const s = ctx.currentSpan()
|
|
620
|
+
return s instanceof Span ? s : undefined
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// -- instrumentation helpers ----------------------------------------------------------------
|
|
624
|
+
|
|
625
|
+
ignoreInbound (method, target, request) {
|
|
626
|
+
const m = String(method || '').toUpperCase()
|
|
627
|
+
if (m === 'OPTIONS' || m === 'HEAD') return true
|
|
628
|
+
return matches(this.ignoreIncoming, target || '', request)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** NIO-220: a SERVER span for an inbound request, or null when ignored. */
|
|
632
|
+
serverSpan (req) {
|
|
633
|
+
const method = req.method || 'GET'
|
|
634
|
+
const target = req.target
|
|
635
|
+
if (this.ignoreInbound(method, target, req.request)) return null
|
|
636
|
+
const b = this.bridge || this.checkBridge()
|
|
637
|
+
if (b) {
|
|
638
|
+
const tags = { 'http.method': method, 'http.target': target, 'http.scheme': req.scheme, 'http.host': req.host, 'http.url': `${req.scheme}://${req.host}${target}` }
|
|
639
|
+
if (req.userAgent) tags['http.user_agent'] = req.userAgent
|
|
640
|
+
return b.serverSpan(method + ' ' + (this.normalizePaths ? normalizePath(target) : rawPath(target)), tags, req.headers)
|
|
641
|
+
}
|
|
642
|
+
const headers = req.headers
|
|
643
|
+
// Node's IncomingHttpHeaders have lowercase keys: read them directly (hot path)
|
|
644
|
+
const plain = Boolean(headers && req.lowercaseHeaders === true)
|
|
645
|
+
const read = (name) => (plain ? headers[name] : firstValue(headers, name))
|
|
646
|
+
const parsed = headers ? w3c.parseTraceparent(read('traceparent')) : null
|
|
647
|
+
let traceId
|
|
648
|
+
let parentId = null
|
|
649
|
+
let sampled
|
|
650
|
+
let state = null
|
|
651
|
+
let remote = false
|
|
652
|
+
if (parsed) {
|
|
653
|
+
traceId = parsed.traceId
|
|
654
|
+
parentId = parsed.spanId
|
|
655
|
+
sampled = parsed.sampled
|
|
656
|
+
remote = true
|
|
657
|
+
const states = (plain ? [].concat(headers.tracestate === undefined ? [] : headers.tracestate) : w3c.getAll(headers, 'tracestate')).map(w3c.text).filter((s) => s)
|
|
658
|
+
state = states.length ? states.join(',') : null
|
|
659
|
+
} else {
|
|
660
|
+
const t = this._newTrace()
|
|
661
|
+
traceId = t.traceId
|
|
662
|
+
sampled = t.sampled
|
|
663
|
+
}
|
|
664
|
+
const rawBag = headers ? (plain ? headers.baggage : w3c.getAll(headers, 'baggage')) : undefined
|
|
665
|
+
const bag = rawBag === undefined || (Array.isArray(rawBag) && rawBag.length === 0) ? w3c.EMPTY_BAG : w3c.parseBaggage(rawBag)
|
|
666
|
+
const tags = {
|
|
667
|
+
'http.method': method,
|
|
668
|
+
'http.target': target,
|
|
669
|
+
'http.scheme': req.scheme,
|
|
670
|
+
'http.host': req.host,
|
|
671
|
+
'http.url': `${req.scheme}://${req.host}${target}`
|
|
672
|
+
}
|
|
673
|
+
if (req.userAgent) tags['http.user_agent'] = req.userAgent
|
|
674
|
+
const name = method + ' ' + (this.normalizePaths ? normalizePath(target) : rawPath(target))
|
|
675
|
+
return this._make(name, 'SERVER', traceId, parentId, sampled, state, tags, bag, remote, null)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** NIO-222: a CLIENT span under the current span, or null (no root CLIENT spans). */
|
|
679
|
+
clientSpan (method, url, host, port, request) {
|
|
680
|
+
if (!this.outbound) return null
|
|
681
|
+
const store = ctx.current()
|
|
682
|
+
const parent = store && store.span
|
|
683
|
+
if (this.bridge) {
|
|
684
|
+
if (!parent || matches(this.ignoreOutgoing, url, request)) return null
|
|
685
|
+
const t = { 'http.method': method, 'http.url': url, 'net.peer.name': host }
|
|
686
|
+
if (port) t['net.peer.port'] = port
|
|
687
|
+
return this.bridge.child(parent, method + ' ' + host, 'CLIENT', t)
|
|
688
|
+
}
|
|
689
|
+
if (!(parent instanceof Span)) return null
|
|
690
|
+
if (matches(this.ignoreOutgoing, url, request)) return null
|
|
691
|
+
const tags = { 'http.method': method, 'http.url': url, 'net.peer.name': host }
|
|
692
|
+
if (port) tags['net.peer.port'] = port
|
|
693
|
+
return this.childOf(parent, method + ' ' + host, 'CLIENT', tags)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// -- export (NIO-204..NIO-206) --------------------------------------------------------------
|
|
697
|
+
|
|
698
|
+
_takeToken () {
|
|
699
|
+
const rate = this.maxSpansPerSecond
|
|
700
|
+
if (rate <= 0) return true
|
|
701
|
+
const now = perf.now()
|
|
702
|
+
if (this._refill === null) this._refill = now
|
|
703
|
+
let tokens = this._tokens + ((now - this._refill) / 1000) * rate
|
|
704
|
+
this._refill = now
|
|
705
|
+
if (tokens > rate) tokens = rate
|
|
706
|
+
if (tokens >= 1) {
|
|
707
|
+
this._tokens = tokens - 1
|
|
708
|
+
return true
|
|
709
|
+
}
|
|
710
|
+
this._tokens = tokens
|
|
711
|
+
return false
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
recordDropped (n) {
|
|
715
|
+
n = n || 1
|
|
716
|
+
this.dropped += n
|
|
717
|
+
const now = Date.now()
|
|
718
|
+
if (now - this._dropLogged >= 60000) {
|
|
719
|
+
this._dropLogged = now
|
|
720
|
+
debug('trace spans dropped (rate limit or send failure): %d so far', this.dropped)
|
|
721
|
+
}
|
|
722
|
+
if (this._onDrop) {
|
|
723
|
+
try {
|
|
724
|
+
this._onDrop(n)
|
|
725
|
+
} catch (_) {}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** OTX-032: a span converted by the bridge's processor (the app SDK sampled it already). */
|
|
730
|
+
exportConverted (zipkin) {
|
|
731
|
+
if (!this._takeToken()) {
|
|
732
|
+
this.recordDropped(1)
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
this._sink.add(zipkin)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
flush () {
|
|
739
|
+
if (this._sink && typeof this._sink.flush === 'function') this._sink.flush()
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
_export (span, durationUs) {
|
|
743
|
+
const local = span.localParent
|
|
744
|
+
span.localParent = null
|
|
745
|
+
// 1. root CLIENT spans are never exported (A.6)
|
|
746
|
+
if (span.kind === 'CLIENT' && !span.parentId) return
|
|
747
|
+
const error = span.status === 'ERROR'
|
|
748
|
+
// 2. sampling (NIO-205)
|
|
749
|
+
if (!span.sampled && !(error && this.exportUnsampledErrors)) return
|
|
750
|
+
// 3. duration floor and its exemptions
|
|
751
|
+
if (durationUs <= this.minDurationUs && !error && span.kind !== 'PRODUCER' && !span.remoteParent && !span.childExported) return
|
|
752
|
+
// 4. rate limit (NIO-206)
|
|
753
|
+
if (!this._takeToken()) {
|
|
754
|
+
this.recordDropped(1)
|
|
755
|
+
return
|
|
756
|
+
}
|
|
757
|
+
if (local) local.childExported = true
|
|
758
|
+
this._sink.add(this.toZipkin(span, durationUs))
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
toZipkin (span, durationUs) {
|
|
762
|
+
const tags = {}
|
|
763
|
+
const attrs = span.attributes
|
|
764
|
+
for (const k of Object.keys(attrs)) {
|
|
765
|
+
const v = attrs[k]
|
|
766
|
+
if (v === undefined || v === null) continue
|
|
767
|
+
tags[k] = tagValue(v)
|
|
768
|
+
}
|
|
769
|
+
const alias = (legacy, stable) => {
|
|
770
|
+
if (tags[legacy] === undefined && tags[stable] !== undefined) tags[legacy] = tags[stable]
|
|
771
|
+
}
|
|
772
|
+
alias('http.method', 'http.request.method')
|
|
773
|
+
alias('http.status_code', 'http.response.status_code')
|
|
774
|
+
alias('http.url', 'url.full')
|
|
775
|
+
alias('http.scheme', 'url.scheme')
|
|
776
|
+
if (tags['http.target'] === undefined && tags['url.path'] !== undefined) {
|
|
777
|
+
tags['http.target'] = tags['url.path'] + (tags['url.query'] !== undefined ? '?' + tags['url.query'] : '')
|
|
778
|
+
}
|
|
779
|
+
if (span.status) {
|
|
780
|
+
tags['otel.status_code'] = span.status
|
|
781
|
+
if (span.status === 'ERROR' && span.error) tags.error = span.error
|
|
782
|
+
}
|
|
783
|
+
if (!span.sampled) tags['pmx.sampled'] = 'false'
|
|
784
|
+
const links = encodeLinks(span.links, span.droppedLinks)
|
|
785
|
+
if (links.value !== undefined) tags['pmx.links'] = links.value
|
|
786
|
+
if (links.dropped) tags['otel.dropped_links_count'] = String(links.dropped)
|
|
787
|
+
for (const k of Object.keys(this.resource)) {
|
|
788
|
+
if (tags[k] === undefined) tags[k] = this.resource[k]
|
|
789
|
+
}
|
|
790
|
+
const out = { traceId: span.traceId, id: span.spanId }
|
|
791
|
+
if (span.parentId) out.parentId = span.parentId
|
|
792
|
+
out.name = span.name
|
|
793
|
+
if (span.kind !== 'INTERNAL') out.kind = span.kind
|
|
794
|
+
out.timestamp = span.startUs
|
|
795
|
+
out.duration = durationUs
|
|
796
|
+
out.localEndpoint = { serviceName: this.serviceName }
|
|
797
|
+
out.tags = tags
|
|
798
|
+
if (span.events && span.events.length) out.annotations = span.events.slice()
|
|
799
|
+
return out
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** startActiveSpan semantics (NIO-207): the span ends when fn returns, throws or settles. */
|
|
804
|
+
function runActive (span, fn) {
|
|
805
|
+
if (!(span instanceof Span)) return fn(span)
|
|
806
|
+
const store = ctx.current()
|
|
807
|
+
let r
|
|
808
|
+
try {
|
|
809
|
+
r = ctx.run({ span, bag: span.bag, server: store && store.server }, fn, undefined, [span])
|
|
810
|
+
} catch (err) {
|
|
811
|
+
span.recordException(err)
|
|
812
|
+
span.end()
|
|
813
|
+
throw err
|
|
814
|
+
}
|
|
815
|
+
if (r && typeof r.then === 'function') {
|
|
816
|
+
return Promise.resolve(r).then((v) => {
|
|
817
|
+
span.end()
|
|
818
|
+
return v
|
|
819
|
+
}, (err) => {
|
|
820
|
+
span.recordException(err)
|
|
821
|
+
span.end()
|
|
822
|
+
throw err
|
|
823
|
+
})
|
|
824
|
+
}
|
|
825
|
+
span.end()
|
|
826
|
+
return r
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function firstValue (headers, name) {
|
|
830
|
+
const all = w3c.getAll(headers, name)
|
|
831
|
+
return all.length ? all[0] : undefined
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
module.exports = {
|
|
835
|
+
Tracer,
|
|
836
|
+
Span,
|
|
837
|
+
noopTracer,
|
|
838
|
+
noopSpan,
|
|
839
|
+
normKind,
|
|
840
|
+
sampledFor,
|
|
841
|
+
encodeLinks,
|
|
842
|
+
tagValue,
|
|
843
|
+
runActive,
|
|
844
|
+
randomHex,
|
|
845
|
+
nowUs,
|
|
846
|
+
dbOptions,
|
|
847
|
+
messagingOptions,
|
|
848
|
+
DEFAULT_IGNORE_INCOMING,
|
|
849
|
+
MAX_LINKS
|
|
850
|
+
}
|