@aidra/pmx-io 0.0.0-stage → 9.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -2
- package/bin/pmx-io-node.js +143 -0
- package/index.d.ts +381 -0
- package/index.js +27 -0
- package/index.mjs +47 -0
- package/lib/actions.js +120 -0
- package/lib/activate.js +402 -0
- package/lib/application.js +106 -0
- package/lib/bun.js +155 -0
- package/lib/config.js +67 -0
- package/lib/constants.js +17 -0
- package/lib/debug.js +48 -0
- package/lib/esm-hooks.mjs +111 -0
- package/lib/exit.js +79 -0
- package/lib/features/metrics.js +409 -0
- package/lib/features/notify.js +358 -0
- package/lib/features/profiling.js +208 -0
- package/lib/hooks.js +151 -0
- package/lib/inbound.js +174 -0
- package/lib/integrations.js +372 -0
- package/lib/itg/amqplib.js +169 -0
- package/lib/itg/bullmq.js +141 -0
- package/lib/itg/common.js +657 -0
- package/lib/itg/dbcomment.js +114 -0
- package/lib/itg/index.js +103 -0
- package/lib/itg/ioredis.js +212 -0
- package/lib/itg/kafkajs.js +205 -0
- package/lib/itg/mongodb.js +322 -0
- package/lib/itg/mysql2.js +205 -0
- package/lib/itg/nats.js +531 -0
- package/lib/itg/noderedis.js +236 -0
- package/lib/itg/pg.js +253 -0
- package/lib/itg/pgqueue.js +305 -0
- package/lib/itg/postgres.js +252 -0
- package/lib/itg/redis-common.js +265 -0
- package/lib/itg/sql.js +302 -0
- package/lib/metrics/service.js +286 -0
- package/lib/metrics/types.js +342 -0
- package/lib/modules.js +260 -0
- package/lib/node-options.js +62 -0
- package/lib/otel/bridge.js +797 -0
- package/lib/otel/env.js +114 -0
- package/lib/outbound.js +362 -0
- package/lib/pmx.js +757 -0
- package/lib/relay/hub.js +279 -0
- package/lib/relay/transport.js +253 -0
- package/lib/standalone/activate.js +307 -0
- package/lib/standalone/capture.js +55 -0
- package/lib/standalone/container.js +909 -0
- package/lib/standalone/transport.js +1023 -0
- package/lib/tracing/context.js +90 -0
- package/lib/tracing/control.js +124 -0
- package/lib/tracing/exporter.js +115 -0
- package/lib/tracing/index.js +242 -0
- package/lib/tracing/logs.js +176 -0
- package/lib/tracing/options.js +105 -0
- package/lib/tracing/paths.js +34 -0
- package/lib/tracing/tracer.js +850 -0
- package/lib/tracing/w3c.js +415 -0
- package/lib/transport/ipc.js +97 -0
- package/lib/transport/ws.js +511 -0
- package/lib/util.js +109 -0
- package/package.json +48 -4
- package/preload.cjs +26 -0
- package/preload.mjs +25 -0
- package/standalone.cjs +16 -0
- package/standalone.mjs +17 -0
package/README.md
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @aidra/pmx-io
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
In-process instrumentation for Node.js and Bun apps that report to
|
|
4
|
+
[prodman](https://github.com/westonpark/prodman): metrics, custom actions, exceptions, events,
|
|
5
|
+
logs, CPU and heap profiling, and distributed tracing with database and message-broker
|
|
6
|
+
integrations. It keeps the `@pm2/io` API, so code written for PM2.io works unchanged.
|
|
7
|
+
|
|
8
|
+
* **Run by pmx:** pmx injects this library into every app it starts; nothing to install. An
|
|
9
|
+
installed copy stays idle and `require` returns the injected instance.
|
|
10
|
+
* **Run by PM2 / pm2_prodman:** reports through the PM2 daemon, like `@pm2/io`.
|
|
11
|
+
* **Standalone** (a container's `node app.js`, an ECS task, a worker): connects to prodman
|
|
12
|
+
itself as a one-process agent.
|
|
13
|
+
|
|
14
|
+
Node.js 18 or later, or Bun 1.1 or later. No dependencies.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install @aidra/pmx-io
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Code that requires `@pm2/io` can keep doing so: install the package under that name.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install @pm2/io@npm:@aidra/pmx-io
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Standalone mode
|
|
29
|
+
|
|
30
|
+
Set the bucket keys (prodman: *Bucket settings → Connect servers*) and an app name:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
PRODMAN_URL=https://prodman.example.com
|
|
34
|
+
PM2_PUBLIC_KEY=<public key>
|
|
35
|
+
PM2_SECRET_KEY=<secret key>
|
|
36
|
+
PM2_APP_NAME=orders-api
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then load the library without changing code:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npx pmx-io-node run -- node server.js
|
|
43
|
+
node --require @aidra/pmx-io/standalone server.js
|
|
44
|
+
NODE_OPTIONS='--import @aidra/pmx-io/standalone.mjs' node server.mjs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
or require it first thing in the app:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
const io = require('@aidra/pmx-io')
|
|
51
|
+
io.init({ apmOptions: { sendLogs: true, application: 'shop' } })
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Settings, server names, containers and troubleshooting: prodman's documentation page
|
|
55
|
+
*Node.js apps without pmx* (`/docs/node-apm`).
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
const io = require('@aidra/pmx-io')
|
|
61
|
+
|
|
62
|
+
io.metric({ name: 'Queue length' }).set(12)
|
|
63
|
+
io.counter({ name: 'Jobs done' }).inc()
|
|
64
|
+
io.action('flush cache', (reply) => reply({ ok: true }))
|
|
65
|
+
io.notifyError(new Error('payment failed'), { custom: { orderId } })
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
TypeScript declarations are included (`index.d.ts`), a superset of `@pm2/io`'s.
|
|
69
|
+
|
|
70
|
+
Proprietary. © 2026 Mike Warriner.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
3
|
+
'use strict'
|
|
4
|
+
|
|
5
|
+
// The standalone launcher (80 NIO-261): `pmx-io-node run [--] <command> [args…]`. Never
|
|
6
|
+
// activates the library. Prepends the standalone preload to NODE_OPTIONS (plus `--import` of
|
|
7
|
+
// standalone.mjs when env trace=true and the Node runs module hooks), sets PMX_IO_STANDALONE=1
|
|
8
|
+
// and PMX_IO_ROOT_PID=<its pid>, inserts `--preload <abs>/standalone.cjs` after a `bun` command,
|
|
9
|
+
// then replaces itself with the command through process.execve (same pid: signals reach the app,
|
|
10
|
+
// its exit status is the app's). With PMX_IO_OTEL_EXPORT=1 it also sets the OTEL_* variables
|
|
11
|
+
// that point an app's own OpenTelemetry SDK at prodman (85 OTX-052, lib/otel/env.js).
|
|
12
|
+
// Without process.execve it spawns the command with inherited
|
|
13
|
+
// stdio, names itself PMX_IO_ROOT_PPID (the child becomes the root), forwards signals and exits
|
|
14
|
+
// with the child's status.
|
|
15
|
+
|
|
16
|
+
const path = require('path')
|
|
17
|
+
const fs = require('fs')
|
|
18
|
+
const { pathToFileURL } = require('url')
|
|
19
|
+
|
|
20
|
+
const PKG = path.resolve(__dirname, '..')
|
|
21
|
+
const CJS = path.join(PKG, 'standalone.cjs')
|
|
22
|
+
const MJS = path.join(PKG, 'standalone.mjs')
|
|
23
|
+
const FORWARDED = ['SIGTERM', 'SIGINT', 'SIGHUP', 'SIGQUIT', 'SIGUSR1', 'SIGUSR2']
|
|
24
|
+
const USAGE = 'usage: pmx-io-node run [--] <command> [args...]\n' +
|
|
25
|
+
' Runs <command> with @aidra/pmx-io in standalone mode (reports to prodman without pmx).\n' +
|
|
26
|
+
' Needs PM2_PUBLIC_KEY, PM2_SECRET_KEY and PM2_APP_NAME (and PRODMAN_URL) in the environment.\n'
|
|
27
|
+
|
|
28
|
+
function quote (p) {
|
|
29
|
+
return '"' + p.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Node ≥ 18.19 / ≥ 20.6 runs --import preloads (INJ-010 rule). */
|
|
33
|
+
function supportsImport () {
|
|
34
|
+
const [a, b] = process.versions.node.split('.').map(Number)
|
|
35
|
+
return (a === 18 && b >= 19) || (a === 20 && b >= 6) || a > 20
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isBun (cmd) {
|
|
39
|
+
const base = path.basename(cmd)
|
|
40
|
+
return base === 'bun' || base.endsWith('bun')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function which (cmd) {
|
|
44
|
+
if (cmd.includes('/')) return cmd
|
|
45
|
+
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
46
|
+
if (!dir) continue
|
|
47
|
+
const full = path.join(dir, cmd)
|
|
48
|
+
try {
|
|
49
|
+
fs.accessSync(full, fs.constants.X_OK)
|
|
50
|
+
if (fs.statSync(full).isFile()) return full
|
|
51
|
+
} catch (_) {}
|
|
52
|
+
}
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function prepare (argv, env) {
|
|
57
|
+
const tokens = [`--require ${quote(CJS)}`]
|
|
58
|
+
if (env.trace === 'true' && supportsImport()) tokens.push(`--import ${quote(pathToFileURL(MJS).href)}`)
|
|
59
|
+
const cur = env.NODE_OPTIONS || ''
|
|
60
|
+
if (!cur.includes(CJS)) env.NODE_OPTIONS = tokens.join(' ') + (cur ? ' ' + cur : '')
|
|
61
|
+
env.PMX_IO_STANDALONE = '1'
|
|
62
|
+
if (env.PMX_IO_OTEL_EXPORT === '1') {
|
|
63
|
+
// 85 OTX-052: wire the app's own OpenTelemetry SDK to prodman's OTLP endpoint (opt-in)
|
|
64
|
+
try {
|
|
65
|
+
require('../lib/otel/env').wireLauncher(env)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
process.stderr.write('pmx-io-node: OpenTelemetry wiring failed: ' + (err && err.message) + '\n')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const args = argv.slice()
|
|
71
|
+
if (isBun(args[0]) && !args.includes(CJS)) args.splice(1, 0, '--preload', CJS)
|
|
72
|
+
return args
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main (argv) {
|
|
76
|
+
let rest = argv.slice(2)
|
|
77
|
+
if (rest[0] !== 'run') {
|
|
78
|
+
process.stderr.write(USAGE)
|
|
79
|
+
return 2
|
|
80
|
+
}
|
|
81
|
+
rest = rest.slice(1)
|
|
82
|
+
if (rest[0] === '--') rest = rest.slice(1)
|
|
83
|
+
if (rest.length === 0) {
|
|
84
|
+
process.stderr.write(USAGE)
|
|
85
|
+
return 2
|
|
86
|
+
}
|
|
87
|
+
const env = Object.assign({}, process.env)
|
|
88
|
+
delete env.PMX_IO_ROOT_PPID
|
|
89
|
+
const args = prepare(rest, env)
|
|
90
|
+
const file = which(args[0])
|
|
91
|
+
if (!file) {
|
|
92
|
+
process.stderr.write(`pmx-io-node: ${args[0]}: command not found\n`)
|
|
93
|
+
return 127
|
|
94
|
+
}
|
|
95
|
+
if (typeof process.execve === 'function' && env.PMX_IO_LAUNCHER_NO_EXECVE !== '1') {
|
|
96
|
+
env.PMX_IO_ROOT_PID = String(process.pid)
|
|
97
|
+
try {
|
|
98
|
+
process.execve(file, args, env)
|
|
99
|
+
} catch (err) {
|
|
100
|
+
process.stderr.write(`pmx-io-node: cannot run ${args[0]}: ${err && err.message}\n`)
|
|
101
|
+
return 126
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// fallback: a parent process
|
|
105
|
+
delete env.PMX_IO_ROOT_PID
|
|
106
|
+
env.PMX_IO_ROOT_PPID = String(process.pid)
|
|
107
|
+
const { spawn } = require('child_process')
|
|
108
|
+
const child = spawn(file, args.slice(1), { stdio: 'inherit', env, argv0: args[0] })
|
|
109
|
+
for (const sig of FORWARDED) {
|
|
110
|
+
try {
|
|
111
|
+
process.on(sig, () => {
|
|
112
|
+
try {
|
|
113
|
+
child.kill(sig)
|
|
114
|
+
} catch (_) {}
|
|
115
|
+
})
|
|
116
|
+
} catch (_) {}
|
|
117
|
+
}
|
|
118
|
+
child.on('error', (err) => {
|
|
119
|
+
process.stderr.write(`pmx-io-node: cannot run ${args[0]}: ${err && err.message}\n`)
|
|
120
|
+
process.exit(126)
|
|
121
|
+
})
|
|
122
|
+
child.on('exit', (code, signal) => {
|
|
123
|
+
if (signal) {
|
|
124
|
+
const n = require('os').constants.signals[signal] || 1
|
|
125
|
+
if (process.pid === 1) process.exit(128 + n)
|
|
126
|
+
for (const sig of FORWARDED) process.removeAllListeners(sig)
|
|
127
|
+
try {
|
|
128
|
+
process.kill(process.pid, signal)
|
|
129
|
+
} catch (_) {}
|
|
130
|
+
setTimeout(() => process.exit(128 + n), 100)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
process.exit(code === null ? 1 : code)
|
|
134
|
+
})
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (require.main === module) {
|
|
139
|
+
const code = main(process.argv)
|
|
140
|
+
if (code !== null && code !== undefined) process.exitCode = code
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { main, prepare, isBun }
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
|
|
3
|
+
// Type declarations for @aidra/pmx-io. The surface is a superset of @pm2/io's public API
|
|
4
|
+
// (80 NIO-001), so `import io from '@pm2/io'` code type-checks against this package.
|
|
5
|
+
|
|
6
|
+
export type MetricMeasurement =
|
|
7
|
+
| 'min' | 'max' | 'sum' | 'count' | 'variance' | 'mean' | 'stddev'
|
|
8
|
+
| 'median' | 'p75' | 'p95' | 'p99' | 'p999' | 'ema'
|
|
9
|
+
|
|
10
|
+
export declare const MetricMeasurements: { readonly [K in MetricMeasurement]: K }
|
|
11
|
+
export declare const MetricType: {
|
|
12
|
+
readonly meter: 'meter'
|
|
13
|
+
readonly histogram: 'histogram'
|
|
14
|
+
readonly counter: 'counter'
|
|
15
|
+
readonly gauge: 'gauge'
|
|
16
|
+
readonly metric: 'metric'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface MetricOptions {
|
|
20
|
+
/** Metric name; the key in axm:monitor. */
|
|
21
|
+
name: string
|
|
22
|
+
/** Free-form type label, reported as `type`. */
|
|
23
|
+
id?: string
|
|
24
|
+
unit?: string
|
|
25
|
+
/** Whether history is kept (default true). */
|
|
26
|
+
historic?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GaugeOptions extends MetricOptions {
|
|
30
|
+
/** Passive gauge: evaluated at every push. */
|
|
31
|
+
value?: () => number | string | boolean | null | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CounterOptions extends MetricOptions {
|
|
35
|
+
count?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MeterOptions extends MetricOptions {
|
|
39
|
+
/** Rate unit in seconds (default 1). */
|
|
40
|
+
samples?: number
|
|
41
|
+
seconds?: number
|
|
42
|
+
/** Averaging window in seconds (default 60). */
|
|
43
|
+
timeframe?: number
|
|
44
|
+
/** Tick interval in ms (default 5000). */
|
|
45
|
+
tickInterval?: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface HistogramOptions extends MetricOptions {
|
|
49
|
+
measurement?: MetricMeasurement
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MetricBulkOptions extends GaugeOptions, CounterOptions, MeterOptions, HistogramOptions {
|
|
53
|
+
type?: 'counter' | 'gauge' | 'histogram' | 'meter' | 'metric'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export declare class Gauge {
|
|
57
|
+
set (value: number | string | boolean): void
|
|
58
|
+
val (): any
|
|
59
|
+
isUsed (): boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export declare class Counter {
|
|
63
|
+
inc (n?: number): void
|
|
64
|
+
dec (n?: number): void
|
|
65
|
+
reset (count?: number): void
|
|
66
|
+
val (): number
|
|
67
|
+
isUsed (): boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export declare class Meter {
|
|
71
|
+
mark (n?: number): void
|
|
72
|
+
val (): number
|
|
73
|
+
isUsed (): boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface HistogramResults {
|
|
77
|
+
min: number | null
|
|
78
|
+
max: number | null
|
|
79
|
+
sum: number
|
|
80
|
+
variance: number | null
|
|
81
|
+
mean: number
|
|
82
|
+
count: number
|
|
83
|
+
median: number | null
|
|
84
|
+
p75: number | null
|
|
85
|
+
p95: number | null
|
|
86
|
+
p99: number | null
|
|
87
|
+
p999: number | null
|
|
88
|
+
ema: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export declare class Histogram {
|
|
92
|
+
update (value: number): void
|
|
93
|
+
val (): number | null
|
|
94
|
+
percentiles (fractions: number[]): { [fraction: number]: number | null }
|
|
95
|
+
fullResults (): HistogramResults
|
|
96
|
+
getMin (): number | null
|
|
97
|
+
getMax (): number | null
|
|
98
|
+
getSum (): number
|
|
99
|
+
getCount (): number
|
|
100
|
+
getEma (): number
|
|
101
|
+
isUsed (): boolean
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface IOConfig {
|
|
105
|
+
catchExceptions?: boolean
|
|
106
|
+
profiling?: boolean | {
|
|
107
|
+
cpuJS?: boolean
|
|
108
|
+
heapSnapshot?: boolean
|
|
109
|
+
heapSampling?: boolean
|
|
110
|
+
implementation?: 'inspector' | 'addon' | 'both' | 'none'
|
|
111
|
+
}
|
|
112
|
+
metrics?: {
|
|
113
|
+
eventLoop?: boolean | { eventLoopActive?: boolean, eventLoopDelay?: boolean }
|
|
114
|
+
network?: boolean | { upload?: boolean, download?: boolean }
|
|
115
|
+
http?: boolean
|
|
116
|
+
/** `HTTP Outbound*` metrics (80 NIO-055), default true */
|
|
117
|
+
httpOutbound?: boolean
|
|
118
|
+
runtime?: boolean | { gcNewPause?: boolean, gcOldPause?: boolean, pageFaults?: boolean, contextSwitchs?: boolean }
|
|
119
|
+
v8?: boolean | { [key: string]: boolean }
|
|
120
|
+
fds?: boolean
|
|
121
|
+
}
|
|
122
|
+
actions?: { eventLoopDump?: boolean }
|
|
123
|
+
standalone?: boolean
|
|
124
|
+
/**
|
|
125
|
+
* The prodman application this process belongs to (pmx 97 PAP-050): 1-63 of `[a-z0-9._-]`,
|
|
126
|
+
* case-insensitive. Standalone only (init > `PMX_APPLICATION` > `default`); under pmx the
|
|
127
|
+
* daemon decides and a differing value is ignored with a warning.
|
|
128
|
+
*/
|
|
129
|
+
application?: string
|
|
130
|
+
apmOptions?: {
|
|
131
|
+
url?: string
|
|
132
|
+
publicKey?: string
|
|
133
|
+
secretKey?: string
|
|
134
|
+
appName?: string
|
|
135
|
+
serverName?: string
|
|
136
|
+
sendLogs?: boolean
|
|
137
|
+
disableLogs?: boolean
|
|
138
|
+
logFilter?: string | RegExp
|
|
139
|
+
/** standalone log capture (80 NIO-267), default 'stdio' */
|
|
140
|
+
captureLogs?: 'stdio' | 'off'
|
|
141
|
+
/** `http://[user:pass@]host:port` (default env PMX_IO_PROXY) */
|
|
142
|
+
proxy?: string
|
|
143
|
+
}
|
|
144
|
+
tracing?: boolean | TracingConfig
|
|
145
|
+
/** report exceptions that frameworks turn into ≥ 500 responses (80 NIO-223), default true */
|
|
146
|
+
httpErrors?: boolean
|
|
147
|
+
/** instrument Node descendants and worker threads through this process (80 NIO-290), default true */
|
|
148
|
+
children?: boolean
|
|
149
|
+
[key: string]: any
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type UrlMatcher = string | RegExp | ((url: string, request: any) => boolean)
|
|
153
|
+
|
|
154
|
+
export interface TracingConfig {
|
|
155
|
+
/** default true when the object is given */
|
|
156
|
+
enabled?: boolean
|
|
157
|
+
serviceName?: string
|
|
158
|
+
/** false: no CLIENT spans for outbound HTTP */
|
|
159
|
+
outbound?: boolean
|
|
160
|
+
/** accepted and ignored (A.6 quirk); use sampleRatio */
|
|
161
|
+
samplingRate?: number
|
|
162
|
+
ignoreIncomingPaths?: UrlMatcher[]
|
|
163
|
+
ignoreOutgoingUrls?: UrlMatcher[]
|
|
164
|
+
/** trace-id-ratio sampling of new traces, [0, 1], default 1 (80 NIO-205) */
|
|
165
|
+
sampleRatio?: number
|
|
166
|
+
/** default true (80 NIO-205) */
|
|
167
|
+
exportUnsampledErrors?: boolean
|
|
168
|
+
/** export rate limit, default 2000, 0 = unlimited (80 NIO-206) */
|
|
169
|
+
maxSpansPerSecond?: number
|
|
170
|
+
/** duration floor in µs, default 1000 (0 with NODE_ENV=test) */
|
|
171
|
+
minDurationUs?: number
|
|
172
|
+
db?: boolean | {
|
|
173
|
+
statement?: 'sanitized' | 'raw' | 'off',
|
|
174
|
+
poolSpans?: boolean,
|
|
175
|
+
/** Trace comment on SQL statements / MongoDB commands of sampled traces (default 'on'; env PMX_DB_COMMENT). */
|
|
176
|
+
comment?: 'on' | 'off' | 'force',
|
|
177
|
+
/** Set PostgreSQL application_name / MongoDB appName to the app name when the app set none (default true). */
|
|
178
|
+
applicationName?: boolean
|
|
179
|
+
}
|
|
180
|
+
messaging?: boolean | { rootProducers?: boolean, redisStreamFields?: boolean, payloadCarrier?: boolean, maxLinks?: number }
|
|
181
|
+
logCorrelation?: boolean | { console?: boolean }
|
|
182
|
+
baggageTags?: string[]
|
|
183
|
+
/** route-less SERVER span names normalised (80 Part G.2), default true */
|
|
184
|
+
normalizePaths?: boolean
|
|
185
|
+
createSpanWithNet?: boolean
|
|
186
|
+
detailedDatabasesCalls?: boolean
|
|
187
|
+
[key: string]: any
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** OpenTelemetry-compatible numbers are accepted too: 0 internal … 4 consumer. */
|
|
191
|
+
export type SpanKind = 'internal' | 'server' | 'client' | 'producer' | 'consumer' | string | 0 | 1 | 2 | 3 | 4
|
|
192
|
+
|
|
193
|
+
export type Carrier =
|
|
194
|
+
| { [name: string]: any }
|
|
195
|
+
| Map<string, any>
|
|
196
|
+
| Array<[string, any]>
|
|
197
|
+
| { get (name: string): any }
|
|
198
|
+
| { set (name: string, value: any): any, has (name: string): boolean }
|
|
199
|
+
|
|
200
|
+
/** The W3C trace context of a span (80 NIO-240). */
|
|
201
|
+
export declare class SpanContext {
|
|
202
|
+
constructor (opts: { traceId: string, spanId: string, sampled?: boolean, traceState?: string | null, baggage?: { [k: string]: string }, isRemote?: boolean })
|
|
203
|
+
readonly traceId: string
|
|
204
|
+
readonly spanId: string
|
|
205
|
+
readonly sampled: boolean
|
|
206
|
+
readonly traceState: string | null
|
|
207
|
+
readonly baggage: Readonly<{ [k: string]: string }>
|
|
208
|
+
readonly isRemote: boolean
|
|
209
|
+
traceparent (): string
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export type Link = SpanContext | { context: SpanContext, attributes?: { [k: string]: any } }
|
|
213
|
+
|
|
214
|
+
export interface SpanOptions {
|
|
215
|
+
kind?: SpanKind
|
|
216
|
+
attributes?: { [k: string]: any }
|
|
217
|
+
/** undefined: the current span (or a new trace); a Span; a SpanContext; or NEW_TRACE */
|
|
218
|
+
parent?: Span | SpanContext | typeof NEW_TRACE
|
|
219
|
+
links?: Link[]
|
|
220
|
+
/** ms since the epoch */
|
|
221
|
+
startTime?: number | Date
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface Span {
|
|
225
|
+
/** null for the no-op span (tracing disabled) */
|
|
226
|
+
readonly context: SpanContext | null
|
|
227
|
+
setAttribute (key: string, value: any): this
|
|
228
|
+
setAttributes (attributes: { [k: string]: any }): this
|
|
229
|
+
addEvent (name: string): this
|
|
230
|
+
addLink (context: SpanContext, attributes?: { [k: string]: any }): this
|
|
231
|
+
recordException (error: any): this
|
|
232
|
+
setStatus (status: 'OK' | 'ERROR' | 'UNSET' | string | { code: 0 | 1 | 2, message?: string }, message?: string): this
|
|
233
|
+
end (endTime?: number | Date): void
|
|
234
|
+
isRecording (): boolean
|
|
235
|
+
traceparent (): string | null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface Tracer {
|
|
239
|
+
readonly enabled: boolean
|
|
240
|
+
startSpan (name: string, options?: SpanOptions): Span
|
|
241
|
+
/** runs fn with the span current; the span ends when fn returns, throws or its promise settles */
|
|
242
|
+
startActiveSpan<T> (name: string, fn: (span: Span) => T): T
|
|
243
|
+
startActiveSpan<T> (name: string, options: SpanOptions, fn: (span: Span) => T): T
|
|
244
|
+
withSpan<T> (span: Span, fn: (span: Span) => T): T
|
|
245
|
+
currentSpan (): Span | undefined
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface InjectOptions {
|
|
249
|
+
context?: SpanContext
|
|
250
|
+
overwrite?: boolean
|
|
251
|
+
asBytes?: boolean
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface ConsumerSpanOptions {
|
|
255
|
+
name?: string
|
|
256
|
+
system?: string
|
|
257
|
+
destination?: string
|
|
258
|
+
attributes?: { [k: string]: any }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface TraceContextFields {
|
|
262
|
+
trace_id?: string
|
|
263
|
+
span_id?: string
|
|
264
|
+
trace_sampled?: boolean
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** As `parent`, starts a new trace even inside a span (80 NIO-247). */
|
|
268
|
+
export declare const NEW_TRACE: { readonly __newTrace?: never }
|
|
269
|
+
|
|
270
|
+
export type ActionReply = (data: any) => void
|
|
271
|
+
export type ActionHandler =
|
|
272
|
+
| ((reply: ActionReply) => any)
|
|
273
|
+
| ((params: any, reply: ActionReply) => any)
|
|
274
|
+
|
|
275
|
+
export interface ScopedActionStream {
|
|
276
|
+
send (data: any): void
|
|
277
|
+
error (data: any): void
|
|
278
|
+
end (data: any): void
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface ErrorContext {
|
|
282
|
+
http?: { [key: string]: any }
|
|
283
|
+
custom?: { [key: string]: any }
|
|
284
|
+
[key: string]: any
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export declare class Entrypoint {
|
|
288
|
+
io: PMX
|
|
289
|
+
constructor ()
|
|
290
|
+
onStart (cb: (err?: Error) => void): void
|
|
291
|
+
onStop (err: Error | undefined, cb: () => void, code: number | null, signal: string | null): void
|
|
292
|
+
sensors (): void
|
|
293
|
+
events (): void
|
|
294
|
+
actuators (): void
|
|
295
|
+
conf (): IOConfig | undefined
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface Probe {
|
|
299
|
+
metric (opts: GaugeOptions | string): Gauge | undefined
|
|
300
|
+
gauge (opts: GaugeOptions | string): Gauge | undefined
|
|
301
|
+
counter (opts: CounterOptions | string): Counter | undefined
|
|
302
|
+
meter (opts: MeterOptions | string): Meter | undefined
|
|
303
|
+
histogram (opts: HistogramOptions | string): Histogram | undefined
|
|
304
|
+
transpose (name: string | { name: string, data: () => any }, fn?: () => any): void
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export interface PMX {
|
|
308
|
+
init (config?: IOConfig): PMX
|
|
309
|
+
destroy (): void
|
|
310
|
+
getConfig (): IOConfig | undefined
|
|
311
|
+
notifyError (err: Error | any, context?: ErrorContext): void
|
|
312
|
+
/** Legacy alias of notifyError. */
|
|
313
|
+
notify (err: Error | any, context?: ErrorContext): void
|
|
314
|
+
metric (opts: GaugeOptions | string): Gauge | undefined
|
|
315
|
+
gauge (opts: GaugeOptions | string): Gauge | undefined
|
|
316
|
+
counter (opts: CounterOptions | string): Counter | undefined
|
|
317
|
+
meter (opts: MeterOptions | string): Meter | undefined
|
|
318
|
+
histogram (opts: HistogramOptions | string): Histogram | undefined
|
|
319
|
+
metrics (defs: MetricBulkOptions | MetricBulkOptions[]): any[]
|
|
320
|
+
probe (): Probe
|
|
321
|
+
transpose (name: string | { name: string, data: () => any }, fn?: () => any): void
|
|
322
|
+
action (name: string, handler: ActionHandler): void
|
|
323
|
+
action (name: string, opts: any, handler: ActionHandler): void
|
|
324
|
+
action (def: { name: string, options?: any, action: ActionHandler }): void
|
|
325
|
+
scopedAction (name: string, handler: (params: any, stream: ScopedActionStream) => any): void
|
|
326
|
+
emit (name: string, data: { [key: string]: any }): void
|
|
327
|
+
onExit (callback: (code: number | null, signal: string | null) => any): void
|
|
328
|
+
getTracer (): Tracer
|
|
329
|
+
inject<C extends Carrier> (carrier: C, options?: InjectOptions): C
|
|
330
|
+
extract (carrier: Carrier): SpanContext | null
|
|
331
|
+
getBaggage (): Readonly<{ [k: string]: string }>
|
|
332
|
+
withBaggage<T> (entries: { [k: string]: any }, fn: () => T): T
|
|
333
|
+
wrap<F extends (...args: any[]) => any> (fn: F): F
|
|
334
|
+
traceContext (): TraceContextFields
|
|
335
|
+
consumerSpan<T> (carrier: Carrier, options: ConsumerSpanOptions, fn: (span: Span) => T): T
|
|
336
|
+
SpanContext: typeof SpanContext
|
|
337
|
+
NEW_TRACE: typeof NEW_TRACE
|
|
338
|
+
initModule (opts?: any, cb?: (err: Error | null, conf: any) => void): any
|
|
339
|
+
configureModule (opts: { [key: string]: any }): void
|
|
340
|
+
expressErrorHandler (): (err: any, req: any, res: any, next: (err?: any) => void) => void
|
|
341
|
+
koaErrorHandler (): (ctx: any, next: () => Promise<any>) => Promise<void>
|
|
342
|
+
Entrypoint: typeof Entrypoint
|
|
343
|
+
MetricType: typeof MetricType
|
|
344
|
+
MetricMeasurements: typeof MetricMeasurements
|
|
345
|
+
version: string
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export declare function init (config?: IOConfig): PMX
|
|
349
|
+
export declare function destroy (): void
|
|
350
|
+
export declare function getConfig (): IOConfig | undefined
|
|
351
|
+
export declare function notifyError (err: Error | any, context?: ErrorContext): void
|
|
352
|
+
export declare function notify (err: Error | any, context?: ErrorContext): void
|
|
353
|
+
export declare function metric (opts: GaugeOptions | string): Gauge | undefined
|
|
354
|
+
export declare function gauge (opts: GaugeOptions | string): Gauge | undefined
|
|
355
|
+
export declare function counter (opts: CounterOptions | string): Counter | undefined
|
|
356
|
+
export declare function meter (opts: MeterOptions | string): Meter | undefined
|
|
357
|
+
export declare function histogram (opts: HistogramOptions | string): Histogram | undefined
|
|
358
|
+
export declare function metrics (defs: MetricBulkOptions | MetricBulkOptions[]): any[]
|
|
359
|
+
export declare function probe (): Probe
|
|
360
|
+
export declare function transpose (name: string | { name: string, data: () => any }, fn?: () => any): void
|
|
361
|
+
export declare function action (name: string, handler: ActionHandler): void
|
|
362
|
+
export declare function action (name: string, opts: any, handler: ActionHandler): void
|
|
363
|
+
export declare function action (def: { name: string, options?: any, action: ActionHandler }): void
|
|
364
|
+
export declare function scopedAction (name: string, handler: (params: any, stream: ScopedActionStream) => any): void
|
|
365
|
+
export declare function emit (name: string, data: { [key: string]: any }): void
|
|
366
|
+
export declare function onExit (callback: (code: number | null, signal: string | null) => any): void
|
|
367
|
+
export declare function getTracer (): Tracer
|
|
368
|
+
export declare function inject<C extends Carrier> (carrier: C, options?: InjectOptions): C
|
|
369
|
+
export declare function extract (carrier: Carrier): SpanContext | null
|
|
370
|
+
export declare function getBaggage (): Readonly<{ [k: string]: string }>
|
|
371
|
+
export declare function withBaggage<T> (entries: { [k: string]: any }, fn: () => T): T
|
|
372
|
+
export declare function wrap<F extends (...args: any[]) => any> (fn: F): F
|
|
373
|
+
export declare function traceContext (): TraceContextFields
|
|
374
|
+
export declare function consumerSpan<T> (carrier: Carrier, options: ConsumerSpanOptions, fn: (span: Span) => T): T
|
|
375
|
+
export declare function initModule (opts?: any, cb?: (err: Error | null, conf: any) => void): any
|
|
376
|
+
export declare function configureModule (opts: { [key: string]: any }): void
|
|
377
|
+
export declare function expressErrorHandler (): (err: any, req: any, res: any, next: (err?: any) => void) => void
|
|
378
|
+
export declare function koaErrorHandler (): (ctx: any, next: () => Promise<any>) => Promise<void>
|
|
379
|
+
|
|
380
|
+
declare const io: PMX
|
|
381
|
+
export default io
|
package/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Public entry (CommonJS). The module value is the process-wide instance stored under
|
|
5
|
+
// Symbol.for('@pm2/io') (80 IO-001): when an instance already exists (the one injected by
|
|
6
|
+
// pmx, or any compatible copy) it is returned and nothing new is created; otherwise a new
|
|
7
|
+
// instance is created, initialised with the default configuration and stored.
|
|
8
|
+
|
|
9
|
+
const SYMBOL = Symbol.for('@pm2/io')
|
|
10
|
+
|
|
11
|
+
let instance
|
|
12
|
+
if (Object.prototype.hasOwnProperty.call(globalThis, SYMBOL)) {
|
|
13
|
+
instance = globalThis[SYMBOL]
|
|
14
|
+
} else {
|
|
15
|
+
const { PMX } = require('./lib/pmx')
|
|
16
|
+
instance = new PMX()
|
|
17
|
+
Object.defineProperty(globalThis, SYMBOL, { value: instance, writable: true, configurable: true, enumerable: false })
|
|
18
|
+
try {
|
|
19
|
+
instance.init()
|
|
20
|
+
} catch (err) {
|
|
21
|
+
try {
|
|
22
|
+
process.stderr.write('[@pm2/io] initialisation failed: ' + (err && err.message) + '\n')
|
|
23
|
+
} catch (_) {}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = instance
|
package/index.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
|
|
3
|
+
// Public entry (ES module): the default export is the process-wide instance (the same object
|
|
4
|
+
// as require('@aidra/pmx-io')); every API method is also a named export (80 NIO-002).
|
|
5
|
+
|
|
6
|
+
import { createRequire } from 'node:module'
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url)
|
|
9
|
+
const io = require('./index.js')
|
|
10
|
+
|
|
11
|
+
const call = (name) => (...args) => (typeof io[name] === 'function' ? io[name](...args) : undefined)
|
|
12
|
+
|
|
13
|
+
export default io
|
|
14
|
+
export const init = call('init')
|
|
15
|
+
export const destroy = call('destroy')
|
|
16
|
+
export const getConfig = call('getConfig')
|
|
17
|
+
export const notifyError = call('notifyError')
|
|
18
|
+
export const notify = call('notify')
|
|
19
|
+
export const metric = call('metric')
|
|
20
|
+
export const gauge = call('gauge')
|
|
21
|
+
export const counter = call('counter')
|
|
22
|
+
export const meter = call('meter')
|
|
23
|
+
export const histogram = call('histogram')
|
|
24
|
+
export const metrics = call('metrics')
|
|
25
|
+
export const probe = call('probe')
|
|
26
|
+
export const transpose = call('transpose')
|
|
27
|
+
export const action = call('action')
|
|
28
|
+
export const scopedAction = call('scopedAction')
|
|
29
|
+
export const emit = call('emit')
|
|
30
|
+
export const onExit = call('onExit')
|
|
31
|
+
export const getTracer = call('getTracer')
|
|
32
|
+
export const initModule = call('initModule')
|
|
33
|
+
export const expressErrorHandler = call('expressErrorHandler')
|
|
34
|
+
export const koaErrorHandler = call('koaErrorHandler')
|
|
35
|
+
export const configureModule = call('configureModule')
|
|
36
|
+
export const inject = call('inject')
|
|
37
|
+
export const extract = call('extract')
|
|
38
|
+
export const getBaggage = call('getBaggage')
|
|
39
|
+
export const withBaggage = call('withBaggage')
|
|
40
|
+
export const wrap = call('wrap')
|
|
41
|
+
export const traceContext = call('traceContext')
|
|
42
|
+
export const consumerSpan = call('consumerSpan')
|
|
43
|
+
export const SpanContext = io.SpanContext
|
|
44
|
+
export const NEW_TRACE = io.NEW_TRACE
|
|
45
|
+
export const Entrypoint = io.Entrypoint
|
|
46
|
+
export const MetricType = io.MetricType
|
|
47
|
+
export const MetricMeasurements = io.MetricMeasurements
|