@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,176 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Log correlation (80 B.14, NIO-255..NIO-258): pino, winston and bunyan records written while a
|
|
5
|
+
// span is current carry `trace_id` and `span_id` (values the app supplied are never
|
|
6
|
+
// overwritten); console lines are prefixed only on request (`logCorrelation: { console: true }`).
|
|
7
|
+
// The libraries are patched when the app loads them (modules.js); the patches look up the
|
|
8
|
+
// current span at log time and do nothing while correlation is off.
|
|
9
|
+
|
|
10
|
+
const ctx = require('./context')
|
|
11
|
+
|
|
12
|
+
const state = { tracer: null, console: false, consoleRestore: null }
|
|
13
|
+
|
|
14
|
+
function current () {
|
|
15
|
+
if (!state.tracer) return null
|
|
16
|
+
const span = ctx.currentSpan()
|
|
17
|
+
if (!span || typeof span.traceId !== 'string' || typeof span.spanId !== 'string') return null
|
|
18
|
+
return span
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function copyStatics (to, from) {
|
|
22
|
+
for (const k of Object.getOwnPropertyNames(from)) {
|
|
23
|
+
if (k === 'length' || k === 'name' || k === 'prototype' || k === 'arguments' || k === 'caller') continue
|
|
24
|
+
try {
|
|
25
|
+
Object.defineProperty(to, k, Object.getOwnPropertyDescriptor(from, k))
|
|
26
|
+
} catch (_) {}
|
|
27
|
+
}
|
|
28
|
+
for (const s of Object.getOwnPropertySymbols(from)) {
|
|
29
|
+
try {
|
|
30
|
+
Object.defineProperty(to, s, Object.getOwnPropertyDescriptor(from, s))
|
|
31
|
+
} catch (_) {}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
Object.defineProperty(to, 'length', { value: from.length })
|
|
35
|
+
Object.defineProperty(to, 'name', { value: from.name })
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------------------------
|
|
40
|
+
// pino (NIO-255): a mixin merged with the app's own
|
|
41
|
+
|
|
42
|
+
function pinoOptions (opts) {
|
|
43
|
+
const o = Object.assign({}, opts)
|
|
44
|
+
const appMixin = typeof o.mixin === 'function' ? o.mixin : null
|
|
45
|
+
o.mixin = function pmxIoMixin (mergeObject, level, logger) {
|
|
46
|
+
const app = appMixin ? appMixin.apply(this, arguments) : undefined
|
|
47
|
+
const span = current()
|
|
48
|
+
if (!span) return app || {}
|
|
49
|
+
const out = { trace_id: span.traceId, span_id: span.spanId }
|
|
50
|
+
if (mergeObject && typeof mergeObject === 'object') {
|
|
51
|
+
if (mergeObject.trace_id !== undefined) delete out.trace_id
|
|
52
|
+
if (mergeObject.span_id !== undefined) delete out.span_id
|
|
53
|
+
}
|
|
54
|
+
return app && typeof app === 'object' ? Object.assign(out, app) : out
|
|
55
|
+
}
|
|
56
|
+
return o
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function patchPino (pino) {
|
|
60
|
+
if (typeof pino !== 'function') return undefined
|
|
61
|
+
const orig = pino
|
|
62
|
+
const wrapped = function pino () {
|
|
63
|
+
const args = Array.prototype.slice.call(arguments)
|
|
64
|
+
const first = args[0]
|
|
65
|
+
if (first === undefined || first === null) args[0] = pinoOptions({})
|
|
66
|
+
else if (typeof first === 'string') args[0] = pinoOptions({ name: first })
|
|
67
|
+
else if (typeof first === 'object' && typeof first.write === 'function') args.unshift(pinoOptions({}))
|
|
68
|
+
else if (typeof first === 'object') args[0] = pinoOptions(first)
|
|
69
|
+
return orig.apply(this, args)
|
|
70
|
+
}
|
|
71
|
+
copyStatics(wrapped, orig)
|
|
72
|
+
wrapped.pino = wrapped
|
|
73
|
+
wrapped.default = wrapped
|
|
74
|
+
return wrapped
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------------------------
|
|
78
|
+
// winston (NIO-256): a format placed before the app's formats
|
|
79
|
+
|
|
80
|
+
function patchWinston (winston) {
|
|
81
|
+
if (!winston || typeof winston.createLogger !== 'function' || !winston.format) return undefined
|
|
82
|
+
const orig = winston.createLogger
|
|
83
|
+
const correlate = winston.format((info) => {
|
|
84
|
+
const span = current()
|
|
85
|
+
if (span) {
|
|
86
|
+
if (info.trace_id === undefined) info.trace_id = span.traceId
|
|
87
|
+
if (info.span_id === undefined) info.span_id = span.spanId
|
|
88
|
+
}
|
|
89
|
+
return info
|
|
90
|
+
})
|
|
91
|
+
const createLogger = function createLogger (opts) {
|
|
92
|
+
const o = Object.assign({}, opts)
|
|
93
|
+
const appFormat = o.format || winston.format.json()
|
|
94
|
+
o.format = winston.format.combine(correlate(), appFormat)
|
|
95
|
+
return orig.call(this, o)
|
|
96
|
+
}
|
|
97
|
+
copyStatics(createLogger, orig)
|
|
98
|
+
const d = Object.getOwnPropertyDescriptor(winston, 'createLogger')
|
|
99
|
+
if (d && (d.writable || d.configurable)) {
|
|
100
|
+
Object.defineProperty(winston, 'createLogger', { value: createLogger, writable: true, enumerable: d.enumerable, configurable: true })
|
|
101
|
+
}
|
|
102
|
+
return undefined
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------------------------
|
|
106
|
+
// bunyan (NIO-257): records gain the fields before they are emitted
|
|
107
|
+
|
|
108
|
+
function patchBunyan (bunyan) {
|
|
109
|
+
const Logger = bunyan && bunyan.prototype && typeof bunyan.prototype._emit === 'function' ? bunyan : bunyan && bunyan.Logger
|
|
110
|
+
const proto = Logger && Logger.prototype
|
|
111
|
+
if (!proto || typeof proto._emit !== 'function' || proto._emit.__pmxWrapped) return undefined
|
|
112
|
+
const orig = proto._emit
|
|
113
|
+
const _emit = function _emit (rec) {
|
|
114
|
+
const span = current()
|
|
115
|
+
if (span && rec && typeof rec === 'object') {
|
|
116
|
+
if (rec.trace_id === undefined) rec.trace_id = span.traceId
|
|
117
|
+
if (rec.span_id === undefined) rec.span_id = span.spanId
|
|
118
|
+
}
|
|
119
|
+
return orig.apply(this, arguments)
|
|
120
|
+
}
|
|
121
|
+
Object.defineProperty(_emit, '__pmxWrapped', { value: true })
|
|
122
|
+
proto._emit = _emit
|
|
123
|
+
return undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------------------------
|
|
127
|
+
// console (NIO-258, opt-in)
|
|
128
|
+
|
|
129
|
+
function enableConsole () {
|
|
130
|
+
if (state.consoleRestore) return
|
|
131
|
+
const saved = {}
|
|
132
|
+
for (const m of ['log', 'info', 'warn', 'error', 'debug']) {
|
|
133
|
+
const orig = console[m]
|
|
134
|
+
if (typeof orig !== 'function') continue
|
|
135
|
+
saved[m] = orig
|
|
136
|
+
console[m] = function () {
|
|
137
|
+
const span = state.console ? current() : null
|
|
138
|
+
if (!span) return orig.apply(this, arguments)
|
|
139
|
+
const prefix = `trace_id=${span.traceId} span_id=${span.spanId}`
|
|
140
|
+
const args = Array.prototype.slice.call(arguments)
|
|
141
|
+
if (typeof args[0] === 'string') args[0] = prefix + ' ' + args[0]
|
|
142
|
+
else args.unshift(prefix)
|
|
143
|
+
return orig.apply(this, args)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
state.consoleRestore = () => {
|
|
147
|
+
for (const m of Object.keys(saved)) console[m] = saved[m]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
function integrations () {
|
|
154
|
+
return [
|
|
155
|
+
{ pkg: 'pino', for: 'tracing', files: { 'pino.js': patchPino } },
|
|
156
|
+
{ pkg: 'winston', for: 'tracing', files: { 'lib/winston.js': patchWinston } },
|
|
157
|
+
{ pkg: 'bunyan', for: 'tracing', files: { 'lib/bunyan.js': patchBunyan } }
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function enable (tracer) {
|
|
162
|
+
state.tracer = tracer
|
|
163
|
+
state.console = tracer.consoleCorrelation === true
|
|
164
|
+
if (state.console) enableConsole()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function disable () {
|
|
168
|
+
state.tracer = null
|
|
169
|
+
state.console = false
|
|
170
|
+
if (state.consoleRestore) {
|
|
171
|
+
state.consoleRestore()
|
|
172
|
+
state.consoleRestore = null
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = { integrations, enable, disable, patchPino, patchWinston, patchBunyan }
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Tracing options, their sources and their order (80 NIO-080, NIO-200, NIO-201; PYI-090a):
|
|
5
|
+
// defaults → env `io` (conf.tracing) → env `trace` (only `enabled`) → env PMX_TRACE_SAMPLE_RATIO,
|
|
6
|
+
// else OTEL_TRACES_SAMPLER_ARG for the trace-id-ratio samplers (only `sampleRatio`) → the app's
|
|
7
|
+
// init(config); env PMX_DB_COMMENT (env stage) sets `db.comment` (96 SVM-500). Objects combine
|
|
8
|
+
// key by key (`db`, `messaging`, `logCorrelation` too); an object that does not name `enabled`
|
|
9
|
+
// counts as `enabled: true`.
|
|
10
|
+
|
|
11
|
+
const debug = require('../debug')('axm:tracing')
|
|
12
|
+
|
|
13
|
+
const NESTED = ['db', 'messaging', 'logCorrelation']
|
|
14
|
+
|
|
15
|
+
function isObj (v) {
|
|
16
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof RegExp)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Apply one source to `out` (mutated). */
|
|
20
|
+
function applySource (out, src) {
|
|
21
|
+
if (src === true || src === false) {
|
|
22
|
+
out.enabled = src
|
|
23
|
+
return out
|
|
24
|
+
}
|
|
25
|
+
if (!isObj(src)) return out
|
|
26
|
+
if (!Object.prototype.hasOwnProperty.call(src, 'enabled')) out.enabled = true
|
|
27
|
+
for (const k of Object.keys(src)) {
|
|
28
|
+
const v = src[k]
|
|
29
|
+
if (v === undefined) continue
|
|
30
|
+
if (NESTED.includes(k) && isObj(v) && isObj(out[k])) {
|
|
31
|
+
out[k] = Object.assign({}, out[k], v)
|
|
32
|
+
} else if (NESTED.includes(k) && isObj(v)) {
|
|
33
|
+
out[k] = Object.assign({}, v)
|
|
34
|
+
} else {
|
|
35
|
+
out[k] = v
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (out.enabled !== true) out.enabled = out.enabled === true
|
|
39
|
+
return out
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function envIoConf (env) {
|
|
43
|
+
if (typeof env.io !== 'string') return undefined
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(env.io)
|
|
46
|
+
return parsed && isObj(parsed.conf) ? parsed.conf : undefined
|
|
47
|
+
} catch (err) {
|
|
48
|
+
debug('env io is not valid JSON: %s', err && err.message)
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function envRatio (env) {
|
|
54
|
+
let raw = env.PMX_TRACE_SAMPLE_RATIO
|
|
55
|
+
if (raw === undefined || raw === '') {
|
|
56
|
+
const sampler = String(env.OTEL_TRACES_SAMPLER || '').trim().toLowerCase()
|
|
57
|
+
if (sampler === 'traceidratio' || sampler === 'parentbased_traceidratio') raw = env.OTEL_TRACES_SAMPLER_ARG
|
|
58
|
+
}
|
|
59
|
+
if (raw === undefined || raw === '') return undefined
|
|
60
|
+
const n = Number(String(raw).trim())
|
|
61
|
+
if (String(raw).trim() === '' || !Number.isFinite(n)) {
|
|
62
|
+
debug('ignoring non-numeric sample ratio %s', raw)
|
|
63
|
+
return undefined
|
|
64
|
+
}
|
|
65
|
+
return n
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The environment's contribution (everything before the app's init). */
|
|
69
|
+
function fromEnv (env) {
|
|
70
|
+
env = env || process.env
|
|
71
|
+
const out = { enabled: false }
|
|
72
|
+
const conf = envIoConf(env)
|
|
73
|
+
if (conf && conf.tracing !== undefined) applySource(out, conf.tracing)
|
|
74
|
+
if (env.trace === 'true') out.enabled = true
|
|
75
|
+
else if (env.trace === 'false') out.enabled = false
|
|
76
|
+
const ratio = envRatio(env)
|
|
77
|
+
if (ratio !== undefined) out.sampleRatio = ratio
|
|
78
|
+
// 96 SVM-500: PMX_DB_COMMENT=on|off|force (key by key into `db`; `db: false` stays off)
|
|
79
|
+
const dbc = typeof env.PMX_DB_COMMENT === 'string' ? env.PMX_DB_COMMENT.trim() : ''
|
|
80
|
+
if (dbc && out.db !== false) out.db = Object.assign({}, isObj(out.db) ? out.db : {}, { comment: dbc })
|
|
81
|
+
return out
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The effective `tracing` object for an init(config): `hasKey` = the app's config names
|
|
86
|
+
* `tracing` (NIO-080: without the key, the environment's settings are kept).
|
|
87
|
+
*/
|
|
88
|
+
function resolveTracing (appValue, hasKey, env) {
|
|
89
|
+
const out = fromEnv(env)
|
|
90
|
+
if (hasKey) applySource(out, appValue)
|
|
91
|
+
return out
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** `children` (NIO-290): config key, else env io conf, default true; PMX_IO_CHILDREN=0 → false. */
|
|
95
|
+
function childrenEnabled (config, env) {
|
|
96
|
+
env = env || process.env
|
|
97
|
+
if (env.PMX_IO_CHILDREN === '0' || env.PMX_IO_CHILDREN === 'false') return false
|
|
98
|
+
if (config && config.children === false) return false
|
|
99
|
+
if (config && config.children === true) return true
|
|
100
|
+
const conf = envIoConf(env)
|
|
101
|
+
if (conf && conf.children === false) return false
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { resolveTracing, fromEnv, applySource, childrenEnabled, envIoConf }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Route-less SERVER span names (80 Part G.2): each path segment that looks like an identifier
|
|
5
|
+
// becomes `:id`; the result is cut to 200 characters.
|
|
6
|
+
|
|
7
|
+
const DIGITS = /^[0-9]+$/
|
|
8
|
+
const UUID = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/
|
|
9
|
+
const HEX16PLUS = /^[0-9A-Fa-f]{16,}$/
|
|
10
|
+
const LONG_TOKEN = /^[A-Za-z0-9_-]{20,}$/
|
|
11
|
+
const HAS_DIGIT = /[0-9]/
|
|
12
|
+
|
|
13
|
+
function isId (seg) {
|
|
14
|
+
if (DIGITS.test(seg) || UUID.test(seg) || HEX16PLUS.test(seg)) return true
|
|
15
|
+
if (LONG_TOKEN.test(seg) && HAS_DIGIT.test(seg)) return true
|
|
16
|
+
return seg.indexOf('@') !== -1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The request path without query string, normalised (G.2). */
|
|
20
|
+
function normalizePath (target) {
|
|
21
|
+
let path = String(target || '/')
|
|
22
|
+
const q = path.indexOf('?')
|
|
23
|
+
if (q !== -1) path = path.slice(0, q)
|
|
24
|
+
const out = path.split('/').map((seg) => (seg !== '' && isId(seg) ? ':id' : seg)).join('/')
|
|
25
|
+
return out.length > 200 ? out.slice(0, 200) : out
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function rawPath (target) {
|
|
29
|
+
const t = String(target || '/')
|
|
30
|
+
const q = t.indexOf('?')
|
|
31
|
+
return q === -1 ? t : t.slice(0, q)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { normalizePath, rawPath, isId }
|