@aidra/pmx-io 9.19.0 → 9.20.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/index.d.ts +6 -0
- package/lib/activate.js +4 -2
- package/lib/bun.js +13 -1
- package/lib/callmap.js +427 -0
- package/lib/esm-hooks.mjs +3 -3
- package/lib/inbound.js +19 -5
- package/lib/itg/common.js +89 -2
- package/lib/itg/ioredis.js +36 -23
- package/lib/itg/mongodb.js +37 -3
- package/lib/itg/mysql2.js +26 -20
- package/lib/itg/noderedis.js +20 -13
- package/lib/itg/pg.js +19 -15
- package/lib/itg/postgres.js +17 -13
- package/lib/modules.js +18 -3
- package/lib/outbound.js +26 -8
- package/lib/pmx.js +55 -2
- package/lib/relay/transport.js +1 -1
- package/lib/standalone/transport.js +17 -0
- package/lib/tracing/control.js +11 -5
- package/package.json +1 -1
- package/preload.mjs +2 -1
package/lib/itg/ioredis.js
CHANGED
|
@@ -99,14 +99,19 @@ function startCommand (client, command) {
|
|
|
99
99
|
return
|
|
100
100
|
}
|
|
101
101
|
const t = c.dbTracer()
|
|
102
|
-
if (!
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
if (t === null && !c.mapOn()) return
|
|
103
|
+
let span = null
|
|
104
|
+
if (t !== null) {
|
|
105
|
+
const tags = dbTags(client)
|
|
106
|
+
tags['db.operation'] = name
|
|
107
|
+
const st = r.statement(name, args, t.db.statement)
|
|
108
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
109
|
+
span = c.clientSpan(t, name, tags)
|
|
110
|
+
}
|
|
111
|
+
// 83 CMP-003: with the call map on every command is recorded (`call` stands in for the span)
|
|
112
|
+
const call = c.dbCall(span, c.dbKeyOf(client, dbTags))
|
|
113
|
+
if (!call) return
|
|
114
|
+
onSettle(command, (v, err) => c.end(call, err, err ? r.errorCode(err) : undefined))
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
function mark (command) {
|
|
@@ -123,7 +128,7 @@ function queuedOffline (client, command) {
|
|
|
123
128
|
|
|
124
129
|
function wrapSendCommand (orig) {
|
|
125
130
|
return function sendCommand (command, stream) {
|
|
126
|
-
if (!command || typeof command !== 'object' || command[MARK] || stream ||
|
|
131
|
+
if (!command || typeof command !== 'object' || command[MARK] || stream || (c.tracer() === null && !c.mapOn())) return orig.apply(this, arguments)
|
|
127
132
|
if (this.status === 'connect') {
|
|
128
133
|
// status 'connect': the connection's own handshake (HELLO, AUTH, SELECT, CLIENT SETINFO)
|
|
129
134
|
// and ready check (INFO) are written now and make no span; a command of the app is queued
|
|
@@ -150,8 +155,13 @@ function wrapExec (orig) {
|
|
|
150
155
|
return function exec () {
|
|
151
156
|
const t = c.dbTracer()
|
|
152
157
|
const queue = this._queue
|
|
153
|
-
if (
|
|
158
|
+
if ((t === null && !c.mapOn()) || this[MARK] || !Array.isArray(queue) || queue.length === 0) return orig.apply(this, arguments)
|
|
159
|
+
// a span only under a current span (ITG-010); the call map counts the pipeline either way
|
|
160
|
+
// (CMP-012: one per pipeline, so its commands are marked and make no record of their own)
|
|
161
|
+
const traced = t !== null && Boolean(c.currentSpan())
|
|
162
|
+
if (!traced && !c.mapOn()) return orig.apply(this, arguments)
|
|
154
163
|
let span = null
|
|
164
|
+
let call = null
|
|
155
165
|
try {
|
|
156
166
|
Object.defineProperty(this, MARK, { value: true, configurable: true })
|
|
157
167
|
const multi = queue[0] && queue[0].name === 'multi'
|
|
@@ -160,29 +170,32 @@ function wrapExec (orig) {
|
|
|
160
170
|
try {
|
|
161
171
|
Object.defineProperty(cmd, MARK, { value: true, configurable: true })
|
|
162
172
|
} catch (_) {}
|
|
163
|
-
if (multi && (cmd.name === 'multi' || cmd.name === 'exec')) continue
|
|
173
|
+
if (!traced || (multi && (cmd.name === 'multi' || cmd.name === 'exec'))) continue
|
|
164
174
|
cmds.push([String(cmd.name || '').toUpperCase(), Array.isArray(cmd.args) ? cmd.args : []])
|
|
165
175
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
if (traced) {
|
|
177
|
+
const name = multi ? 'MULTI' : 'PIPELINE'
|
|
178
|
+
const tags = dbTags(this.redis)
|
|
179
|
+
tags['db.operation'] = name
|
|
180
|
+
const st = r.pipelineStatement(cmds, t.db.statement)
|
|
181
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
182
|
+
tags['db.batch_size'] = String(cmds.length)
|
|
183
|
+
span = c.clientSpan(t, name, tags)
|
|
184
|
+
}
|
|
185
|
+
call = c.dbCall(span, c.dbKeyOf(this.redis, dbTags))
|
|
173
186
|
} catch (_) {
|
|
174
|
-
|
|
187
|
+
call = span
|
|
175
188
|
}
|
|
176
189
|
let p
|
|
177
190
|
try {
|
|
178
191
|
p = orig.apply(this, arguments)
|
|
179
192
|
} catch (err) {
|
|
180
|
-
c.end(
|
|
193
|
+
c.end(call, err)
|
|
181
194
|
throw err
|
|
182
195
|
}
|
|
183
|
-
if (
|
|
184
|
-
p.then(() => c.end(
|
|
185
|
-
} else c.end(
|
|
196
|
+
if (call && c.isThenable(p)) {
|
|
197
|
+
p.then(() => c.end(call), (err) => c.end(call, err, r.errorCode(err)))
|
|
198
|
+
} else c.end(call)
|
|
186
199
|
return p
|
|
187
200
|
}
|
|
188
201
|
}
|
package/lib/itg/mongodb.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const crypto = require('crypto')
|
|
15
15
|
const c = require('./common')
|
|
16
16
|
const dbc = require('./dbcomment')
|
|
17
|
+
const callmap = require('../callmap')
|
|
17
18
|
|
|
18
19
|
const MAX_STATEMENT = 2000
|
|
19
20
|
const SKIPPED = new Set(['saslstart', 'saslcontinue', 'authenticate', 'getnonce', 'hello', 'ismaster',
|
|
@@ -131,7 +132,18 @@ function started (open, ev) {
|
|
|
131
132
|
const name = ev.commandName
|
|
132
133
|
if (SKIPPED.has(name.toLowerCase())) return
|
|
133
134
|
const t = c.dbTracer()
|
|
134
|
-
if (
|
|
135
|
+
if (t === null) {
|
|
136
|
+
// 83 CMP-003: tracing off, call map on: the command is recorded only
|
|
137
|
+
if (!c.mapOn()) return
|
|
138
|
+
const call = c.dbCall(null, mapKey(ev))
|
|
139
|
+
if (call) open.set(keyOf(ev), call)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
if (c.suppressedLib('mongodb')) {
|
|
143
|
+
const call = c.mapOn() ? c.dbCall(null, mapKey(ev)) : null
|
|
144
|
+
if (call) open.set(keyOf(ev), call)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
135
147
|
let command = ev.command || {}
|
|
136
148
|
// our trace comment (96 SVM-503): the span gets the id the comment carries, and the comment
|
|
137
149
|
// is not part of db.statement
|
|
@@ -156,8 +168,29 @@ function started (open, ev) {
|
|
|
156
168
|
span.spanId = plan.spanId
|
|
157
169
|
span._ctx = null
|
|
158
170
|
}
|
|
159
|
-
|
|
171
|
+
const call = c.dbCall(span, c.mapOn() ? mapKey(ev) : null)
|
|
172
|
+
if (call) open.set(keyOf(ev), call)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** CMP-012: (mongodb, host, port, database) of a command event. */
|
|
176
|
+
function mapKey (ev) {
|
|
177
|
+
const address = typeof ev.address === 'string' ? ev.address : ''
|
|
178
|
+
const db = ev.databaseName ? String(ev.databaseName) : null
|
|
179
|
+
let byDb = mapKeys.get(address)
|
|
180
|
+
if (byDb === undefined) {
|
|
181
|
+
if (mapKeys.size >= 1000) mapKeys.clear()
|
|
182
|
+
mapKeys.set(address, (byDb = new Map()))
|
|
183
|
+
}
|
|
184
|
+
let k = byDb.get(db)
|
|
185
|
+
if (k === undefined) {
|
|
186
|
+
const p = peer(address)
|
|
187
|
+
const port = p['net.peer.port'] !== undefined ? Number(p['net.peer.port']) : null
|
|
188
|
+
k = callmap.dbKey('mongodb', p['net.peer.name'] !== undefined ? p['net.peer.name'] : null, Number.isFinite(port) ? port : null, db)
|
|
189
|
+
byDb.set(db, k)
|
|
190
|
+
}
|
|
191
|
+
return k
|
|
160
192
|
}
|
|
193
|
+
const mapKeys = new Map()
|
|
161
194
|
|
|
162
195
|
function succeeded (open, ev) {
|
|
163
196
|
const k = keyOf(ev)
|
|
@@ -259,7 +292,8 @@ function proxyClient (MongoClient) {
|
|
|
259
292
|
construct (target, args, newTarget) {
|
|
260
293
|
let a = args
|
|
261
294
|
try {
|
|
262
|
-
|
|
295
|
+
// 83 CMP-003: command events are wanted for spans or for the call map
|
|
296
|
+
if (c.dbTracer() || c.mapOn()) {
|
|
263
297
|
const opts = args[1]
|
|
264
298
|
if (opts === undefined || opts === null || typeof opts === 'object') {
|
|
265
299
|
let next = opts || {}
|
package/lib/itg/mysql2.js
CHANGED
|
@@ -109,40 +109,46 @@ function comment (t, span, s, args, prepared) {
|
|
|
109
109
|
function wrapCall (op) {
|
|
110
110
|
return (orig) => function (s, values, cb) {
|
|
111
111
|
const t = c.dbTracer()
|
|
112
|
-
if (
|
|
112
|
+
if ((t === null && !c.mapOn()) || c.nested() || s === null || s === undefined) return orig.apply(this, arguments)
|
|
113
113
|
let span = null
|
|
114
114
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
if (t !== null) {
|
|
116
|
+
const text = textOf(s)
|
|
117
|
+
const base = connTags(this)
|
|
118
|
+
const operation = sql.operation(text)
|
|
119
|
+
const tags = Object.assign({}, base)
|
|
120
|
+
if (operation) tags['db.operation'] = operation
|
|
121
|
+
const st = sql.statementTag(text, t.db.statement, true)
|
|
122
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
123
|
+
const name = operation ? (base['db.name'] ? operation + ' ' + base['db.name'] : operation) : (base['db.name'] || 'mysql')
|
|
124
|
+
span = c.clientSpan(t, name, tags)
|
|
125
|
+
}
|
|
124
126
|
} catch (_) {
|
|
125
127
|
span = null
|
|
126
128
|
}
|
|
127
|
-
|
|
129
|
+
// 83 CMP-003: with the call map on every command is recorded (`call` stands in for the span)
|
|
130
|
+
const call = c.dbCall(span, c.dbKeyOf(this, connTags))
|
|
131
|
+
if (!call) return orig.apply(this, arguments)
|
|
128
132
|
const args = Array.prototype.slice.call(arguments)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
if (span) {
|
|
134
|
+
try {
|
|
135
|
+
comment(t, span, s, args, op === 'execute')
|
|
136
|
+
} catch (_) {}
|
|
137
|
+
// callbacks run later from the socket: give them the caller's context
|
|
138
|
+
for (let i = 1; i < args.length; i++) if (typeof args[i] === 'function') args[i] = c.bind(args[i])
|
|
139
|
+
}
|
|
134
140
|
let cmd
|
|
135
141
|
try {
|
|
136
142
|
cmd = c.outer(orig, this, args)
|
|
137
143
|
} catch (err) {
|
|
138
|
-
c.end(
|
|
144
|
+
c.end(call, err)
|
|
139
145
|
throw err
|
|
140
146
|
}
|
|
141
147
|
try {
|
|
142
|
-
if (cmd && typeof cmd.emit === 'function') observe(cmd,
|
|
143
|
-
else c.end(
|
|
148
|
+
if (cmd && typeof cmd.emit === 'function') observe(cmd, call)
|
|
149
|
+
else c.end(call)
|
|
144
150
|
} catch (_) {
|
|
145
|
-
c.end(
|
|
151
|
+
c.end(call)
|
|
146
152
|
}
|
|
147
153
|
return cmd
|
|
148
154
|
}
|
package/lib/itg/noderedis.js
CHANGED
|
@@ -62,14 +62,19 @@ function startCommand (client, redisArgs) {
|
|
|
62
62
|
return { extra: call.extra, done: (v, err) => call.done(v, err) }
|
|
63
63
|
}
|
|
64
64
|
const t = c.dbTracer()
|
|
65
|
-
if (!
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
if (t === null && !c.mapOn()) return null
|
|
66
|
+
let span = null
|
|
67
|
+
if (t !== null) {
|
|
68
|
+
const tags = dbTags(client)
|
|
69
|
+
tags['db.operation'] = name
|
|
70
|
+
const st = r.statement(name, args, t.db.statement)
|
|
71
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
72
|
+
span = c.clientSpan(t, name, tags)
|
|
73
|
+
}
|
|
74
|
+
// 83 CMP-003: with the call map on every command is recorded (`call` stands in for the span)
|
|
75
|
+
const call = c.dbCall(span, c.dbKeyOf(client, dbTags))
|
|
76
|
+
if (!call) return null
|
|
77
|
+
return { extra: [], done: (v, err) => c.end(call, err, err ? r.errorCode(err) : undefined) }
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
function finishWith (p, done) {
|
|
@@ -88,7 +93,7 @@ function finishWith (p, done) {
|
|
|
88
93
|
|
|
89
94
|
function wrapSendCommand (orig) {
|
|
90
95
|
return function sendCommand (args, options) {
|
|
91
|
-
if (
|
|
96
|
+
if ((c.tracer() === null && !c.mapOn()) || c.nested() || !Array.isArray(args)) return orig.apply(this, arguments)
|
|
92
97
|
let call = null
|
|
93
98
|
try {
|
|
94
99
|
call = startCommand(this, args)
|
|
@@ -117,7 +122,7 @@ function wrapSendCommand (orig) {
|
|
|
117
122
|
*/
|
|
118
123
|
function wrapCommandsExecutor (orig, transform, transformReply) {
|
|
119
124
|
return function commandsExecutor (command, args) {
|
|
120
|
-
if (
|
|
125
|
+
if ((c.tracer() === null && !c.mapOn()) || c.nested()) return orig.apply(this, arguments)
|
|
121
126
|
let call = null
|
|
122
127
|
let t = null
|
|
123
128
|
try {
|
|
@@ -155,7 +160,9 @@ function wrapCommandsExecutor (orig, transform, transformReply) {
|
|
|
155
160
|
|
|
156
161
|
function batchSpan (client, commands, multi) {
|
|
157
162
|
const t = c.dbTracer()
|
|
158
|
-
if (!
|
|
163
|
+
if (!Array.isArray(commands) || commands.length === 0) return null
|
|
164
|
+
// 83 CMP-003: with the call map on every batch is recorded (a Call stands in for the span)
|
|
165
|
+
if (t === null) return c.dbCall(null, c.dbKeyOf(client, dbTags))
|
|
159
166
|
const cmds = []
|
|
160
167
|
for (const cmd of commands) {
|
|
161
168
|
const a = Array.isArray(cmd) ? cmd : cmd && cmd.args
|
|
@@ -170,12 +177,12 @@ function batchSpan (client, commands, multi) {
|
|
|
170
177
|
const st = r.pipelineStatement(cmds, t.db.statement)
|
|
171
178
|
if (st !== undefined) tags['db.statement'] = st
|
|
172
179
|
tags['db.batch_size'] = String(cmds.length)
|
|
173
|
-
return c.clientSpan(t, name, tags)
|
|
180
|
+
return c.dbCall(c.clientSpan(t, name, tags), c.dbKeyOf(client, dbTags))
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
function wrapBatch (multiFromArgs) {
|
|
177
184
|
return (orig) => function (commands) {
|
|
178
|
-
if (
|
|
185
|
+
if ((c.tracer() === null && !c.mapOn()) || c.nested()) return orig.apply(this, arguments)
|
|
179
186
|
let span = null
|
|
180
187
|
try {
|
|
181
188
|
span = batchSpan(this, commands, multiFromArgs(arguments))
|
package/lib/itg/pg.js
CHANGED
|
@@ -88,25 +88,29 @@ function traceSubmittable (span, q) {
|
|
|
88
88
|
function wrapQuery (orig) {
|
|
89
89
|
return function query (config, values, callback) {
|
|
90
90
|
const t = c.dbTracer()
|
|
91
|
-
if (
|
|
91
|
+
if ((t === null && !c.mapOn()) || config === null || config === undefined || c.nested()) return orig.apply(this, arguments)
|
|
92
92
|
let span = null
|
|
93
93
|
let text
|
|
94
94
|
try {
|
|
95
95
|
if (typeof config === 'object') untag(config)
|
|
96
96
|
text = typeof config === 'string' ? config : config.text
|
|
97
97
|
if (typeof text !== 'string' && typeof config.submit === 'function') text = config.text || (config.cursor && config.cursor.text)
|
|
98
|
-
span = makeSpan(t, this, typeof text === 'string' ? text : '', typeof config === 'object' ? config.name : undefined)
|
|
98
|
+
if (t !== null) span = makeSpan(t, this, typeof text === 'string' ? text : '', typeof config === 'object' ? config.name : undefined)
|
|
99
99
|
} catch (_) {
|
|
100
100
|
span = null
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
// 83 CMP-003: with the call map on every query is recorded (`call` stands in for the span)
|
|
103
|
+
const call = c.dbCall(span, c.dbKeyOf(this, connTags))
|
|
104
|
+
if (!call) return orig.apply(this, arguments)
|
|
103
105
|
const args = Array.prototype.slice.call(arguments)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if (span) {
|
|
107
|
+
try {
|
|
108
|
+
comment(t, span, config, text, args)
|
|
109
|
+
} catch (_) {}
|
|
110
|
+
}
|
|
107
111
|
try {
|
|
108
112
|
if (typeof config.submit === 'function') {
|
|
109
|
-
traceSubmittable(
|
|
113
|
+
traceSubmittable(call, config)
|
|
110
114
|
return orig.apply(this, args)
|
|
111
115
|
}
|
|
112
116
|
// callback forms: (text, cb), (text, values, cb), (config, cb), config.callback
|
|
@@ -116,8 +120,8 @@ function wrapQuery (orig) {
|
|
|
116
120
|
if (cbIndex !== -1) {
|
|
117
121
|
const cb = c.bind(args[cbIndex])
|
|
118
122
|
args[cbIndex] = function (err, res) {
|
|
119
|
-
if (!err) rows(
|
|
120
|
-
c.end(
|
|
123
|
+
if (!err) rows(call)(res)
|
|
124
|
+
c.end(call, err || undefined, codeOf(err))
|
|
121
125
|
return cb.apply(this, arguments)
|
|
122
126
|
}
|
|
123
127
|
return orig.apply(this, args)
|
|
@@ -126,26 +130,26 @@ function wrapQuery (orig) {
|
|
|
126
130
|
const cb = c.bind(config.callback)
|
|
127
131
|
args[0] = Object.assign(Object.create(Object.getPrototypeOf(args[0])), args[0], {
|
|
128
132
|
callback (err, res) {
|
|
129
|
-
if (!err) rows(
|
|
130
|
-
c.end(
|
|
133
|
+
if (!err) rows(call)(res)
|
|
134
|
+
c.end(call, err || undefined, codeOf(err))
|
|
131
135
|
return cb.apply(this, arguments)
|
|
132
136
|
}
|
|
133
137
|
})
|
|
134
138
|
return orig.apply(this, args)
|
|
135
139
|
}
|
|
136
140
|
} catch (err) {
|
|
137
|
-
c.end(
|
|
141
|
+
c.end(call)
|
|
138
142
|
return orig.apply(this, arguments)
|
|
139
143
|
}
|
|
140
144
|
let result
|
|
141
145
|
try {
|
|
142
146
|
result = orig.apply(this, args)
|
|
143
147
|
} catch (err) {
|
|
144
|
-
c.end(
|
|
148
|
+
c.end(call, err, codeOf(err))
|
|
145
149
|
throw err
|
|
146
150
|
}
|
|
147
|
-
if (c.isThenable(result)) return c.settle(result,
|
|
148
|
-
c.end(
|
|
151
|
+
if (c.isThenable(result)) return c.settle(result, call, rows(call), codeOf)
|
|
152
|
+
c.end(call)
|
|
149
153
|
return result
|
|
150
154
|
}
|
|
151
155
|
}
|
package/lib/itg/postgres.js
CHANGED
|
@@ -70,7 +70,7 @@ function instrument (q, span, t) {
|
|
|
70
70
|
// the trace comment is never part of db.statement (96 SVM-504)
|
|
71
71
|
const prefix = q[PREFIX]
|
|
72
72
|
if (st !== null && prefix && st.startsWith(prefix)) st = st.slice(prefix.length)
|
|
73
|
-
if (st !== null) {
|
|
73
|
+
if (st !== null && t) {
|
|
74
74
|
const tag = sql.statementTag(st, t.db.statement, false)
|
|
75
75
|
if (tag !== undefined) span.setAttribute('db.statement', tag)
|
|
76
76
|
if (!span.attributes['db.operation']) {
|
|
@@ -145,24 +145,28 @@ function wrapHandle (orig) {
|
|
|
145
145
|
return function handle () {
|
|
146
146
|
if (this.executed || this[SPAN] !== undefined) return orig.apply(this, arguments)
|
|
147
147
|
const t = c.dbTracer()
|
|
148
|
-
if (t && !c.nested()) {
|
|
148
|
+
if ((t !== null || c.mapOn()) && !c.nested()) {
|
|
149
149
|
try {
|
|
150
150
|
const text = templateText(this)
|
|
151
151
|
const op = sql.operation(text)
|
|
152
152
|
const isTx = TX_OPS.has(op) || (op === 'PREPARE' && /^\s*prepare\s+transaction\b/i.test(text))
|
|
153
153
|
if (!isTx) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
instrument(this, span, t)
|
|
164
|
-
comment(this, t, span, text)
|
|
154
|
+
let span = null
|
|
155
|
+
if (t !== null) {
|
|
156
|
+
const base = optionTags(optionsOf(this))
|
|
157
|
+
const tags = Object.assign({}, base)
|
|
158
|
+
if (op) tags['db.operation'] = op
|
|
159
|
+
const st = sql.statementTag(text, t.db.statement, false)
|
|
160
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
161
|
+
const name = op ? (base['db.name'] ? op + ' ' + base['db.name'] : op) : (base['db.name'] || 'postgresql')
|
|
162
|
+
span = c.clientSpan(t, name, tags)
|
|
165
163
|
}
|
|
164
|
+
// 83 CMP-003: with the call map on every query is recorded (`call` stands in for the span)
|
|
165
|
+
const options = optionsOf(this)
|
|
166
|
+
const call = c.dbCall(span, c.dbKeyOf(options || null, optionTags))
|
|
167
|
+
this[SPAN] = call
|
|
168
|
+
if (call) instrument(this, call, t)
|
|
169
|
+
if (span) comment(this, t, span, text)
|
|
166
170
|
}
|
|
167
171
|
} catch (_) {}
|
|
168
172
|
}
|
package/lib/modules.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// * ES modules: hooks registered by preload.mjs when tracing is enabled at spawn (NIO-085,
|
|
13
13
|
// esm-hooks.mjs) replace a target module by a wrapper module that re-exports it with the
|
|
14
14
|
// patched exports (globalThis[Symbol.for('@pmx/io:esm')] below).
|
|
15
|
+
// The database integrations are also wanted while the call map is on (83 CMP-003).
|
|
15
16
|
// Integrations are declared in integrations.js: { pkg, files: { '<path in package>': patch } }.
|
|
16
17
|
// A patch receives (exports, info) and returns undefined (patched in place) or a replacement.
|
|
17
18
|
// Wrapping is transparent (82 ITG-003); a module that fails to patch is left untouched.
|
|
@@ -32,7 +33,7 @@ const PATCHED = Symbol.for('@pmx/io:patched')
|
|
|
32
33
|
function registry () {
|
|
33
34
|
let r = globalThis[REGISTRY]
|
|
34
35
|
if (!r) {
|
|
35
|
-
r = { installed: false, integrations: null, wants: { errors: false, tracing: false }, tracing: null, done: new Set() }
|
|
36
|
+
r = { installed: false, integrations: null, wants: { errors: false, tracing: false, callmap: false }, tracing: null, done: new Set() }
|
|
36
37
|
Object.defineProperty(globalThis, REGISTRY, { value: r, enumerable: false, configurable: true })
|
|
37
38
|
}
|
|
38
39
|
return r
|
|
@@ -73,7 +74,9 @@ function wanted (it) {
|
|
|
73
74
|
const r = registry()
|
|
74
75
|
const w = r.wants
|
|
75
76
|
if (it.for === 'db' || it.for === 'messaging' || it.for === 'db+messaging') {
|
|
76
|
-
// 82 ITG-001: database
|
|
77
|
+
// 82 ITG-001 / 83 CMP-003: database integrations with tracing and `tracing.db` on, or with the
|
|
78
|
+
// call map on; broker integrations with tracing and `tracing.messaging` on
|
|
79
|
+
if (it.for !== 'messaging' && w.callmap === true) return true
|
|
77
80
|
const t = w.tracing && r.tracing && r.tracing._tracer
|
|
78
81
|
if (!t) return false
|
|
79
82
|
return (it.for !== 'messaging' && Boolean(t.db)) || (it.for !== 'db' && Boolean(t.messaging))
|
|
@@ -249,6 +252,18 @@ function disableTracing () {
|
|
|
249
252
|
r.tracing = null
|
|
250
253
|
}
|
|
251
254
|
|
|
255
|
+
/** 83 CMP-003: the call map wants the database integrations (patched now when loaded already). */
|
|
256
|
+
function enableCallMap () {
|
|
257
|
+
const r = registry()
|
|
258
|
+
r.wants.callmap = true
|
|
259
|
+
install()
|
|
260
|
+
patchLoaded()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function disableCallMap () {
|
|
264
|
+
registry().wants.callmap = false
|
|
265
|
+
}
|
|
266
|
+
|
|
252
267
|
function disableErrors () {
|
|
253
268
|
registry().wants.errors = false
|
|
254
269
|
}
|
|
@@ -257,4 +272,4 @@ function wants () {
|
|
|
257
272
|
return registry().wants
|
|
258
273
|
}
|
|
259
274
|
|
|
260
|
-
module.exports = { TARGET_PKGS, install, enableErrors, enableTracing, disableTracing, disableErrors, packageOf, applyPatch, wants, registry, ESM_KEY }
|
|
275
|
+
module.exports = { TARGET_PKGS, install, enableErrors, enableTracing, disableTracing, enableCallMap, disableCallMap, disableErrors, packageOf, applyPatch, wants, registry, ESM_KEY }
|
package/lib/outbound.js
CHANGED
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
// https.request/get are wrapped instead, each when it is loaded (see below).
|
|
10
10
|
// * the global fetch: Node's undici diagnostics channels (`undici:request:*`, headers added with
|
|
11
11
|
// the request's addHeader); Bun: globalThis.fetch is wrapped.
|
|
12
|
-
// Requests made by @aidra/pmx-io itself never use these APIs (raw sockets, transport/ws.js)
|
|
12
|
+
// Requests made by @aidra/pmx-io itself never use these APIs (raw sockets, transport/ws.js), so
|
|
13
|
+
// the call map (83 CMP-010, consumer `callmap`) never counts them.
|
|
13
14
|
|
|
14
15
|
const hooks = require('./hooks')
|
|
15
16
|
const debug = require('./debug')('axm:tracing')
|
|
16
17
|
|
|
17
18
|
const OUT = Symbol.for('@pmx/io:outbound-state')
|
|
18
|
-
const consumers = { metrics: null, tracing: null }
|
|
19
|
+
const consumers = { metrics: null, tracing: null, callmap: null }
|
|
19
20
|
// resolved on first use: reading globalThis.performance loads the perf_hooks internals (NIO-024)
|
|
20
21
|
let perf = null
|
|
21
22
|
function pnow () {
|
|
@@ -43,8 +44,13 @@ function hasErrorChannel () {
|
|
|
43
44
|
// ---------------------------------------------------------------------------------------------
|
|
44
45
|
// shared start/end
|
|
45
46
|
|
|
46
|
-
function startState (canInject, info, inject) {
|
|
47
|
-
const st = { t0: pnow(), span: null, ended: false }
|
|
47
|
+
function startState (canInject, info, inject, keyOf) {
|
|
48
|
+
const st = { t0: pnow(), span: null, ended: false, key: null }
|
|
49
|
+
if (consumers.callmap !== null && keyOf) {
|
|
50
|
+
try {
|
|
51
|
+
st.key = keyOf()
|
|
52
|
+
} catch (_) {}
|
|
53
|
+
}
|
|
48
54
|
const t = consumers.tracing
|
|
49
55
|
if (t && canInject) {
|
|
50
56
|
try {
|
|
@@ -63,12 +69,18 @@ function startState (canInject, info, inject) {
|
|
|
63
69
|
function endState (st, status, err) {
|
|
64
70
|
if (!st || st.ended) return
|
|
65
71
|
st.ended = true
|
|
72
|
+
const ms = pnow() - st.t0
|
|
66
73
|
const m = consumers.metrics
|
|
67
74
|
if (m) {
|
|
68
75
|
try {
|
|
69
|
-
m(
|
|
76
|
+
m(ms)
|
|
70
77
|
} catch (_) {}
|
|
71
78
|
}
|
|
79
|
+
const cm = consumers.callmap
|
|
80
|
+
if (cm !== null && st.key !== null) {
|
|
81
|
+
// CMP-010: an error is no response or a status ≥ 500
|
|
82
|
+
cm.record(st.key, ms, err !== undefined && err !== null ? true : (typeof status !== 'number' || status >= 500))
|
|
83
|
+
}
|
|
72
84
|
if (st.span && consumers.tracing) {
|
|
73
85
|
try {
|
|
74
86
|
consumers.tracing.endClient(st.span, status, err)
|
|
@@ -113,6 +125,12 @@ function onClientCreated (req, canInject) {
|
|
|
113
125
|
if (req.getHeader(name) === undefined) req.setHeader(name, value)
|
|
114
126
|
} catch (_) { /* headers already sent (array form) */ }
|
|
115
127
|
}
|
|
128
|
+
}, () => {
|
|
129
|
+
let authority
|
|
130
|
+
try {
|
|
131
|
+
authority = req.getHeader('host')
|
|
132
|
+
} catch (_) {}
|
|
133
|
+
return consumers.callmap.httpOutKey(req.protocol || 'http:', req.host || 'localhost', authority)
|
|
116
134
|
})
|
|
117
135
|
setOn(req, st)
|
|
118
136
|
if (!hasErrorChannel() && typeof req.once === 'function') {
|
|
@@ -266,7 +284,7 @@ function installUndici (dc) {
|
|
|
266
284
|
for (const [name, value] of headers) {
|
|
267
285
|
if (!undiciHas(request, name)) request.addHeader(name, value)
|
|
268
286
|
}
|
|
269
|
-
})
|
|
287
|
+
}, () => consumers.callmap.originKey(String(request.origin || '')))
|
|
270
288
|
undiciStates.set(request, st)
|
|
271
289
|
})
|
|
272
290
|
dc.subscribe('undici:request:headers', ({ request, response }) => {
|
|
@@ -323,7 +341,7 @@ function wrapBunFetch () {
|
|
|
323
341
|
}
|
|
324
342
|
}
|
|
325
343
|
if (changed) args = [input, Object.assign({}, init || {}, { headers: h })]
|
|
326
|
-
})
|
|
344
|
+
}, () => consumers.callmap.urlKey(typeof Request === 'function' && input instanceof Request ? input.url : input))
|
|
327
345
|
} catch (_) {}
|
|
328
346
|
const p = orig.apply(this, args)
|
|
329
347
|
if (!st || !p || typeof p.then !== 'function') return p
|
|
@@ -348,7 +366,7 @@ function wrapBunFetch () {
|
|
|
348
366
|
// ---------------------------------------------------------------------------------------------
|
|
349
367
|
|
|
350
368
|
function refresh () {
|
|
351
|
-
if (!consumers.metrics && !consumers.tracing) return
|
|
369
|
+
if (!consumers.metrics && !consumers.tracing && !consumers.callmap) return
|
|
352
370
|
installDc()
|
|
353
371
|
if (BUN) wrapBunFetch()
|
|
354
372
|
if (consumers.tracing && consumers.tracing.outbound && !hasCreatedChannel()) wantClientWrap()
|