@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
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 }
|