@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,797 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Bridge mode (85 Part A; OTX-001…OTX-036, Node side): when the app runs its own OpenTelemetry SDK,
|
|
5
|
+
// @aidra/pmx-io creates its spans through that SDK instead of exporting competing ones.
|
|
6
|
+
//
|
|
7
|
+
// * Detection (OTX-002/003) is a property lookup on the OTel API's global registry
|
|
8
|
+
// (globalThis[Symbol.for('opentelemetry.js.api.1')]); this module is loaded only once an app SDK
|
|
9
|
+
// was found. The switch is one-way; spans started natively before it end natively.
|
|
10
|
+
// * Spans (OTX-010/015): created with the app SDK's tracer '@aidra/pmx-io' and wrapped in BridgeSpan,
|
|
11
|
+
// which has the native Span's API, so instrumentation and getTracer() code is unchanged.
|
|
12
|
+
// * Context (OTX-011): the OTel active context is the source of truth. When pmx makes a span current
|
|
13
|
+
// (context.js run/enter/bind) it also makes the OTel span active; pmx's current span is the OTel
|
|
14
|
+
// active span (its own BridgeSpan, or a ForeignSpan wrapping an app/OTel-instrumentation span).
|
|
15
|
+
// * Propagation (OTX-012): inject/extract and inbound/outbound HTTP use the app's global propagator.
|
|
16
|
+
// * Sampling (OTX-013): the app SDK's sampler decides; pmx's sampling options are ignored.
|
|
17
|
+
// * No double spans (OTX-020/021): no SERVER span when a recording SERVER span of another scope is
|
|
18
|
+
// active; no CLIENT/PRODUCER span when the active span is a recording span of the same kind from
|
|
19
|
+
// another scope; libraries whose hooked function OTel's shimmer wrapped again (`__wrapped`) are
|
|
20
|
+
// passed through (lib/itg/common.js). Metrics are never suppressed (OTX-022).
|
|
21
|
+
// * Export (OTX-030…036): 'receiver' (the app's OTLP exporter reaches prodman), 'processor' (a span
|
|
22
|
+
// processor added to the app SDK converts finished, sampled spans to the native Zipkin shape and
|
|
23
|
+
// hands them to the native export pipeline) or 'none'.
|
|
24
|
+
//
|
|
25
|
+
// Nothing here requires an OpenTelemetry package: only objects the app's SDK registered are used.
|
|
26
|
+
|
|
27
|
+
const { VERSION } = require('../constants')
|
|
28
|
+
const w3c = require('../tracing/w3c')
|
|
29
|
+
const debug = require('../debug')('axm:tracing')
|
|
30
|
+
|
|
31
|
+
const API_KEY = Symbol.for('opentelemetry.js.api.1')
|
|
32
|
+
const SPAN_KEY = Symbol.for('OpenTelemetry Context Key SPAN')
|
|
33
|
+
const KINDS = ['INTERNAL', 'SERVER', 'CLIENT', 'PRODUCER', 'CONSUMER']
|
|
34
|
+
const OTEL_KIND = { INTERNAL: 0, SERVER: 1, CLIENT: 2, PRODUCER: 3, CONSUMER: 4 }
|
|
35
|
+
|
|
36
|
+
/** OTX-002: the app's tracer provider, or null (no allocation when there is none). */
|
|
37
|
+
function detect () {
|
|
38
|
+
const g = globalThis[API_KEY]
|
|
39
|
+
if (!g || !g.trace) return null
|
|
40
|
+
let p = g.trace
|
|
41
|
+
try {
|
|
42
|
+
if (typeof p.getDelegate === 'function') p = p.getDelegate()
|
|
43
|
+
} catch (_) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
if (!p || (p.constructor && p.constructor.name === 'NoopTracerProvider')) return null
|
|
47
|
+
return p
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------------------------
|
|
51
|
+
// minimal OTel-compatible values (no API package is loaded)
|
|
52
|
+
|
|
53
|
+
class BaseContext {
|
|
54
|
+
constructor (parent) {
|
|
55
|
+
this._v = parent ? new Map(parent._v) : new Map()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getValue (k) {
|
|
59
|
+
return this._v.get(k)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setValue (k, v) {
|
|
63
|
+
const c = new BaseContext(this)
|
|
64
|
+
c._v.set(k, v)
|
|
65
|
+
return c
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
deleteValue (k) {
|
|
69
|
+
const c = new BaseContext(this)
|
|
70
|
+
c._v.delete(k)
|
|
71
|
+
return c
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const ROOT = new BaseContext()
|
|
75
|
+
|
|
76
|
+
class TraceState {
|
|
77
|
+
constructor (s) {
|
|
78
|
+
this._s = s || ''
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
serialize () {
|
|
82
|
+
return this._s
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get (key) {
|
|
86
|
+
for (const part of this._s.split(',')) {
|
|
87
|
+
const i = part.indexOf('=')
|
|
88
|
+
if (i > 0 && part.slice(0, i).trim() === key) return part.slice(i + 1).trim()
|
|
89
|
+
}
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set () { return this }
|
|
94
|
+
unset () { return this }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A non-recording span standing for a remote or native parent context. */
|
|
98
|
+
function parentSpan (traceId, spanId, sampled, traceState, isRemote) {
|
|
99
|
+
const sc = { traceId, spanId, traceFlags: sampled ? 1 : 0, isRemote: Boolean(isRemote) }
|
|
100
|
+
if (traceState) sc.traceState = new TraceState(traceState)
|
|
101
|
+
return {
|
|
102
|
+
spanContext: () => sc,
|
|
103
|
+
isRecording: () => false,
|
|
104
|
+
setAttribute () { return this },
|
|
105
|
+
setAttributes () { return this },
|
|
106
|
+
addEvent () { return this },
|
|
107
|
+
addLink () { return this },
|
|
108
|
+
addLinks () { return this },
|
|
109
|
+
setStatus () { return this },
|
|
110
|
+
updateName () { return this },
|
|
111
|
+
recordException () {},
|
|
112
|
+
end () {}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const SETTER = { set (c, k, v) { c[k] = v } }
|
|
117
|
+
const GETTER = {
|
|
118
|
+
keys: (c) => Object.keys(c || {}),
|
|
119
|
+
get (c, k) {
|
|
120
|
+
const all = w3c.getAll(c, String(k).toLowerCase())
|
|
121
|
+
if (all.length === 0) return undefined
|
|
122
|
+
return all.length === 1 ? w3c.text(all[0]) || all[0] : all.map((v) => w3c.text(v) || v)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function scopeOf (o) {
|
|
127
|
+
const s = o && (o.instrumentationScope || o.instrumentationLibrary)
|
|
128
|
+
return s && typeof s.name === 'string' ? s.name : undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function kindOf (o) {
|
|
132
|
+
return o && typeof o.kind === 'number' ? KINDS[o.kind] : undefined
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** '@opentelemetry/instrumentation-pg' → 'pg' (the name reported in `suppressed`). */
|
|
136
|
+
function libName (scope) {
|
|
137
|
+
if (!scope) return 'unknown'
|
|
138
|
+
return scope.replace(/^@opentelemetry\/instrumentation-/, '').replace(/^opentelemetry-instrumentation-/, '')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------------------------
|
|
142
|
+
// spans
|
|
143
|
+
|
|
144
|
+
function makeSpanClasses (Span) {
|
|
145
|
+
/** A pmx span backed by an app SDK span (OTX-010). */
|
|
146
|
+
class BridgeSpan extends Span {
|
|
147
|
+
constructor (tracer, otel, name, kind, parentId, attributes, bag) {
|
|
148
|
+
const sc = otel.spanContext()
|
|
149
|
+
super(tracer, name, kind, sc.traceId, parentId, (sc.traceFlags & 1) === 1,
|
|
150
|
+
sc.traceState && typeof sc.traceState.serialize === 'function' ? sc.traceState.serialize() || null : null,
|
|
151
|
+
attributes, bag, false, null)
|
|
152
|
+
this.spanId = sc.spanId
|
|
153
|
+
this.otel = otel
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
setAttribute (key, value) {
|
|
157
|
+
if (!this.ended && typeof key === 'string') {
|
|
158
|
+
this.attributes[key] = value
|
|
159
|
+
try {
|
|
160
|
+
if (value !== undefined && value !== null) this.otel.setAttribute(key, otelValue(value))
|
|
161
|
+
} catch (_) {}
|
|
162
|
+
}
|
|
163
|
+
return this
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setAttributes (obj) {
|
|
167
|
+
if (obj && typeof obj === 'object') for (const k of Object.keys(obj)) this.setAttribute(k, obj[k])
|
|
168
|
+
return this
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
updateName (name) {
|
|
172
|
+
if (!this.ended) {
|
|
173
|
+
this.name = String(name)
|
|
174
|
+
try { this.otel.updateName(this.name) } catch (_) {}
|
|
175
|
+
}
|
|
176
|
+
return this
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
addEvent (name, attrsOrTime, time) {
|
|
180
|
+
if (!this.ended) {
|
|
181
|
+
try { this.otel.addEvent(String(name), attrsOrTime, time) } catch (_) {}
|
|
182
|
+
}
|
|
183
|
+
return this
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
addLink (spanContext, attributes) {
|
|
187
|
+
if (this.ended) return this
|
|
188
|
+
const l = attributes === undefined ? spanContext : { context: spanContext, attributes }
|
|
189
|
+
const ctx = l && (w3c.isSpanContext(l) ? l : l.context)
|
|
190
|
+
if (!w3c.isSpanContext(ctx)) return this
|
|
191
|
+
if (!this.links) this.links = []
|
|
192
|
+
if (this.links.length >= this.maxLinks) {
|
|
193
|
+
this.droppedLinks++
|
|
194
|
+
return this
|
|
195
|
+
}
|
|
196
|
+
this.links.push(l)
|
|
197
|
+
try {
|
|
198
|
+
if (typeof this.otel.addLink === 'function') this.otel.addLink(otelLink(l))
|
|
199
|
+
} catch (_) {}
|
|
200
|
+
return this
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
recordException (err) {
|
|
204
|
+
if (this.ended) return this
|
|
205
|
+
super.recordException(err)
|
|
206
|
+
try {
|
|
207
|
+
this.otel.recordException(err)
|
|
208
|
+
this.otel.setStatus({ code: 2, message: this.error || undefined })
|
|
209
|
+
} catch (_) {}
|
|
210
|
+
return this
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
setStatus (status, message) {
|
|
214
|
+
if (this.ended) return this
|
|
215
|
+
super.setStatus(status, message)
|
|
216
|
+
try {
|
|
217
|
+
if (this.status === 'ERROR') this.otel.setStatus({ code: 2, message: this.error || undefined })
|
|
218
|
+
else if (this.status === 'OK') this.otel.setStatus({ code: 1 })
|
|
219
|
+
else this.otel.setStatus({ code: 0 })
|
|
220
|
+
} catch (_) {}
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
isRecording () {
|
|
225
|
+
try {
|
|
226
|
+
return !this.ended && this.otel.isRecording()
|
|
227
|
+
} catch (_) {
|
|
228
|
+
return !this.ended
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
end (endTime) {
|
|
233
|
+
if (this.ended) return
|
|
234
|
+
this.ended = true
|
|
235
|
+
try {
|
|
236
|
+
if (this.droppedLinks && typeof this.otel.setAttribute === 'function') {
|
|
237
|
+
this.otel.setAttribute('otel.dropped_links_count', this.droppedLinks)
|
|
238
|
+
}
|
|
239
|
+
this.otel.end(endTime)
|
|
240
|
+
} catch (err) {
|
|
241
|
+
debug('span end failed: %s', err && err.message)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return BridgeSpan
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** An app or OTel-instrumentation span seen as pmx's current span (parent of pmx spans). */
|
|
250
|
+
class ForeignSpan {
|
|
251
|
+
constructor (otel) {
|
|
252
|
+
const sc = otel.spanContext()
|
|
253
|
+
this.otel = otel
|
|
254
|
+
this.foreign = true
|
|
255
|
+
this.traceId = sc.traceId
|
|
256
|
+
this.spanId = sc.spanId
|
|
257
|
+
this.sampled = (sc.traceFlags & 1) === 1
|
|
258
|
+
this.traceState = sc.traceState && typeof sc.traceState.serialize === 'function' ? sc.traceState.serialize() || null : null
|
|
259
|
+
this.bag = w3c.EMPTY_BAG
|
|
260
|
+
this.kind = kindOf(otel) || 'INTERNAL'
|
|
261
|
+
this.attributes = {}
|
|
262
|
+
this.ended = false
|
|
263
|
+
this._ctx = null
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
get context () {
|
|
267
|
+
if (!this._ctx) this._ctx = w3c.makeContext(this.traceId, this.spanId, this.sampled, this.traceState, this.bag, false)
|
|
268
|
+
return this._ctx
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
isRecording () {
|
|
272
|
+
try { return this.otel.isRecording() } catch (_) { return false }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
spanContext () { return this.otel.spanContext() }
|
|
276
|
+
traceparent () { return `00-${this.traceId}-${this.spanId}-${this.sampled ? '01' : '00'}` }
|
|
277
|
+
setAttribute (k, v) { try { this.otel.setAttribute(k, otelValue(v)) } catch (_) {} return this }
|
|
278
|
+
setAttributes (o) { if (o) for (const k of Object.keys(o)) this.setAttribute(k, o[k]); return this }
|
|
279
|
+
updateName (n) { try { this.otel.updateName(String(n)) } catch (_) {} return this }
|
|
280
|
+
addEvent (n) { try { this.otel.addEvent(String(n)) } catch (_) {} return this }
|
|
281
|
+
addLink () { return this }
|
|
282
|
+
recordException (e) { try { this.otel.recordException(e) } catch (_) {} return this }
|
|
283
|
+
setStatus () { return this }
|
|
284
|
+
end () {} // not ours to end
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function otelValue (v) {
|
|
288
|
+
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return v
|
|
289
|
+
if (Array.isArray(v)) return v.map((x) => (typeof x === 'number' || typeof x === 'boolean' ? x : String(x)))
|
|
290
|
+
try {
|
|
291
|
+
return JSON.stringify(v)
|
|
292
|
+
} catch (_) {
|
|
293
|
+
return String(v)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function otelLink (l) {
|
|
298
|
+
const ctx = w3c.isSpanContext(l) ? l : l.context
|
|
299
|
+
const out = { context: { traceId: ctx.traceId, spanId: ctx.spanId, traceFlags: ctx.sampled ? 1 : 0, isRemote: true } }
|
|
300
|
+
if (l.attributes) {
|
|
301
|
+
out.attributes = {}
|
|
302
|
+
for (const k of Object.keys(l.attributes)) out.attributes[k] = otelValue(l.attributes[k])
|
|
303
|
+
}
|
|
304
|
+
return out
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------------------------
|
|
308
|
+
// the bridge
|
|
309
|
+
|
|
310
|
+
class Bridge {
|
|
311
|
+
/**
|
|
312
|
+
* @param {object} tracer the native Tracer (lib/tracing/tracer.js)
|
|
313
|
+
* @param {object} provider the app's tracer provider (detect())
|
|
314
|
+
* @param {object} env { report(obj), tracing (pmx tracing enabled), standalone, prodmanUrl, debugOut }
|
|
315
|
+
*/
|
|
316
|
+
constructor (tracer, provider, env) {
|
|
317
|
+
const { Span } = require('../tracing/tracer')
|
|
318
|
+
this.BridgeSpan = makeSpanClasses(Span)
|
|
319
|
+
this.tracer = tracer
|
|
320
|
+
this.provider = provider
|
|
321
|
+
this.env = env || {}
|
|
322
|
+
this.g = globalThis[API_KEY]
|
|
323
|
+
this.otelTracer = provider.getTracer('@pmx/io', VERSION)
|
|
324
|
+
this.byOtel = new WeakMap() // otel span → BridgeSpan
|
|
325
|
+
this.foreign = new WeakMap() // otel span → ForeignSpan
|
|
326
|
+
this.suppressed = new Set()
|
|
327
|
+
this.exportPath = 'none'
|
|
328
|
+
this.processor = null
|
|
329
|
+
this.sdk = sdkVersion(provider)
|
|
330
|
+
this.disabledMarkerCheck = process.env.PMX_IO_TEST_OTEL_NO_MARKER === '1'
|
|
331
|
+
this.enterWarned = false
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// -- context ----------------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
cm () {
|
|
337
|
+
const g = this.g
|
|
338
|
+
return g && g.context && typeof g.context.active === 'function' ? g.context : null
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
active () {
|
|
342
|
+
const cm = this.cm()
|
|
343
|
+
try {
|
|
344
|
+
return cm ? cm.active() : ROOT
|
|
345
|
+
} catch (_) {
|
|
346
|
+
return ROOT
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
activeOtelSpan () {
|
|
351
|
+
const ctx = this.active()
|
|
352
|
+
return ctx && typeof ctx.getValue === 'function' ? ctx.getValue(SPAN_KEY) : undefined
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** The OTel span standing for a pmx span (BridgeSpan, ForeignSpan, native Span). */
|
|
356
|
+
otelOf (span) {
|
|
357
|
+
if (!span) return undefined
|
|
358
|
+
if (span.otel) return span.otel
|
|
359
|
+
if (typeof span.traceId === 'string' && typeof span.spanId === 'string') {
|
|
360
|
+
return parentSpan(span.traceId, span.spanId, span.sampled, span.traceState, false)
|
|
361
|
+
}
|
|
362
|
+
return undefined
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
ctxWith (span, base) {
|
|
366
|
+
const o = this.otelOf(span)
|
|
367
|
+
base = base || this.active()
|
|
368
|
+
return o ? base.setValue(SPAN_KEY, o) : base
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** context.js: the store pmx code sees as current (OTX-011). */
|
|
372
|
+
current (store) {
|
|
373
|
+
const o = this.activeOtelSpan()
|
|
374
|
+
if (!o) return store
|
|
375
|
+
if (store && store.span && store.span.otel === o) return store
|
|
376
|
+
let span = this.byOtel.get(o)
|
|
377
|
+
if (!span) {
|
|
378
|
+
span = this.foreign.get(o)
|
|
379
|
+
if (!span) {
|
|
380
|
+
try {
|
|
381
|
+
span = new ForeignSpan(o)
|
|
382
|
+
} catch (_) {
|
|
383
|
+
return store
|
|
384
|
+
}
|
|
385
|
+
this.foreign.set(o, span)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return { span, bag: store ? store.bag : w3c.EMPTY_BAG, server: store && store.server }
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** context.js run(): fn runs with the store and, in OTel, with its span active. */
|
|
392
|
+
run (als, store, fn, thisArg, args) {
|
|
393
|
+
const cm = this.cm()
|
|
394
|
+
const span = store && store.span
|
|
395
|
+
if (!cm || !span) return als.run(store, () => fn.apply(thisArg, args || []))
|
|
396
|
+
const ctx = this.ctxWith(span)
|
|
397
|
+
return als.run(store, () => cm.with(ctx, () => fn.apply(thisArg, args || [])))
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** context.js enter(): also enter the OTel context (AsyncLocalStorageContextManager). */
|
|
401
|
+
enter (store) {
|
|
402
|
+
const cm = this.cm()
|
|
403
|
+
const als = cm && cm._asyncLocalStorage
|
|
404
|
+
if (!als || typeof als.enterWith !== 'function') {
|
|
405
|
+
// another context manager (or a future SDK without the private field): pmx's own context
|
|
406
|
+
// still holds the span, but OTel spans the app starts there do not see it as their parent
|
|
407
|
+
if (!this.enterWarned) {
|
|
408
|
+
this.enterWarned = true
|
|
409
|
+
debug('OpenTelemetry context manager without _asyncLocalStorage: inbound requests and iterator spans are not mirrored into the app SDK context')
|
|
410
|
+
}
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const span = store && store.span
|
|
415
|
+
als.enterWith(span ? this.ctxWith(span) : this.active().deleteValue(SPAN_KEY))
|
|
416
|
+
} catch (_) {}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** context.js bind(): fn runs in the OTel context current at bind time as well. */
|
|
420
|
+
bind (fn) {
|
|
421
|
+
const cm = this.cm()
|
|
422
|
+
if (!cm || typeof cm.bind !== 'function') return fn
|
|
423
|
+
try {
|
|
424
|
+
return cm.bind(this.active(), fn)
|
|
425
|
+
} catch (_) {
|
|
426
|
+
return fn
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// -- spans ------------------------------------------------------------------------------------
|
|
431
|
+
|
|
432
|
+
/** OTX-021: the active span blocks a pmx span of this kind, else null; records the library. */
|
|
433
|
+
guard (kind) {
|
|
434
|
+
if (kind !== 'SERVER' && kind !== 'CLIENT' && kind !== 'PRODUCER') return false
|
|
435
|
+
const o = this.activeOtelSpan()
|
|
436
|
+
if (!o || this.byOtel.has(o)) return false
|
|
437
|
+
let recording = false
|
|
438
|
+
try {
|
|
439
|
+
recording = o.isRecording()
|
|
440
|
+
} catch (_) {}
|
|
441
|
+
if (!recording || kindOf(o) !== kind) return false
|
|
442
|
+
const scope = scopeOf(o)
|
|
443
|
+
if (scope === '@pmx/io') return false
|
|
444
|
+
this.suppress(libName(scope))
|
|
445
|
+
return true
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
suppress (lib) {
|
|
449
|
+
if (this.suppressed.has(lib)) return
|
|
450
|
+
this.suppressed.add(lib)
|
|
451
|
+
this.report()
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Create a BridgeSpan under the OTel context `ctx`. */
|
|
455
|
+
make (name, kind, ctx, attributes, links, startUs, bag) {
|
|
456
|
+
if (this.tracer._onStart) {
|
|
457
|
+
try { this.tracer._onStart() } catch (_) {}
|
|
458
|
+
}
|
|
459
|
+
const opts = { kind: OTEL_KIND[kind] || 0 }
|
|
460
|
+
if (attributes) {
|
|
461
|
+
opts.attributes = {}
|
|
462
|
+
for (const k of Object.keys(attributes)) {
|
|
463
|
+
const v = attributes[k]
|
|
464
|
+
if (v !== undefined && v !== null) opts.attributes[k] = otelValue(v)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (links && links.length) opts.links = links.map(otelLink)
|
|
468
|
+
if (startUs !== undefined) opts.startTime = startUs / 1000
|
|
469
|
+
const o = this.otelTracer.startSpan(String(name), opts, ctx)
|
|
470
|
+
const parent = ctx && ctx.getValue(SPAN_KEY)
|
|
471
|
+
let parentId = null
|
|
472
|
+
try {
|
|
473
|
+
if (parent) parentId = parent.spanContext().spanId
|
|
474
|
+
} catch (_) {}
|
|
475
|
+
const span = new this.BridgeSpan(this.tracer, o, String(name), kind, parentId, attributes, bag)
|
|
476
|
+
if (links && links.length) span.links = links.slice(0, span.maxLinks)
|
|
477
|
+
this.byOtel.set(o, span)
|
|
478
|
+
return span
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Tracer.startSpan in bridge mode (parent: undefined | Span | ForeignSpan | SpanContext | NEW_TRACE). */
|
|
482
|
+
startSpan (name, kind, o, store) {
|
|
483
|
+
const parent = o.parent
|
|
484
|
+
let ctx
|
|
485
|
+
let bag = store ? store.bag : undefined
|
|
486
|
+
if (parent === undefined) {
|
|
487
|
+
ctx = this.active()
|
|
488
|
+
if (store && store.span && !ctx.getValue(SPAN_KEY)) ctx = this.ctxWith(store.span, ctx)
|
|
489
|
+
} else if (parent === w3c.NEW_TRACE) {
|
|
490
|
+
ctx = this.active().deleteValue(SPAN_KEY)
|
|
491
|
+
} else if (w3c.isSpanContext(parent)) {
|
|
492
|
+
ctx = this.active().setValue(SPAN_KEY, parentSpan(parent.traceId, parent.spanId, parent.sampled, parent.traceState, parent.isRemote))
|
|
493
|
+
bag = w3c.contextBag(parent)
|
|
494
|
+
} else {
|
|
495
|
+
ctx = this.ctxWith(parent)
|
|
496
|
+
bag = parent.bag
|
|
497
|
+
}
|
|
498
|
+
const links = Array.isArray(o.links) ? o.links.map(linkOf).filter(Boolean) : null
|
|
499
|
+
return this.make(name, kind, ctx, o.attributes, links, o.startUs, bag)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
child (parent, name, kind, attributes) {
|
|
503
|
+
if (this.guard(kind)) return null
|
|
504
|
+
return this.make(name, kind, this.ctxWith(parent), attributes, null, undefined, parent && parent.bag)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Inbound HTTP (NIO-220) through the app's propagator (OTX-012) and the runtime guard. */
|
|
508
|
+
serverSpan (name, tags, headers) {
|
|
509
|
+
if (this.guard('SERVER')) return null
|
|
510
|
+
// OTX-020: OpenTelemetry's http instrumentation wraps Server.prototype.emit (shimmer
|
|
511
|
+
// `__wrapped`). On Node ≥ 20 pmx sees the request through the `http.server.request.start`
|
|
512
|
+
// channel, published before that wrapper runs, so its span is not active yet: the marker decides.
|
|
513
|
+
if (!this.disabledMarkerCheck && this.httpCovered()) {
|
|
514
|
+
this.suppress('http')
|
|
515
|
+
return null
|
|
516
|
+
}
|
|
517
|
+
let ctx = this.active().deleteValue(SPAN_KEY)
|
|
518
|
+
const prop = this.g && this.g.propagation
|
|
519
|
+
if (prop && typeof prop.extract === 'function' && headers) {
|
|
520
|
+
try {
|
|
521
|
+
ctx = prop.extract(ctx, headers, GETTER)
|
|
522
|
+
} catch (_) {}
|
|
523
|
+
} else if (headers) {
|
|
524
|
+
const sc = w3c.extract(headers)
|
|
525
|
+
if (sc) ctx = ctx.setValue(SPAN_KEY, parentSpan(sc.traceId, sc.spanId, sc.sampled, sc.traceState, true))
|
|
526
|
+
}
|
|
527
|
+
return this.make(name, 'SERVER', ctx, tags, null, undefined, undefined)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
httpCovered () {
|
|
531
|
+
try {
|
|
532
|
+
const e = require('http').Server.prototype.emit
|
|
533
|
+
return Boolean(e && e.__wrapped === true)
|
|
534
|
+
} catch (_) {
|
|
535
|
+
return false
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** [[name, value]] trace headers for `span` through the app's propagator (OTX-012). */
|
|
540
|
+
headersFor (span) {
|
|
541
|
+
const prop = this.g && this.g.propagation
|
|
542
|
+
if (!prop || typeof prop.inject !== 'function') return null
|
|
543
|
+
const carrier = {}
|
|
544
|
+
try {
|
|
545
|
+
prop.inject(this.ctxWith(span), carrier, SETTER)
|
|
546
|
+
} catch (_) {
|
|
547
|
+
return null
|
|
548
|
+
}
|
|
549
|
+
return Object.keys(carrier).map((k) => [k, String(carrier[k])])
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** io.inject (NIO-241) through the propagator; false when there is none (native rules apply). */
|
|
553
|
+
inject (carrier, span) {
|
|
554
|
+
const pairs = this.headersFor(span)
|
|
555
|
+
if (!pairs) return false
|
|
556
|
+
w3c.writeHeaders(carrier, pairs, false, false)
|
|
557
|
+
return true
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** io.extract (NIO-242) through the propagator: a pmx SpanContext, or undefined (none). */
|
|
561
|
+
extract (carrier) {
|
|
562
|
+
const prop = this.g && this.g.propagation
|
|
563
|
+
if (!prop || typeof prop.extract !== 'function') return undefined
|
|
564
|
+
try {
|
|
565
|
+
const ctx = prop.extract(ROOT, carrier, GETTER)
|
|
566
|
+
const o = ctx && ctx.getValue(SPAN_KEY)
|
|
567
|
+
if (!o) return null
|
|
568
|
+
const sc = o.spanContext()
|
|
569
|
+
return w3c.makeContext(sc.traceId, sc.spanId, (sc.traceFlags & 1) === 1,
|
|
570
|
+
sc.traceState && typeof sc.traceState.serialize === 'function' ? sc.traceState.serialize() || null : null,
|
|
571
|
+
w3c.contextBag(w3c.extract(carrier)), true)
|
|
572
|
+
} catch (_) {
|
|
573
|
+
return null
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** OTX-020 for fetch: OTel's undici instrumentation subscribes to the same channel. */
|
|
578
|
+
undiciCovered () {
|
|
579
|
+
try {
|
|
580
|
+
const ch = require('diagnostics_channel').channel('undici:request:create')
|
|
581
|
+
const subs = ch._subscribers
|
|
582
|
+
if (Array.isArray(subs) && subs.length > 1) {
|
|
583
|
+
this.suppress('undici')
|
|
584
|
+
return true
|
|
585
|
+
}
|
|
586
|
+
} catch (_) {}
|
|
587
|
+
return false
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// -- export (OTX-030…036) ---------------------------------------------------------------------
|
|
591
|
+
|
|
592
|
+
setting () {
|
|
593
|
+
const v = String(process.env.PMX_OTEL_BRIDGE || this.env.bridge || 'auto').toLowerCase()
|
|
594
|
+
return ['auto', 'receiver', 'processor', 'off'].includes(v) ? v : 'auto'
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** OTX-031 / OTX-051: does the app SDK's own exporter reach prodman (directly or via the receiver)? */
|
|
598
|
+
receiverInUse () {
|
|
599
|
+
const env = process.env
|
|
600
|
+
const ep = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || env.OTEL_EXPORTER_OTLP_ENDPOINT || ''
|
|
601
|
+
const exp = env.OTEL_TRACES_EXPORTER
|
|
602
|
+
const otlp = !exp || exp.split(',').map((s) => s.trim()).includes('otlp')
|
|
603
|
+
if (!otlp || !ep) return false
|
|
604
|
+
if (env.PMX_OTLP_ENDPOINT && ep.startsWith(env.PMX_OTLP_ENDPOINT)) return true
|
|
605
|
+
let prodman = this.env.prodmanUrl
|
|
606
|
+
if (env.PMX_IO_STANDALONE === '1' || prodman) {
|
|
607
|
+
try {
|
|
608
|
+
prodman = require('../standalone/transport').resolveUrl(prodman, env)
|
|
609
|
+
} catch (_) {}
|
|
610
|
+
}
|
|
611
|
+
if (prodman && ep.startsWith(prodman.replace(/\/+$/, '') + '/otlp')) return true
|
|
612
|
+
return false
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
start () {
|
|
616
|
+
const setting = this.setting()
|
|
617
|
+
if (setting === 'receiver' || (setting !== 'processor' && this.receiverInUse())) {
|
|
618
|
+
this.exportPath = 'receiver'
|
|
619
|
+
} else if ((setting === 'processor' || setting === 'auto') && this.env.tracing !== false) {
|
|
620
|
+
this.exportPath = this.attachProcessor() ? 'processor' : 'none'
|
|
621
|
+
} else {
|
|
622
|
+
this.exportPath = 'none'
|
|
623
|
+
}
|
|
624
|
+
const t = this.tracer
|
|
625
|
+
const ignored = []
|
|
626
|
+
if (t.opts.sampleRatio !== undefined) ignored.push('sampleRatio')
|
|
627
|
+
if (t.opts.minDurationUs !== undefined) ignored.push('minDurationUs')
|
|
628
|
+
if (t.opts.maxSpansPerSecond !== undefined && this.exportPath !== 'processor') ignored.push('maxSpansPerSecond')
|
|
629
|
+
if (t.opts.exportUnsampledErrors !== undefined) ignored.push('exportUnsampledErrors')
|
|
630
|
+
if (ignored.length) debug('OpenTelemetry SDK decides sampling: ignoring %s', ignored.join(', '))
|
|
631
|
+
const line = `[@pmx/io] OpenTelemetry SDK detected (${this.sdk}): pmx spans are created through it; export: ${this.exportPath}`
|
|
632
|
+
debug(line)
|
|
633
|
+
if (this.env.debugOut) {
|
|
634
|
+
try { this.env.debugOut(line) } catch (_) {}
|
|
635
|
+
}
|
|
636
|
+
this.report()
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
report () {
|
|
640
|
+
if (typeof this.env.report !== 'function') return
|
|
641
|
+
try {
|
|
642
|
+
this.env.report({ otel: { sdk: this.sdk, mode: 'bridge', export: this.exportPath, suppressed: Array.from(this.suppressed).sort() } })
|
|
643
|
+
} catch (_) {}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** OTX-032: add the processor to SDK 1.x (addSpanProcessor) or 2.x (the active processor's list). */
|
|
647
|
+
attachProcessor () {
|
|
648
|
+
const p = this.provider
|
|
649
|
+
const proc = new Processor(this)
|
|
650
|
+
try {
|
|
651
|
+
if (process.env.PMX_IO_TEST_OTEL_NO_PRIVATE === '1') throw new Error('test hook: no private fields')
|
|
652
|
+
if (typeof p.addSpanProcessor === 'function') {
|
|
653
|
+
p.addSpanProcessor(proc)
|
|
654
|
+
} else if (p._activeSpanProcessor && Array.isArray(p._activeSpanProcessor._spanProcessors)) {
|
|
655
|
+
p._activeSpanProcessor._spanProcessors.push(proc)
|
|
656
|
+
} else {
|
|
657
|
+
throw new Error('no span processor list')
|
|
658
|
+
}
|
|
659
|
+
} catch (err) {
|
|
660
|
+
debug('cannot attach to this OpenTelemetry SDK; set OTEL_EXPORTER_OTLP_ENDPOINT to $PMX_OTLP_ENDPOINT to reach prodman (%s)', err && err.message)
|
|
661
|
+
return false
|
|
662
|
+
}
|
|
663
|
+
this.processor = proc
|
|
664
|
+
return true
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** The processor failed (OTX-019 isolation): stop using it. */
|
|
668
|
+
processorFailed (err) {
|
|
669
|
+
if (this.exportPath !== 'processor') return
|
|
670
|
+
debug('OpenTelemetry span processor disabled: %s', err && err.message)
|
|
671
|
+
this.exportPath = 'none'
|
|
672
|
+
if (this.processor) this.processor.disabled = true
|
|
673
|
+
this.report()
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function linkOf (el) {
|
|
678
|
+
if (w3c.isSpanContext(el)) return { context: el }
|
|
679
|
+
if (el && typeof el === 'object' && w3c.isSpanContext(el.context)) return { context: el.context, attributes: el.attributes }
|
|
680
|
+
if (el && typeof el.traceId === 'string' && typeof el.spanId === 'string' && el.context) return { context: el.context }
|
|
681
|
+
return null
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function sdkVersion (provider) {
|
|
685
|
+
try {
|
|
686
|
+
const res = provider.resource || provider._resource || (provider._config && provider._config.resource) ||
|
|
687
|
+
(provider._tracers && provider._tracers.size && Array.from(provider._tracers.values())[0]._resource)
|
|
688
|
+
const attrs = res && res.attributes
|
|
689
|
+
const v = attrs && attrs['telemetry.sdk.version']
|
|
690
|
+
return 'nodejs ' + (typeof v === 'string' ? v : 'unknown')
|
|
691
|
+
} catch (_) {
|
|
692
|
+
return 'nodejs unknown'
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ---------------------------------------------------------------------------------------------
|
|
697
|
+
// processor path (OTX-032/034)
|
|
698
|
+
|
|
699
|
+
function hrToUs (t) {
|
|
700
|
+
if (Array.isArray(t)) return t[0] * 1e6 + Math.round(t[1] / 1000)
|
|
701
|
+
if (typeof t === 'number') return Math.round(t * 1000)
|
|
702
|
+
if (t instanceof Date) return t.getTime() * 1000
|
|
703
|
+
return 0
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
class Processor {
|
|
707
|
+
constructor (bridge) {
|
|
708
|
+
this.bridge = bridge
|
|
709
|
+
this.disabled = false
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
onStart () {}
|
|
713
|
+
onEnding () {}
|
|
714
|
+
|
|
715
|
+
onEnd (span) {
|
|
716
|
+
if (this.disabled) return
|
|
717
|
+
try {
|
|
718
|
+
if (process.env.PMX_IO_TEST_OTEL_THROW === '1') throw new Error('test hook: onEnd throws')
|
|
719
|
+
const sc = span.spanContext()
|
|
720
|
+
if ((sc.traceFlags & 1) !== 1) return
|
|
721
|
+
this.bridge.tracer.exportConverted(toZipkin(span, this.bridge.tracer))
|
|
722
|
+
} catch (err) {
|
|
723
|
+
this.bridge.processorFailed(err)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
forceFlush () {
|
|
728
|
+
try {
|
|
729
|
+
this.bridge.tracer.flush()
|
|
730
|
+
} catch (_) {}
|
|
731
|
+
return Promise.resolve()
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
shutdown () {
|
|
735
|
+
return this.forceFlush()
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** OTX-034: a finished OTel span in the native Zipkin v2 shape (40 §7.11). */
|
|
740
|
+
function toZipkin (span, tracer) {
|
|
741
|
+
const { tagValue, encodeLinks } = require('../tracing/tracer')
|
|
742
|
+
const sc = span.spanContext()
|
|
743
|
+
const tags = {}
|
|
744
|
+
const attrs = span.attributes || {}
|
|
745
|
+
for (const k of Object.keys(attrs)) {
|
|
746
|
+
const v = attrs[k]
|
|
747
|
+
if (v === undefined || v === null) continue
|
|
748
|
+
tags[k] = tagValue(v)
|
|
749
|
+
}
|
|
750
|
+
const alias = (legacy, stable) => {
|
|
751
|
+
if (tags[legacy] === undefined && tags[stable] !== undefined) tags[legacy] = tags[stable]
|
|
752
|
+
}
|
|
753
|
+
alias('http.method', 'http.request.method')
|
|
754
|
+
alias('http.status_code', 'http.response.status_code')
|
|
755
|
+
alias('http.url', 'url.full')
|
|
756
|
+
alias('http.scheme', 'url.scheme')
|
|
757
|
+
if (tags['http.target'] === undefined && tags['url.path'] !== undefined) {
|
|
758
|
+
tags['http.target'] = tags['url.path'] + (tags['url.query'] !== undefined ? '?' + tags['url.query'] : '')
|
|
759
|
+
}
|
|
760
|
+
const status = span.status || {}
|
|
761
|
+
if (status.code === 1) tags['otel.status_code'] = 'OK'
|
|
762
|
+
else if (status.code === 2) {
|
|
763
|
+
tags['otel.status_code'] = 'ERROR'
|
|
764
|
+
tags.error = status.message || 'Error'
|
|
765
|
+
}
|
|
766
|
+
if (span.droppedAttributesCount) tags['otel.dropped_attributes_count'] = String(span.droppedAttributesCount)
|
|
767
|
+
if (span.droppedEventsCount) tags['otel.dropped_events_count'] = String(span.droppedEventsCount)
|
|
768
|
+
const links = (span.links || []).map((l) => ({
|
|
769
|
+
context: { traceId: l.context.traceId, spanId: l.context.spanId },
|
|
770
|
+
attributes: l.attributes && Object.keys(l.attributes).length ? l.attributes : undefined
|
|
771
|
+
}))
|
|
772
|
+
const enc = encodeLinks(links.slice(0, 128), Math.max(0, links.length - 128) + (span.droppedLinksCount || 0))
|
|
773
|
+
if (enc.value !== undefined) tags['pmx.links'] = enc.value
|
|
774
|
+
if (enc.dropped) tags['otel.dropped_links_count'] = String(enc.dropped)
|
|
775
|
+
const scope = span.instrumentationScope || span.instrumentationLibrary
|
|
776
|
+
if (scope && scope.name) tags['otel.scope.name'] = scope.name
|
|
777
|
+
if (scope && scope.version) tags['otel.scope.version'] = scope.version
|
|
778
|
+
const res = span.resource && span.resource.attributes ? span.resource.attributes : {}
|
|
779
|
+
for (const k of Object.keys(res)) {
|
|
780
|
+
if (tags[k] === undefined && res[k] !== undefined && res[k] !== null) tags[k] = tagValue(res[k])
|
|
781
|
+
}
|
|
782
|
+
tags['pmx.bridge'] = 'processor'
|
|
783
|
+
const out = { traceId: sc.traceId, id: sc.spanId }
|
|
784
|
+
const parentId = span.parentSpanId || (span.parentSpanContext && span.parentSpanContext.spanId)
|
|
785
|
+
if (parentId) out.parentId = parentId
|
|
786
|
+
out.name = span.name
|
|
787
|
+
const kind = KINDS[span.kind]
|
|
788
|
+
if (kind && kind !== 'INTERNAL') out.kind = kind
|
|
789
|
+
out.timestamp = hrToUs(span.startTime)
|
|
790
|
+
out.duration = Math.max(0, span.duration ? hrToUs(span.duration) : hrToUs(span.endTime) - out.timestamp)
|
|
791
|
+
out.localEndpoint = { serviceName: typeof res['service.name'] === 'string' ? res['service.name'] : tracer.serviceName }
|
|
792
|
+
out.tags = tags
|
|
793
|
+
if (span.events && span.events.length) out.annotations = span.events.map((e) => ({ timestamp: hrToUs(e.time), value: e.name }))
|
|
794
|
+
return out
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
module.exports = { detect, Bridge, toZipkin, ForeignSpan, API_KEY, SPAN_KEY, ROOT, BaseContext }
|