@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.
Files changed (67) hide show
  1. package/README.md +69 -2
  2. package/bin/pmx-io-node.js +143 -0
  3. package/index.d.ts +381 -0
  4. package/index.js +27 -0
  5. package/index.mjs +47 -0
  6. package/lib/actions.js +120 -0
  7. package/lib/activate.js +402 -0
  8. package/lib/application.js +106 -0
  9. package/lib/bun.js +155 -0
  10. package/lib/config.js +67 -0
  11. package/lib/constants.js +17 -0
  12. package/lib/debug.js +48 -0
  13. package/lib/esm-hooks.mjs +111 -0
  14. package/lib/exit.js +79 -0
  15. package/lib/features/metrics.js +409 -0
  16. package/lib/features/notify.js +358 -0
  17. package/lib/features/profiling.js +208 -0
  18. package/lib/hooks.js +151 -0
  19. package/lib/inbound.js +174 -0
  20. package/lib/integrations.js +372 -0
  21. package/lib/itg/amqplib.js +169 -0
  22. package/lib/itg/bullmq.js +141 -0
  23. package/lib/itg/common.js +657 -0
  24. package/lib/itg/dbcomment.js +114 -0
  25. package/lib/itg/index.js +103 -0
  26. package/lib/itg/ioredis.js +212 -0
  27. package/lib/itg/kafkajs.js +205 -0
  28. package/lib/itg/mongodb.js +322 -0
  29. package/lib/itg/mysql2.js +205 -0
  30. package/lib/itg/nats.js +531 -0
  31. package/lib/itg/noderedis.js +236 -0
  32. package/lib/itg/pg.js +253 -0
  33. package/lib/itg/pgqueue.js +305 -0
  34. package/lib/itg/postgres.js +252 -0
  35. package/lib/itg/redis-common.js +265 -0
  36. package/lib/itg/sql.js +302 -0
  37. package/lib/metrics/service.js +286 -0
  38. package/lib/metrics/types.js +342 -0
  39. package/lib/modules.js +260 -0
  40. package/lib/node-options.js +62 -0
  41. package/lib/otel/bridge.js +797 -0
  42. package/lib/otel/env.js +114 -0
  43. package/lib/outbound.js +362 -0
  44. package/lib/pmx.js +757 -0
  45. package/lib/relay/hub.js +279 -0
  46. package/lib/relay/transport.js +253 -0
  47. package/lib/standalone/activate.js +307 -0
  48. package/lib/standalone/capture.js +55 -0
  49. package/lib/standalone/container.js +909 -0
  50. package/lib/standalone/transport.js +1023 -0
  51. package/lib/tracing/context.js +90 -0
  52. package/lib/tracing/control.js +124 -0
  53. package/lib/tracing/exporter.js +115 -0
  54. package/lib/tracing/index.js +242 -0
  55. package/lib/tracing/logs.js +176 -0
  56. package/lib/tracing/options.js +105 -0
  57. package/lib/tracing/paths.js +34 -0
  58. package/lib/tracing/tracer.js +850 -0
  59. package/lib/tracing/w3c.js +415 -0
  60. package/lib/transport/ipc.js +97 -0
  61. package/lib/transport/ws.js +511 -0
  62. package/lib/util.js +109 -0
  63. package/package.json +48 -4
  64. package/preload.cjs +26 -0
  65. package/preload.mjs +25 -0
  66. package/standalone.cjs +16 -0
  67. package/standalone.mjs +17 -0
