@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,236 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// node-redis (`redis` 4.x/5.x/6.x through `@redis/client`; 82 §6 ITG-122, ITG-123, ITG-032; streams
|
|
5
|
+
// §10 ITG-210). Every command the client sends is one CLIENT span:
|
|
6
|
+
// * @redis/client ≥ 5 (`redis` ≥ 5): `RedisClient.prototype.sendCommand(args, options)` — the
|
|
7
|
+
// typed command methods, scripts and raw `sendCommand` all go through it; `_executeMulti`
|
|
8
|
+
// (`multi().exec()`, MULTI span) and `_executePipeline` (`multi().execAsPipeline()`, PIPELINE
|
|
9
|
+
// span) queue their commands directly.
|
|
10
|
+
// * @redis/client 1.x (`redis` 4.x): `commandsExecutor(command, args)` (typed methods) and
|
|
11
|
+
// `sendCommand(args, options)`; `multiExecutor(commands, selectedDB, chainId)` (MULTI when a
|
|
12
|
+
// chain id is given, PIPELINE otherwise).
|
|
13
|
+
// `duplicate()`, pool/isolation clients and the node clients of `createCluster` are instances of
|
|
14
|
+
// the same class. Subscriptions do not go through these methods and make no span.
|
|
15
|
+
|
|
16
|
+
const c = require('./common')
|
|
17
|
+
const r = require('./redis-common')
|
|
18
|
+
|
|
19
|
+
function peerTags (client) {
|
|
20
|
+
const tags = {}
|
|
21
|
+
try {
|
|
22
|
+
const o = client.options || {}
|
|
23
|
+
const s = o.socket || {}
|
|
24
|
+
if (s.path) tags['net.peer.name'] = String(s.path)
|
|
25
|
+
else if (s.host || o.url) {
|
|
26
|
+
let host = s.host
|
|
27
|
+
let port = s.port
|
|
28
|
+
if (!host && o.url) {
|
|
29
|
+
try {
|
|
30
|
+
const u = new URL(o.url)
|
|
31
|
+
host = u.hostname
|
|
32
|
+
port = u.port || 6379
|
|
33
|
+
} catch (_) {}
|
|
34
|
+
}
|
|
35
|
+
if (host) tags['net.peer.name'] = String(host)
|
|
36
|
+
if (port) tags['net.peer.port'] = String(port)
|
|
37
|
+
} else {
|
|
38
|
+
tags['net.peer.name'] = 'localhost'
|
|
39
|
+
tags['net.peer.port'] = String(s.port || 6379)
|
|
40
|
+
}
|
|
41
|
+
} catch (_) {}
|
|
42
|
+
return tags
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function dbTags (client) {
|
|
46
|
+
const tags = Object.assign({ 'db.system': 'redis' }, peerTags(client))
|
|
47
|
+
try {
|
|
48
|
+
const db = client.options && client.options.database
|
|
49
|
+
tags['db.redis.database_index'] = String(db || 0)
|
|
50
|
+
} catch (_) {}
|
|
51
|
+
return tags
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Start the span of one command given its Redis arguments (name first); returns a finisher. */
|
|
55
|
+
function startCommand (client, redisArgs) {
|
|
56
|
+
if (!Array.isArray(redisArgs) || redisArgs.length === 0) return null
|
|
57
|
+
const name = r.text(redisArgs[0]).toUpperCase()
|
|
58
|
+
const args = redisArgs.slice(1)
|
|
59
|
+
if (r.STREAM_COMMANDS.has(name) && c.msgTracer()) {
|
|
60
|
+
const call = r.streamCall(name, args, peerTags(client))
|
|
61
|
+
if (!call || !call.span) return { extra: [], done () {} }
|
|
62
|
+
return { extra: call.extra, done: (v, err) => call.done(v, err) }
|
|
63
|
+
}
|
|
64
|
+
const t = c.dbTracer()
|
|
65
|
+
if (!t) return null
|
|
66
|
+
const tags = dbTags(client)
|
|
67
|
+
tags['db.operation'] = name
|
|
68
|
+
const st = r.statement(name, args, t.db.statement)
|
|
69
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
70
|
+
const span = c.clientSpan(t, name, tags)
|
|
71
|
+
if (!span) return null
|
|
72
|
+
return { extra: [], done: (v, err) => c.end(span, err, err ? r.errorCode(err) : undefined) }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function finishWith (p, done) {
|
|
76
|
+
if (!c.isThenable(p)) {
|
|
77
|
+
done(p)
|
|
78
|
+
return p
|
|
79
|
+
}
|
|
80
|
+
return p.then((v) => {
|
|
81
|
+
done(v)
|
|
82
|
+
return v
|
|
83
|
+
}, (err) => {
|
|
84
|
+
done(undefined, err)
|
|
85
|
+
throw err
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function wrapSendCommand (orig) {
|
|
90
|
+
return function sendCommand (args, options) {
|
|
91
|
+
if (!c.tracer() || c.nested() || !Array.isArray(args)) return orig.apply(this, arguments)
|
|
92
|
+
let call = null
|
|
93
|
+
try {
|
|
94
|
+
call = startCommand(this, args)
|
|
95
|
+
} catch (_) {
|
|
96
|
+
call = null
|
|
97
|
+
}
|
|
98
|
+
if (!call) return orig.apply(this, arguments)
|
|
99
|
+
const a = Array.prototype.slice.call(arguments)
|
|
100
|
+
if (call.extra.length) a[0] = args.concat(call.extra)
|
|
101
|
+
let p
|
|
102
|
+
try {
|
|
103
|
+
p = c.outer(orig, this, a)
|
|
104
|
+
} catch (err) {
|
|
105
|
+
call.done(undefined, err)
|
|
106
|
+
throw err
|
|
107
|
+
}
|
|
108
|
+
return finishWith(p, call.done)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @redis/client 1.x typed commands: commandsExecutor(command, args). The executor sends through
|
|
114
|
+
* the client's private send, so an XADD whose entry gets the trace fields (ITG-210,
|
|
115
|
+
* `redisStreamFields`) is sent here through the public `sendCommand` with the extended arguments
|
|
116
|
+
* and its reply transformed as the executor would (`commander.transformCommandReply`).
|
|
117
|
+
*/
|
|
118
|
+
function wrapCommandsExecutor (orig, transform, transformReply) {
|
|
119
|
+
return function commandsExecutor (command, args) {
|
|
120
|
+
if (!c.tracer() || c.nested()) return orig.apply(this, arguments)
|
|
121
|
+
let call = null
|
|
122
|
+
let t = null
|
|
123
|
+
try {
|
|
124
|
+
t = transform ? transform(command, args) : { args: command.transformArguments.apply(command, args) }
|
|
125
|
+
call = startCommand(this, Array.from(t.args))
|
|
126
|
+
} catch (_) {
|
|
127
|
+
call = null
|
|
128
|
+
}
|
|
129
|
+
if (!call) return c.outer(orig, this, arguments)
|
|
130
|
+
if (call.extra.length) {
|
|
131
|
+
if (transformReply && typeof this.sendCommand === 'function') {
|
|
132
|
+
let p
|
|
133
|
+
try {
|
|
134
|
+
const a = Array.from(t.args).concat(call.extra)
|
|
135
|
+
if (t.args.preserve !== undefined) a.preserve = t.args.preserve
|
|
136
|
+
p = c.outer(this.sendCommand, this, [a, t.options])
|
|
137
|
+
} catch (err) {
|
|
138
|
+
call.done(undefined, err)
|
|
139
|
+
throw err
|
|
140
|
+
}
|
|
141
|
+
return finishWith(p, call.done).then((reply) => transformReply(command, reply, t.args.preserve))
|
|
142
|
+
}
|
|
143
|
+
call.extra.length = 0 // no reply transform available: the span without the fields
|
|
144
|
+
}
|
|
145
|
+
let p
|
|
146
|
+
try {
|
|
147
|
+
p = c.outer(orig, this, arguments)
|
|
148
|
+
} catch (err) {
|
|
149
|
+
call.done(undefined, err)
|
|
150
|
+
throw err
|
|
151
|
+
}
|
|
152
|
+
return finishWith(p, call.done)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function batchSpan (client, commands, multi) {
|
|
157
|
+
const t = c.dbTracer()
|
|
158
|
+
if (!t || !Array.isArray(commands) || commands.length === 0) return null
|
|
159
|
+
const cmds = []
|
|
160
|
+
for (const cmd of commands) {
|
|
161
|
+
const a = Array.isArray(cmd) ? cmd : cmd && cmd.args
|
|
162
|
+
if (!Array.isArray(a) || a.length === 0) continue
|
|
163
|
+
const name = r.text(a[0]).toUpperCase()
|
|
164
|
+
if (multi && (name === 'MULTI' || name === 'EXEC')) continue // 1.x queues them with the commands
|
|
165
|
+
cmds.push([name, a.slice(1)])
|
|
166
|
+
}
|
|
167
|
+
const name = multi ? 'MULTI' : 'PIPELINE'
|
|
168
|
+
const tags = dbTags(client)
|
|
169
|
+
tags['db.operation'] = name
|
|
170
|
+
const st = r.pipelineStatement(cmds, t.db.statement)
|
|
171
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
172
|
+
tags['db.batch_size'] = String(cmds.length)
|
|
173
|
+
return c.clientSpan(t, name, tags)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function wrapBatch (multiFromArgs) {
|
|
177
|
+
return (orig) => function (commands) {
|
|
178
|
+
if (!c.tracer() || c.nested()) return orig.apply(this, arguments)
|
|
179
|
+
let span = null
|
|
180
|
+
try {
|
|
181
|
+
span = batchSpan(this, commands, multiFromArgs(arguments))
|
|
182
|
+
} catch (_) {
|
|
183
|
+
span = null
|
|
184
|
+
}
|
|
185
|
+
if (!span) return orig.apply(this, arguments)
|
|
186
|
+
let p
|
|
187
|
+
try {
|
|
188
|
+
p = c.outer(orig, this, arguments)
|
|
189
|
+
} catch (err) {
|
|
190
|
+
c.end(span, err)
|
|
191
|
+
throw err
|
|
192
|
+
}
|
|
193
|
+
return finishWith(p, (v, err) => c.end(span, err, err ? r.errorCode(err) : undefined))
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** dist/lib/client/index.js: the RedisClient class (default export). */
|
|
198
|
+
function patchClient (exports, info) {
|
|
199
|
+
const Client = exports && (exports.default || exports.RedisClient || exports)
|
|
200
|
+
const proto = Client && Client.prototype
|
|
201
|
+
if (!proto || typeof proto.sendCommand !== 'function') {
|
|
202
|
+
c.disabled('@redis/client', 'RedisClient.prototype.sendCommand not found')
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
c.wrap(proto, 'sendCommand', wrapSendCommand)
|
|
206
|
+
if (typeof proto._executeMulti === 'function') c.wrap(proto, '_executeMulti', wrapBatch(() => true))
|
|
207
|
+
if (typeof proto._executePipeline === 'function') c.wrap(proto, '_executePipeline', wrapBatch(() => false))
|
|
208
|
+
if (typeof proto.multiExecutor === 'function') {
|
|
209
|
+
// 1.x: a chain id (third argument) marks a transaction
|
|
210
|
+
c.wrap(proto, 'multiExecutor', wrapBatch((args) => args.length >= 3 && args[2] !== undefined))
|
|
211
|
+
}
|
|
212
|
+
if (typeof proto.commandsExecutor === 'function') {
|
|
213
|
+
let transform = null
|
|
214
|
+
let transformReply = null
|
|
215
|
+
try {
|
|
216
|
+
const commander = require(require('path').join(info.dir, 'dist/lib/commander.js'))
|
|
217
|
+
if (typeof commander.transformCommandArguments === 'function') transform = commander.transformCommandArguments
|
|
218
|
+
if (typeof commander.transformCommandReply === 'function') transformReply = commander.transformCommandReply
|
|
219
|
+
} catch (_) {}
|
|
220
|
+
c.wrap(proto, 'commandsExecutor', (orig) => wrapCommandsExecutor(orig, transform, transformReply))
|
|
221
|
+
// 1.x binds the typed command methods to the executor it had when the module was evaluated
|
|
222
|
+
// (attachCommands); attaching them again with the wrapped executor routes them through it
|
|
223
|
+
try {
|
|
224
|
+
const path = require('path')
|
|
225
|
+
const commander = require(path.join(info.dir, 'dist/lib/commander.js'))
|
|
226
|
+
const commands = require(path.join(info.dir, 'dist/lib/client/commands.js'))
|
|
227
|
+
if (typeof commander.attachCommands === 'function' && commands && commands.default) {
|
|
228
|
+
commander.attachCommands({ BaseClass: Client, commands: commands.default, executor: proto.commandsExecutor })
|
|
229
|
+
}
|
|
230
|
+
} catch (err) {
|
|
231
|
+
c.disabled('@redis/client', 'typed commands not re-attached: ' + (err && err.message))
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = { patchClient }
|
package/lib/itg/pg.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// node-postgres (82 §5.3, ITG-110–113): `Client.prototype.query` in every form (text, text +
|
|
5
|
+
// values, config object, callback or promise, submittables such as pg-cursor / pg-query-stream)
|
|
6
|
+
// is one CLIENT span; `Pool.prototype.connect` binds its callback to the caller's context (so the
|
|
7
|
+
// query that `Pool.query` issues after a checkout belongs to the right request) and, with
|
|
8
|
+
// `db.poolSpans`, creates a `pool.acquire` span when the checkout has to wait. The native client
|
|
9
|
+
// (`pg.native`) is wrapped the same way.
|
|
10
|
+
|
|
11
|
+
const c = require('./common')
|
|
12
|
+
const sql = require('./sql')
|
|
13
|
+
const dbc = require('./dbcomment')
|
|
14
|
+
|
|
15
|
+
const TAGS = Symbol('pmx-pg-tags')
|
|
16
|
+
// a submittable (Query, Cursor, QueryStream) pmx tagged in place: { target, prefix }
|
|
17
|
+
const TAGGED = Symbol('pmx-pg-tagged')
|
|
18
|
+
|
|
19
|
+
function connTags (client) {
|
|
20
|
+
let tags = client[TAGS]
|
|
21
|
+
if (tags) return tags
|
|
22
|
+
tags = { 'db.system': 'postgresql' }
|
|
23
|
+
const p = client.connectionParameters || client
|
|
24
|
+
const db = client.database || p.database
|
|
25
|
+
const user = client.user || p.user
|
|
26
|
+
const host = client.host || p.host
|
|
27
|
+
const port = client.port || p.port
|
|
28
|
+
if (db) tags['db.name'] = String(db)
|
|
29
|
+
if (user) tags['db.user'] = String(user)
|
|
30
|
+
if (typeof host === 'string' && host.startsWith('/')) {
|
|
31
|
+
tags['net.peer.name'] = host.replace(/\/$/, '') + '/.s.PGSQL.' + (port || 5432)
|
|
32
|
+
} else {
|
|
33
|
+
if (host) tags['net.peer.name'] = String(host)
|
|
34
|
+
if (port) tags['net.peer.port'] = String(port)
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
Object.defineProperty(client, TAGS, { value: tags, configurable: true })
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
return tags
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeSpan (t, client, text, name) {
|
|
43
|
+
const base = connTags(client)
|
|
44
|
+
const op = sql.operation(text)
|
|
45
|
+
const tags = Object.assign({}, base)
|
|
46
|
+
if (op) tags['db.operation'] = op
|
|
47
|
+
const st = sql.statementTag(text, t.db.statement, false)
|
|
48
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
49
|
+
if (name) tags['db.statement.name'] = String(name)
|
|
50
|
+
const spanName = op ? (base['db.name'] ? op + ' ' + base['db.name'] : op) : (base['db.name'] || 'postgresql')
|
|
51
|
+
return c.clientSpan(t, spanName, tags)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const rows = (span) => (res) => {
|
|
55
|
+
if (res && typeof res.rowCount === 'number') span.setAttribute('db.rows', String(res.rowCount))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const codeOf = (err) => (err && typeof err.code === 'string' ? err.code : undefined)
|
|
59
|
+
|
|
60
|
+
/** Submittables (ITG-112): one span until ReadyForQuery or an error; db.rows = rows read. */
|
|
61
|
+
function traceSubmittable (span, q) {
|
|
62
|
+
let n = 0
|
|
63
|
+
const done = (err) => {
|
|
64
|
+
if (span.ended) return
|
|
65
|
+
span.setAttribute('db.rows', String(n))
|
|
66
|
+
c.end(span, err, codeOf(err))
|
|
67
|
+
}
|
|
68
|
+
const hook = (name, after) => {
|
|
69
|
+
const orig = q[name]
|
|
70
|
+
if (typeof orig !== 'function') return
|
|
71
|
+
Object.defineProperty(q, name, {
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
enumerable: Object.prototype.propertyIsEnumerable.call(q, name),
|
|
75
|
+
value: function () {
|
|
76
|
+
try {
|
|
77
|
+
after.apply(null, arguments)
|
|
78
|
+
} catch (_) {}
|
|
79
|
+
return orig.apply(this, arguments)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
hook('handleDataRow', () => { n++ })
|
|
84
|
+
hook('handleError', (err) => done(err || new Error('query failed')))
|
|
85
|
+
hook('handleReadyForQuery', () => done())
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function wrapQuery (orig) {
|
|
89
|
+
return function query (config, values, callback) {
|
|
90
|
+
const t = c.dbTracer()
|
|
91
|
+
if (!t || config === null || config === undefined || c.nested()) return orig.apply(this, arguments)
|
|
92
|
+
let span = null
|
|
93
|
+
let text
|
|
94
|
+
try {
|
|
95
|
+
if (typeof config === 'object') untag(config)
|
|
96
|
+
text = typeof config === 'string' ? config : config.text
|
|
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)
|
|
99
|
+
} catch (_) {
|
|
100
|
+
span = null
|
|
101
|
+
}
|
|
102
|
+
if (!span) return orig.apply(this, arguments)
|
|
103
|
+
const args = Array.prototype.slice.call(arguments)
|
|
104
|
+
try {
|
|
105
|
+
comment(t, span, config, text, args)
|
|
106
|
+
} catch (_) {}
|
|
107
|
+
try {
|
|
108
|
+
if (typeof config.submit === 'function') {
|
|
109
|
+
traceSubmittable(span, config)
|
|
110
|
+
return orig.apply(this, args)
|
|
111
|
+
}
|
|
112
|
+
// callback forms: (text, cb), (text, values, cb), (config, cb), config.callback
|
|
113
|
+
let cbIndex = -1
|
|
114
|
+
if (typeof values === 'function') cbIndex = 1
|
|
115
|
+
else if (typeof callback === 'function') cbIndex = 2
|
|
116
|
+
if (cbIndex !== -1) {
|
|
117
|
+
const cb = c.bind(args[cbIndex])
|
|
118
|
+
args[cbIndex] = function (err, res) {
|
|
119
|
+
if (!err) rows(span)(res)
|
|
120
|
+
c.end(span, err || undefined, codeOf(err))
|
|
121
|
+
return cb.apply(this, arguments)
|
|
122
|
+
}
|
|
123
|
+
return orig.apply(this, args)
|
|
124
|
+
}
|
|
125
|
+
if (typeof config === 'object' && typeof config.callback === 'function') {
|
|
126
|
+
const cb = c.bind(config.callback)
|
|
127
|
+
args[0] = Object.assign(Object.create(Object.getPrototypeOf(args[0])), args[0], {
|
|
128
|
+
callback (err, res) {
|
|
129
|
+
if (!err) rows(span)(res)
|
|
130
|
+
c.end(span, err || undefined, codeOf(err))
|
|
131
|
+
return cb.apply(this, arguments)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
return orig.apply(this, args)
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
c.end(span)
|
|
138
|
+
return orig.apply(this, arguments)
|
|
139
|
+
}
|
|
140
|
+
let result
|
|
141
|
+
try {
|
|
142
|
+
result = orig.apply(this, args)
|
|
143
|
+
} catch (err) {
|
|
144
|
+
c.end(span, err, codeOf(err))
|
|
145
|
+
throw err
|
|
146
|
+
}
|
|
147
|
+
if (c.isThenable(result)) return c.settle(result, span, rows(span), codeOf)
|
|
148
|
+
c.end(span)
|
|
149
|
+
return result
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 96 SVM-501/502: prepend the trace comment to the statement (args[0] replaced by a copy; a
|
|
155
|
+
* submittable's own text is set in place). Named statements (`name`) are prepared and cached by
|
|
156
|
+
* name: skipped with `on`; with `force` the copy is sent unnamed (parsed every time).
|
|
157
|
+
*/
|
|
158
|
+
function comment (t, span, config, text, args) {
|
|
159
|
+
const named = typeof config === 'object' && config.name !== undefined && config.name !== null && config.name !== ''
|
|
160
|
+
if (!dbc.wants(t, span, text, named)) return
|
|
161
|
+
const prefix = dbc.sqlPrefix(t, span)
|
|
162
|
+
if (typeof config === 'string') {
|
|
163
|
+
args[0] = prefix + config
|
|
164
|
+
} else if (typeof config.submit === 'function') {
|
|
165
|
+
if (named) return // the app's object would have to be renamed
|
|
166
|
+
// tagged in place (the app holds the object); untag() removes it before a re-execution
|
|
167
|
+
const target = config.cursor && typeof config.cursor.text === 'string' && config.text === undefined ? config.cursor : config
|
|
168
|
+
if (typeof target.text !== 'string') return
|
|
169
|
+
target.text = prefix + target.text
|
|
170
|
+
try {
|
|
171
|
+
Object.defineProperty(config, TAGGED, { value: { target, prefix }, configurable: true, writable: true })
|
|
172
|
+
} catch (_) {}
|
|
173
|
+
} else if (typeof config.text === 'string') {
|
|
174
|
+
const copy = Object.assign(Object.create(Object.getPrototypeOf(config)), config, { text: prefix + config.text })
|
|
175
|
+
if (named) copy.name = undefined
|
|
176
|
+
args[0] = copy
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A submittable reused by the app: remove the comment pmx added at its previous execution. */
|
|
181
|
+
function untag (config) {
|
|
182
|
+
const tag = config[TAGGED]
|
|
183
|
+
if (!tag) return
|
|
184
|
+
config[TAGGED] = undefined
|
|
185
|
+
const { target, prefix } = tag
|
|
186
|
+
if (typeof target.text === 'string' && target.text.startsWith(prefix)) target.text = target.text.slice(prefix.length)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 96 SVM-505: application_name = the pmx app name when the app set none (or PGAPPNAME). */
|
|
190
|
+
function wrapClientConnect (orig) {
|
|
191
|
+
return function connect () {
|
|
192
|
+
try {
|
|
193
|
+
const p = this.connectionParameters
|
|
194
|
+
const t = p && c.dbTracer()
|
|
195
|
+
if (t && !p.application_name && !p.fallback_application_name) {
|
|
196
|
+
const name = dbc.connectionAppName(t)
|
|
197
|
+
if (name) p.fallback_application_name = name
|
|
198
|
+
}
|
|
199
|
+
} catch (_) {}
|
|
200
|
+
return orig.apply(this, arguments)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function wrapConnect (orig) {
|
|
205
|
+
return function connect (cb) {
|
|
206
|
+
const t = c.tracer()
|
|
207
|
+
if (!t) return orig.apply(this, arguments)
|
|
208
|
+
let span = null
|
|
209
|
+
try {
|
|
210
|
+
const max = this.options && this.options.max
|
|
211
|
+
if (t.db && t.db.poolSpans && c.currentSpan() &&
|
|
212
|
+
this.idleCount === 0 && typeof max === 'number' && this.totalCount >= max) {
|
|
213
|
+
span = c.clientSpan(t, 'pool.acquire', {
|
|
214
|
+
'db.system': 'postgresql',
|
|
215
|
+
'db.pool.size': String(this.totalCount),
|
|
216
|
+
'db.pool.max_size': String(max),
|
|
217
|
+
'db.pool.waiting': String(this.waitingCount)
|
|
218
|
+
}, 'INTERNAL')
|
|
219
|
+
}
|
|
220
|
+
} catch (_) {
|
|
221
|
+
span = null
|
|
222
|
+
}
|
|
223
|
+
if (typeof cb === 'function') {
|
|
224
|
+
const bound = c.bind(cb)
|
|
225
|
+
return orig.call(this, function (err) {
|
|
226
|
+
c.end(span, err || undefined)
|
|
227
|
+
return bound.apply(this, arguments)
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
const p = orig.apply(this, arguments)
|
|
231
|
+
if (span && c.isThenable(p)) return c.settle(p, span)
|
|
232
|
+
return p
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function patchClient (Client) {
|
|
237
|
+
if (!Client || !Client.prototype || typeof Client.prototype.query !== 'function') {
|
|
238
|
+
c.disabled('pg', 'Client.prototype.query not found')
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
c.wrap(Client.prototype, 'query', wrapQuery)
|
|
242
|
+
if (typeof Client.prototype.connect === 'function') c.wrap(Client.prototype, 'connect', wrapClientConnect)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function patchPool (Pool) {
|
|
246
|
+
if (!Pool || !Pool.prototype || typeof Pool.prototype.connect !== 'function') {
|
|
247
|
+
c.disabled('pg-pool', 'Pool.prototype.connect not found')
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
c.wrap(Pool.prototype, 'connect', wrapConnect)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = { patchClient, patchPool, connTags }
|