package/lib/pmx.js ADDED
@@ -0,0 +1,757 @@
1
+ // Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
2
+ 'use strict'
3
+
4
+ // The process-wide instrumentation instance: the @pm2/io-compatible public API
5
+ // (80 A.11, NIO-033, NIO-040..042) and its lifecycle (A.1, A.2, A.8, A.9).
6
+
7
+ const fs = require('fs')
8
+ const { normalizeConfig, decorate, DEFAULT_WIDGET } = require('./config')
9
+ const { MetricService, normalizeOpts } = require('./metrics/service')
10
+ const { MetricType, MetricMeasurements, Gauge, Counter, Meter, Histogram } = require('./metrics/types')
11
+ const { ActionService } = require('./actions')
12
+ const { IPCTransport } = require('./transport/ipc')
13
+ const { NotifyFeature } = require('./features/notify')
14
+ const { initDefaultMetrics } = require('./features/metrics')
15
+ const { resolveTracing } = require('./tracing/options')
16
+ const control = require('./tracing/control')
17
+ // loaded on first use: the propagation API is not needed before the app's first line (NIO-024)
18
+ let w3cModule = null
19
+ const w3c = new Proxy({}, { get: (_, k) => (w3cModule || (w3cModule = require('./tracing/w3c')))[k] })
20
+ const context = require('./tracing/context')
21
+ const { readPackageJsonSync, findPackageJson, isPlainObject, jsonCopy } = require('./util')
22
+ const { VERSION } = require('./constants')
23
+ const debug = require('./debug')('axm:main')
24
+
25
+ const STANDALONE_KEYS = ['PM2_PUBLIC_KEY', 'PM2_SECRET_KEY', 'PM2_APP_NAME']
26
+
27
+ const printed = new Set()
28
+ function printOnce (line) {
29
+ if (printed.has(line)) return
30
+ printed.add(line)
31
+ try {
32
+ process.stderr.write(line + '\n')
33
+ } catch (_) {}
34
+ }
35
+
36
+ const PUBLIC_METHODS = [
37
+ 'init', 'destroy', 'getConfig', 'notifyError', 'notify', 'metric', 'gauge', 'counter', 'meter',
38
+ 'histogram', 'metrics', 'probe', 'transpose', 'action', 'scopedAction', 'emit', 'onExit',
39
+ 'getTracer', 'initModule', 'expressErrorHandler', 'koaErrorHandler', 'configureModule',
40
+ // trace context propagation (80 B.12)
41
+ 'inject', 'extract', 'getBaggage', 'withBaggage', 'wrap', 'traceContext', 'consumerSpan'
42
+ ]
43
+
44
+ const REPORTED = Symbol.for('@pmx/io:reported')
45
+ // G.5: the app → daemon envelopes a relay may forward (anything else from a relay is ignored)
46
+ const RELAYED_TYPES = ['process:exception', 'human:event', 'trace-span', 'trace-spans']
47
+
48
+ function profilingEnabled (p) {
49
+ if (p === false) return false
50
+ if (p && typeof p === 'object' && p.implementation === 'none') return false
51
+ return true
52
+ }
53
+
54
+ function makeEntrypoint (io) {
55
+ return class Entrypoint {
56
+ constructor () {
57
+ io.init(this.conf())
58
+ this.io = io
59
+ this.onStart((err) => {
60
+ if (err) {
61
+ console.error(err)
62
+ process.exit(1)
63
+ return
64
+ }
65
+ this.sensors()
66
+ this.events()
67
+ this.actuators()
68
+ io.onExit((code, signal) => this.onStop(err, () => io.destroy(), code, signal))
69
+ io._sendRaw('ready')
70
+ })
71
+ }
72
+
73
+ onStart (cb) { // eslint-disable-line no-unused-vars
74
+ throw new Error('Entrypoint onStart() not specified')
75
+ }
76
+
77
+ onStop (err, cb, code, signal) { // eslint-disable-line no-unused-vars
78
+ return cb()
79
+ }
80
+
81
+ sensors () {}
82
+ events () {}
83
+ actuators () {}
84
+ conf () { return undefined }
85
+ }
86
+ }
87
+
88
+ class PMX {
89
+ get SpanContext () {
90
+ return w3c.SpanContext
91
+ }
92
+
93
+ get NEW_TRACE () {
94
+ return w3c.NEW_TRACE
95
+ }
96
+
97
+ constructor (options) {
98
+ options = options || {}
99
+ // Set by the preload: the process runs under pmx, so the IPC transport is always used.
100
+ this._managed = options.managed === true
101
+ // 'managed' (daemon root over IPC), 'relay' (hub relay), 'thread' (worker-thread relay),
102
+ // 'standalone' (standalone root), or null (not decided: IPC when under PM2, else inert or
103
+ // standalone per 80 NIO-260)
104
+ this._mode = options.managed === true ? 'managed' : (options.mode || null)
105
+ this._caps = Array.isArray(options.caps) ? options.caps : []
106
+ this._relayFactory = options.relayFactory || null
107
+ this._standalone = options.standalone || null
108
+ this._initialized = false
109
+ this._generation = 0
110
+ this._config = undefined
111
+ this._transport = null
112
+ this._metrics = null
113
+ this._actions = null
114
+ this._notify = null
115
+ this._profiling = null
116
+ this._tracing = null
117
+ // 80 NIO-298: the prodman layer (undefined until the first init reads PMX_IO_TRACING)
118
+ this._tracingLayer = undefined
119
+ this._appTracing = { value: undefined, has: false }
120
+ this._defaultMetrics = null
121
+ for (const m of PUBLIC_METHODS) this[m] = this[m].bind(this)
122
+ this.Entrypoint = makeEntrypoint(this)
123
+ this.MetricType = MetricType
124
+ this.MetricMeasurements = MetricMeasurements
125
+ this.Gauge = Gauge
126
+ this.Counter = Counter
127
+ this.Meter = Meter
128
+ this.Histogram = Histogram
129
+ this.version = VERSION
130
+ }
131
+
132
+ // ---------------------------------------------------------------- lifecycle
133
+
134
+ _createTransport (config) {
135
+ switch (this._mode) {
136
+ case 'managed':
137
+ return new IPCTransport()
138
+ case 'relay':
139
+ return this._relayFactory()
140
+ case 'thread': {
141
+ const { ThreadRelayTransport } = require('./relay/transport')
142
+ return new ThreadRelayTransport()
143
+ }
144
+ case 'standalone': {
145
+ // init({ standalone: true, apmOptions }) after activation (e.g. by the launcher)
146
+ const t = this._standalone.transport
147
+ if (config.apmOptions && typeof config.apmOptions === 'object') {
148
+ try {
149
+ const { resolveConfig } = require('./standalone/transport')
150
+ const cfg = resolveConfig(config.apmOptions, process.env, t.info, t.cfg)
151
+ if (!cfg.missing.length) t.reconfigure(cfg)
152
+ } catch (_) {}
153
+ }
154
+ this._standalone.instance = this
155
+ return t
156
+ }
157
+ default:
158
+ }
159
+ const underPM2 = process.env.pm_id !== undefined && typeof process.send === 'function'
160
+ if (underPM2) return new IPCTransport()
161
+ const present = STANDALONE_KEYS.filter((k) => process.env[k])
162
+ const wantStandalone = config.standalone === true || present.length === STANDALONE_KEYS.length || process.env.PMX_IO_STANDALONE === '1'
163
+ if (!wantStandalone && present.length > 0) {
164
+ const missing = STANDALONE_KEYS.filter((k) => !process.env[k])
165
+ printOnce(`[@pm2/io] not reporting to prodman: ${missing.join(', ')} not set (standalone mode needs PM2_PUBLIC_KEY, PM2_SECRET_KEY and PM2_APP_NAME)`)
166
+ }
167
+ if (wantStandalone) {
168
+ try {
169
+ const t = require('./standalone/activate').fromInit(this, config)
170
+ if (t) return t
171
+ } catch (err) {
172
+ debug('standalone activation failed: %s', err && err.message)
173
+ }
174
+ }
175
+ return new IPCTransport()
176
+ }
177
+
178
+ /** Root side of the relay protocol (hub connections and worker threads; 80 Part G.5). */
179
+ _onRelayMessage (msg) {
180
+ if (!msg || typeof msg !== 'object' || !this._initialized) return
181
+ const kind = msg.__pmx_relay__
182
+ if (kind === 'msg') {
183
+ const m = msg.m
184
+ if (!m || typeof m.type !== 'string' || !RELAYED_TYPES.includes(m.type)) return
185
+ if (m.type === 'trace-spans' && m.data && Array.isArray(m.data.spans) && this._tracing && this._tracing._sink) {
186
+ for (const span of m.data.spans) this._tracing._sink.add(span)
187
+ } else if (m.type === 'trace-span' && this._tracing && this._tracing._sink) this._tracing._sink.add(m.data)
188
+ else this._transport.send(m.type, m.data)
189
+ } else if (kind === 'metrics') {
190
+ if (Array.isArray(msg.c) && this._metrics) for (const item of msg.c) this._metrics.applyContribution(item)
191
+ } else if (kind === 'log') {
192
+ if (this._transport && typeof this._transport.onLogChunk === 'function' && typeof msg.d === 'string') {
193
+ this._transport.onLogChunk(msg.s === 'err' ? 'err' : 'out', msg.d)
194
+ }
195
+ }
196
+ }
197
+
198
+ _isRelay () {
199
+ return this._mode === 'relay' || this._mode === 'thread'
200
+ }
201
+
202
+ /** NIO-223: the reporter of exceptions frameworks turn into ≥ 500 responses. */
203
+ _errorsConsumer () {
204
+ return {
205
+ report: (st, status) => {
206
+ const err = st.error
207
+ if (!err || (typeof err === 'object' && err[REPORTED])) return
208
+ const req = st.req || {}
209
+ const info = st.info || {}
210
+ let path = req.path
211
+ let query = info.query
212
+ const url = req.url
213
+ if (typeof url === 'string') {
214
+ const q = url.indexOf('?')
215
+ if (path === undefined) path = q === -1 ? url : url.slice(0, q)
216
+ if (query === undefined && q !== -1) {
217
+ query = {}
218
+ try {
219
+ for (const [k, v] of new URLSearchParams(url.slice(q + 1))) query[k] = v
220
+ } catch (_) {}
221
+ }
222
+ }
223
+ const metadata = {
224
+ http: { url, method: req.method, path, route: st.route, query: query === undefined ? {} : query, params: info.params === undefined ? {} : info.params },
225
+ custom: { user: info.user }
226
+ }
227
+ const span = st.span
228
+ if (span && typeof span.traceId === 'string') {
229
+ metadata.trace_id = span.traceId
230
+ metadata.span_id = span.spanId
231
+ }
232
+ this._notify.notifyError(err, metadata, { status })
233
+ }
234
+ }
235
+ }
236
+
237
+ init (config) {
238
+ if (this._initialized) this._teardown(true)
239
+ const hasTracing = config !== null && typeof config === 'object' && Object.prototype.hasOwnProperty.call(config, 'tracing')
240
+ const appTracing = hasTracing ? config.tracing : undefined
241
+ config = normalizeConfig(config)
242
+ // NIO-080/NIO-201: environment sources, then the app's `tracing` key (kept when absent)
243
+ config.tracing = resolveTracing(appTracing, hasTracing)
244
+ this._appTracing = { value: appTracing, has: hasTracing }
245
+ // 80 NIO-298 / INJ-026: the prodman layer beats every other source
246
+ if (this._tracingLayer === undefined) this._tracingLayer = control.layerFromEnv()
247
+ control.applyLayer(config.tracing, this._tracingLayer)
248
+ this._config = config
249
+ const generation = ++this._generation
250
+
251
+ // 1. transport
252
+ const transport = this._createTransport(config)
253
+ // pmx 97 PAP-050: under a pmx daemon the daemon decides; standalone, init > env > default
254
+ const managed = this._mode === 'managed' || (this._mode !== 'standalone' && process.env.pm_id !== undefined)
255
+ require('./application').resolve(config.application, { managed, warn: printOnce })
256
+ if (typeof transport.setApplication === 'function') transport.setApplication()
257
+ transport.init()
258
+ this._transport = transport
259
+ if (transport.tracingLayer && typeof transport.tracingLayer === 'object') {
260
+ // standalone: the layer from the last handshake / prodman:settings (PROTO-161)
261
+ this._tracingLayer = transport.tracingLayer
262
+ config.tracing = control.applyLayer(resolveTracing(appTracing, hasTracing), this._tracingLayer)
263
+ }
264
+ control.exportLayer(this._tracingLayer)
265
+
266
+ const relay = this._isRelay()
267
+ // Work the app's first line does not need and that sends nothing the 40 §5.2 sequence puts
268
+ // before the app's code (80 NIO-024) runs 100 ms later: the process-level default metrics
269
+ // and the dependency report. Skipped when init()/destroy() came first.
270
+ const later = []
271
+ // 2. inspector session (connected on first use)
272
+ const profiling = !relay && profilingEnabled(config.profiling)
273
+ this._profiling = null
274
+
275
+ // 3. actions, metrics (990 ms push)
276
+ this._notify = new NotifyFeature(transport)
277
+ this._actions = new ActionService(transport, (err) => this.notifyError(err))
278
+ this._metrics = new MetricService(transport)
279
+ if (relay && typeof transport.sendContributions === 'function') {
280
+ this._metrics.setRelay((list) => transport.sendContributions(list))
281
+ if (!this._onExitFlush) {
282
+ // the last second of a relay's metrics (contributions are otherwise sent every 1000 ms)
283
+ this._onExitFlush = () => {
284
+ try {
285
+ if (this._metrics) this._metrics.flushContributions()
286
+ } catch (_) {}
287
+ }
288
+ process.on('exit', this._onExitFlush)
289
+ }
290
+ }
291
+ this._metrics.start()
292
+
293
+ // 5. features: exceptions, profiling, events, metrics, tracing, dependencies
294
+ if (config.catchExceptions !== false) {
295
+ if (this._mode === 'thread') this._notify.enableThreadCapture()
296
+ else this._notify.enableCapture()
297
+ transport.send('axm:option:configuration', { error: true })
298
+ }
299
+ if (profiling) {
300
+ // synchronous: 40 §5.2 puts the profiling options and actions before the app's code
301
+ const { ProfilingFeature } = require('./features/profiling')
302
+ this._profiling = new ProfilingFeature(transport, this._actions)
303
+ this._profiling.connect()
304
+ this._profiling.init()
305
+ }
306
+ // request metrics now (a request can arrive in the first event-loop turn); the
307
+ // process-level groups (event loop, V8, GC/rusage, fds, network) one tick later
308
+ this._defaultMetrics = initDefaultMetrics(this._metrics, config.metrics, { relay, later: (fn) => later.unshift(fn) })
309
+ if (this._defaultMetrics.http) transport.send('axm:option:configuration', { latency: true })
310
+ if (config.httpErrors !== false) {
311
+ const inbound = require('./inbound')
312
+ inbound.setConsumer('errors', this._errorsConsumer())
313
+ require('./modules').enableErrors()
314
+ if (process.versions.bun) require('./bun').install()
315
+ }
316
+ if (config.tracing && config.tracing.enabled === true) this._startTracing(config)
317
+
318
+ // 6. full configuration
319
+ this._initialized = true
320
+ if (!relay) {
321
+ // synchronous: 40 §5.2 sends the full configuration before the app's code runs
322
+ const pkg = readPackageJsonSync()
323
+ const report = Object.assign({}, config)
324
+ delete report.apmOptions
325
+ decorate(report, pkg, process.env.name !== undefined ? process.env.name : 'outside-pm2')
326
+ transport.send('axm:option:configuration', report)
327
+ this._reportTracing()
328
+ // 40 §8.6: prodman tracing values, applied live (IPC message; standalone: from settings)
329
+ if (typeof transport.onMessage === 'function') {
330
+ transport.onMessage((m) => {
331
+ if (m && typeof m === 'object' && m.type === 'pmx:tracing' && generation === this._generation) {
332
+ this._setTracingLayer(control.cleanLayer(m.data))
333
+ }
334
+ })
335
+ }
336
+ // A.8.4 / 40 §5.2: the dependency report comes after the app's code has started
337
+ later.push(() => this._reportDependencies(generation))
338
+ }
339
+ if (later.length) {
340
+ // a timer, not setImmediate: with an --import preload Node runs the entry point after an
341
+ // asynchronous step, so immediates would still run before the app's first line. The
342
+ // first metric push is 990 ms away (GC pauses of the first 100 ms are not measured).
343
+ const t = setTimeout(() => {
344
+ for (const fn of later) {
345
+ if (generation !== this._generation || !this._initialized) return
346
+ try {
347
+ fn()
348
+ } catch (err) {
349
+ debug('deferred initialisation step failed: %s', err && err.message)
350
+ }
351
+ }
352
+ }, 100)
353
+ if (t.unref) t.unref()
354
+ }
355
+ debug('initialised (transport %s)', transport.kind)
356
+ return this
357
+ }
358
+
359
+ _startTracing (config) {
360
+ try {
361
+ const { TracingFeature } = require('./tracing/index')
362
+ const batch = this._mode === 'managed' && this._caps.includes('trace-spans')
363
+ this._tracing = new TracingFeature(this._transport, { metrics: this._metrics, batch })
364
+ this._tracing.init(config, config.tracing)
365
+ this._notify.setTracing(true)
366
+ if (process.versions.bun) require('./bun').install()
367
+ } catch (err) {
368
+ console.error('[pmx] tracing disabled: ' + (err && err.message))
369
+ this._tracing = null
370
+ }
371
+ }
372
+
373
+ /** 80 NIO-298: replace the prodman layer and apply it at once (no restart). */
374
+ _setTracingLayer (layer) {
375
+ this._tracingLayer = control.cleanLayer(layer)
376
+ control.exportLayer(this._tracingLayer)
377
+ if (!this._initialized || this._isRelay()) return
378
+ const t = control.applyLayer(resolveTracing(this._appTracing.value, this._appTracing.has), this._tracingLayer)
379
+ this._config.tracing = t
380
+ const want = t.enabled === true
381
+ if (want && !this._tracing && this._drainingTracing && this._drainingTracing.resume()) {
382
+ // switched on again while the previous switch-off was still draining: keep that tracer
383
+ this._tracing = this._drainingTracing
384
+ this._drainingTracing = null
385
+ }
386
+ if (want && !this._tracing) {
387
+ this._startTracing(this._config)
388
+ } else if (!want && this._tracing) {
389
+ // NIO-298: in-flight spans finish and are exported; no new spans meanwhile
390
+ const f = this._tracing
391
+ this._tracing = null
392
+ this._drainingTracing = f
393
+ try {
394
+ f.drain(() => {
395
+ if (this._drainingTracing === f) this._drainingTracing = null
396
+ if (this._notify && !this._tracing) this._notify.setTracing(false)
397
+ })
398
+ } catch (err) {
399
+ debug('tracing stop failed: %s', err && err.message)
400
+ }
401
+ }
402
+ if (want && this._tracing) {
403
+ const tracer = this._tracing.getTracer()
404
+ const eff = control.report(t, {}, {})
405
+ if (tracer) {
406
+ tracer.opts = t
407
+ tracer.sampleRatio = eff.sample_ratio.value
408
+ if (tracer.db) tracer.db.comment = eff.db_comment.value
409
+ }
410
+ }
411
+ this._reportTracing()
412
+ }
413
+
414
+ /** PROTO-163: `tracing_effective` with the source of each value. */
415
+ _reportTracing () {
416
+ if (!this._transport || !this._config || this._isRelay()) return
417
+ const src = control.sources(this._appTracing.value, this._appTracing.has)
418
+ this._transport.send('axm:option:configuration', { tracing_effective: control.report(this._config.tracing, src, this._tracingLayer || {}) })
419
+ }
420
+
421
+ _reportDependencies (generation) {
422
+ const file = findPackageJson()
423
+ if (!file) return
424
+ fs.readFile(file, 'utf8', (err, text) => {
425
+ if (err || generation !== this._generation || !this._initialized) return
426
+ let pkg
427
+ try {
428
+ pkg = JSON.parse(text)
429
+ } catch (_) {
430
+ return
431
+ }
432
+ if (!isPlainObject(pkg.dependencies)) return
433
+ const out = {}
434
+ for (const name of Object.keys(pkg.dependencies)) out[name] = { version: pkg.dependencies[name] }
435
+ this._transport.send('application:dependencies', out)
436
+ })
437
+ }
438
+
439
+ destroy () {
440
+ this._teardown(false)
441
+ if (this._mode === 'standalone' && this._standalone) {
442
+ try {
443
+ this._standalone.destroy()
444
+ } catch (_) {}
445
+ }
446
+ }
447
+
448
+ _teardown (keepTransport) {
449
+ if (!this._initialized) return
450
+ this._initialized = false
451
+ this._generation++
452
+ const safe = (fn) => {
453
+ try {
454
+ fn()
455
+ } catch (err) {
456
+ debug('destroy step failed: %s', err && err.message)
457
+ }
458
+ }
459
+ safe(() => this._tracing && this._tracing.destroy())
460
+ safe(() => this._drainingTracing && this._drainingTracing.destroy())
461
+ this._drainingTracing = null
462
+ safe(() => this._defaultMetrics && this._defaultMetrics.destroy())
463
+ safe(() => this._metrics && this._metrics.destroy())
464
+ safe(() => this._profiling && this._profiling.destroy())
465
+ safe(() => this._notify && this._notify.destroy())
466
+ safe(() => this._actions && this._actions.destroy())
467
+ safe(() => require('./inbound').setConsumer('errors', null))
468
+ // the standalone root keeps its prodman connection across re-inits (destroyed by destroy())
469
+ if (this._mode !== 'standalone') safe(() => this._transport && this._transport.destroy())
470
+ this._tracing = this._defaultMetrics = this._metrics = this._profiling = null
471
+ this._notify = this._actions = null
472
+ }
473
+
474
+ getConfig () {
475
+ return this._config
476
+ }
477
+
478
+ _sendRaw (msg) {
479
+ if (this._transport) this._transport.sendRaw(msg)
480
+ else if (typeof process.send === 'function' && process.connected !== false) process.send(msg)
481
+ }
482
+
483
+ _send (type, data) {
484
+ if (this._transport && this._initialized) this._transport.send(type, data)
485
+ }
486
+
487
+ /** True while tracing is active (used by preload.mjs, NIO-083). */
488
+ _tracingEnabled () {
489
+ return Boolean(this._initialized && this._tracing && this._tracing.isEnabled())
490
+ }
491
+
492
+ // ---------------------------------------------------------------- metrics
493
+
494
+ _metricService () {
495
+ if (!this._metrics) {
496
+ console.trace('Tried to register a metric without initializing @pm2/io')
497
+ return null
498
+ }
499
+ return this._metrics
500
+ }
501
+
502
+ metric (opts) {
503
+ const s = this._metricService()
504
+ return s ? s.gauge(opts) : undefined
505
+ }
506
+
507
+ gauge (opts) {
508
+ return this.metric(opts)
509
+ }
510
+
511
+ counter (opts) {
512
+ const s = this._metricService()
513
+ return s ? s.counter(opts) : undefined
514
+ }
515
+
516
+ meter (opts) {
517
+ const s = this._metricService()
518
+ return s ? s.meter(opts) : undefined
519
+ }
520
+
521
+ histogram (opts) {
522
+ const s = this._metricService()
523
+ return s ? s.histogram(opts) : undefined
524
+ }
525
+
526
+ metrics (list) {
527
+ const s = this._metricService()
528
+ return s ? s.metrics(list) : []
529
+ }
530
+
531
+ /** Legacy: passive gauge (NIO-033). */
532
+ transpose (name, fn) {
533
+ let opts = normalizeOpts(name)
534
+ if (typeof fn === 'function') opts = Object.assign({}, opts, { value: fn })
535
+ else if (typeof opts.data === 'function') opts = Object.assign({}, opts, { value: opts.data })
536
+ return this.metric(opts)
537
+ }
538
+
539
+ /** Legacy probe() object (NIO-033). */
540
+ probe () {
541
+ return {
542
+ metric: (o) => this.metric(o),
543
+ gauge: (o) => this.metric(o),
544
+ counter: (o) => this.counter(o),
545
+ meter: (o) => this.meter(o),
546
+ histogram: (o) => this.histogram(o),
547
+ transpose: (n, f) => this.transpose(n, f)
548
+ }
549
+ }
550
+
551
+ // ---------------------------------------------------------------- actions
552
+
553
+ action (name, opts, handler) {
554
+ if (name !== null && typeof name === 'object' && opts === undefined && handler === undefined) {
555
+ handler = name.action
556
+ opts = name.options !== undefined ? name.options : name.opts
557
+ name = name.name
558
+ } else if (typeof opts === 'function' && handler === undefined) {
559
+ handler = opts
560
+ opts = undefined
561
+ }
562
+ if (!this._actions) return undefined
563
+ this._actions.register(name, opts, handler)
564
+ return undefined
565
+ }
566
+
567
+ scopedAction (name, handler) {
568
+ if (!this._actions) return undefined
569
+ this._actions.registerScoped(name, handler)
570
+ return undefined
571
+ }
572
+
573
+ // ---------------------------------------------------------------- errors & events
574
+
575
+ notifyError (err, context) {
576
+ if (!this._notify) return undefined
577
+ this._notify.notifyError(err, context)
578
+ return undefined
579
+ }
580
+
581
+ notify (err, context) {
582
+ return this.notifyError(err, context)
583
+ }
584
+
585
+ expressErrorHandler () {
586
+ if (!this._notify) return (err, req, res, next) => next(err)
587
+ return this._notify.expressErrorHandler()
588
+ }
589
+
590
+ koaErrorHandler () {
591
+ if (!this._notify) return async (ctx, next) => next()
592
+ return this._notify.koaErrorHandler()
593
+ }
594
+
595
+ emit (name, data) {
596
+ if (typeof name !== 'string') {
597
+ console.error('event name must be a string')
598
+ console.trace()
599
+ return undefined
600
+ }
601
+ if (data === null || typeof data !== 'object') {
602
+ console.error('event data must be an object')
603
+ console.trace()
604
+ return undefined
605
+ }
606
+ if (Array.isArray(data)) {
607
+ console.error('event data cannot be an array')
608
+ console.trace()
609
+ return undefined
610
+ }
611
+ let copy
612
+ try {
613
+ copy = jsonCopy(data)
614
+ } catch (err) {
615
+ console.log('Failed to serialize the event data ' + (err && err.message))
616
+ return undefined
617
+ }
618
+ copy = isPlainObject(copy) ? copy : {}
619
+ copy.__name = name
620
+ this._send('human:event', copy)
621
+ return undefined
622
+ }
623
+
624
+ onExit (fn) {
625
+ return require('./exit').onExit(fn)
626
+ }
627
+
628
+ // ---------------------------------------------------------------- tracing
629
+
630
+ getTracer () {
631
+ if (!this._tracing || !this._tracing.isEnabled()) return require('./tracing/tracer').noopTracer
632
+ return this._tracing.getTracer()
633
+ }
634
+
635
+ _activeTracer () {
636
+ return this._tracing && this._tracing.isEnabled() ? this._tracing.getTracer() : null
637
+ }
638
+
639
+ /** NIO-241. */
640
+ inject (carrier, options) {
641
+ try {
642
+ const o = options && typeof options === 'object' ? options : {}
643
+ let ctx = o.context
644
+ let bag
645
+ if (ctx === undefined || ctx === null) {
646
+ const store = context.current()
647
+ const span = store && store.span
648
+ if (!span || typeof span.traceId !== 'string') return carrier
649
+ // 85 OTX-012: the app's propagator in bridge mode
650
+ const b = this._activeTracer() && this._activeTracer().bridge
651
+ if (b && o.overwrite !== true && o.asBytes !== true && b.inject(carrier, span)) return carrier
652
+ ctx = span.context
653
+ bag = store.bag || span.bag
654
+ } else {
655
+ if (!w3c.isSpanContext(ctx)) {
656
+ if (ctx && w3c.isSpanContext(ctx.context)) ctx = ctx.context
657
+ else return carrier
658
+ }
659
+ bag = w3c.contextBag(ctx)
660
+ }
661
+ w3c.writeHeaders(carrier, w3c.headersFor(ctx, bag), o.overwrite === true, o.asBytes === true)
662
+ } catch (err) {
663
+ debug('inject failed: %s', err && err.message)
664
+ }
665
+ return carrier
666
+ }
667
+
668
+ /** NIO-242. */
669
+ extract (carrier) {
670
+ const t = this._activeTracer()
671
+ if (t && t.bridge) {
672
+ const ctx = t.bridge.extract(carrier)
673
+ if (ctx !== undefined) return ctx
674
+ }
675
+ return w3c.extract(carrier)
676
+ }
677
+
678
+ /** NIO-243. */
679
+ getBaggage () {
680
+ const store = context.current()
681
+ return w3c.bagObject(store && store.bag)
682
+ }
683
+
684
+ withBaggage (entries, fn) {
685
+ if (typeof fn !== 'function') throw new TypeError('withBaggage needs a function')
686
+ const store = context.current()
687
+ const bag = w3c.mergeBag((store && store.bag) || w3c.EMPTY_BAG, entries)
688
+ return context.run({ span: store && store.span, bag, server: store && store.server }, fn)
689
+ }
690
+
691
+ /** NIO-244. */
692
+ wrap (fn) {
693
+ return context.bind(fn)
694
+ }
695
+
696
+ /** NIO-245. */
697
+ traceContext () {
698
+ if (!this._activeTracer()) return {}
699
+ const span = context.currentSpan()
700
+ if (!span || typeof span.traceId !== 'string') return {}
701
+ return { trace_id: span.traceId, span_id: span.spanId, trace_sampled: span.sampled }
702
+ }
703
+
704
+ /** NIO-246. */
705
+ consumerSpan (carrier, options, fn) {
706
+ if (typeof options === 'function' && fn === undefined) {
707
+ fn = options
708
+ options = {}
709
+ }
710
+ const o = options && typeof options === 'object' ? options : {}
711
+ const tracer = this.getTracer()
712
+ const parent = w3c.extract(carrier) || w3c.NEW_TRACE
713
+ const attributes = Object.assign({}, o.attributes || {})
714
+ if (o.system !== undefined) attributes['messaging.system'] = o.system
715
+ if (o.destination !== undefined) attributes['messaging.destination.name'] = o.destination
716
+ attributes['messaging.operation'] = 'process'
717
+ const name = o.name || (o.destination !== undefined ? 'process ' + o.destination : 'process')
718
+ return tracer.startActiveSpan(name, { kind: 'consumer', parent, attributes }, fn)
719
+ }
720
+
721
+ // ---------------------------------------------------------------- modules
722
+
723
+ initModule (opts, cb) {
724
+ opts = isPlainObject(opts) ? opts : {}
725
+ if (opts.reference !== undefined) {
726
+ opts.name = opts.reference
727
+ delete opts.reference
728
+ }
729
+ opts.widget = Object.assign({}, DEFAULT_WIDGET, isPlainObject(opts.widget) ? opts.widget : {})
730
+ opts.isModule = true
731
+ const file = findPackageJson()
732
+ if (!file) throw new Error('Cannot find package.json for this module')
733
+ const pkg = JSON.parse(fs.readFileSync(file, 'utf8'))
734
+ opts.module_name = pkg.name
735
+ opts.module_version = pkg.version
736
+ opts.description = pkg.description
737
+ decorate(opts, pkg, pkg.name)
738
+ opts.module_name = pkg.name
739
+ if (typeof opts.module_version !== 'string') opts.module_version = String(pkg.version)
740
+ if (typeof opts.description !== 'string') opts.description = pkg.description === undefined ? '' : String(pkg.description)
741
+ if (this._transport) this._transport.send('axm:option:configuration', opts)
742
+ else this._sendRaw({ type: 'axm:option:configuration', data: opts })
743
+ if (typeof cb === 'function') {
744
+ cb(null, opts)
745
+ return undefined
746
+ }
747
+ return opts
748
+ }
749
+
750
+ /** Legacy: report module options. */
751
+ configureModule (opts) {
752
+ if (isPlainObject(opts)) this._send('axm:option:configuration', opts)
753
+ return undefined
754
+ }
755
+ }
756
+
757
+ module.exports = { PMX, PUBLIC_METHODS, REPORTED, printOnce }