@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,322 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// MongoDB Node driver (82 §8 ITG-142, ITG-143, ITG-033), through the driver's command monitoring.
|
|
5
|
+
// `MongoClient` (the export of lib/mongo_client.js) is replaced by a Proxy whose construct trap
|
|
6
|
+
// enables `monitorCommands` unless the app passed `monitorCommands: false` explicitly, and
|
|
7
|
+
// observes `commandStarted` / `commandSucceeded` / `commandFailed` through the instance's `emit`
|
|
8
|
+
// (no listener is added, so the client's listener APIs show only the app's own). The driver emits
|
|
9
|
+
// these events synchronously in the async context of the operation, so a span's parent is the
|
|
10
|
+
// span current when the app called the operation. Documented side effect (82 open issue 3): an app
|
|
11
|
+
// listener registered for command events on a client created without the option now fires.
|
|
12
|
+
// The Proxy keeps `name`, statics, `instanceof` and subclassing (`class X extends MongoClient`).
|
|
13
|
+
|
|
14
|
+
const crypto = require('crypto')
|
|
15
|
+
const c = require('./common')
|
|
16
|
+
const dbc = require('./dbcomment')
|
|
17
|
+
|
|
18
|
+
const MAX_STATEMENT = 2000
|
|
19
|
+
const SKIPPED = new Set(['saslstart', 'saslcontinue', 'authenticate', 'getnonce', 'hello', 'ismaster',
|
|
20
|
+
'copydbsaslstart', 'createuser', 'updateuser'])
|
|
21
|
+
const DROPPED = new Set(['lsid', '$clusterTime', '$db', 'txnNumber', 'signature', '$readPreference'])
|
|
22
|
+
|
|
23
|
+
let ejson = null
|
|
24
|
+
|
|
25
|
+
function isPlain (v) {
|
|
26
|
+
if (!v || typeof v !== 'object') return false
|
|
27
|
+
if (Array.isArray(v)) return false
|
|
28
|
+
if (v._bsontype) return false
|
|
29
|
+
if (v instanceof Date || v instanceof Uint8Array || v instanceof RegExp) return false
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function shape (v, depth) {
|
|
34
|
+
if (depth > 32) return '?'
|
|
35
|
+
if (Array.isArray(v)) {
|
|
36
|
+
if (v.length === 0) return []
|
|
37
|
+
const first = shape(v[0], depth + 1)
|
|
38
|
+
return v.length > 1 ? [first, '…'] : [first]
|
|
39
|
+
}
|
|
40
|
+
if (v instanceof Map) {
|
|
41
|
+
const o = {}
|
|
42
|
+
for (const [k, x] of v) if (!DROPPED.has(k)) o[String(k)] = shape(x, depth + 1)
|
|
43
|
+
return o
|
|
44
|
+
}
|
|
45
|
+
if (isPlain(v)) {
|
|
46
|
+
const o = {}
|
|
47
|
+
for (const k of Object.keys(v)) if (!DROPPED.has(k)) o[k] = shape(v[k], depth + 1)
|
|
48
|
+
return o
|
|
49
|
+
}
|
|
50
|
+
return '?'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function truncate (s) {
|
|
54
|
+
return s.length > MAX_STATEMENT ? s.slice(0, MAX_STATEMENT - 1) + '…' : s
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** ITG-033: the command document, values replaced by "?" (sanitized) or as extended JSON (raw). */
|
|
58
|
+
function statement (command, mode) {
|
|
59
|
+
if (mode === 'off' || !command || typeof command !== 'object') return undefined
|
|
60
|
+
try {
|
|
61
|
+
if (mode === 'raw') {
|
|
62
|
+
const doc = {}
|
|
63
|
+
for (const k of Object.keys(command)) if (!DROPPED.has(k)) doc[k] = command[k]
|
|
64
|
+
const text = ejson ? ejson.stringify(doc, { relaxed: true }) : JSON.stringify(doc, (k, v) => (typeof v === 'bigint' ? String(v) : v))
|
|
65
|
+
return truncate(text)
|
|
66
|
+
}
|
|
67
|
+
return truncate(JSON.stringify(shape(command, 0)))
|
|
68
|
+
} catch (_) {
|
|
69
|
+
return undefined
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function collectionOf (name, command) {
|
|
74
|
+
try {
|
|
75
|
+
const v = name === 'getMore' ? command.collection : command[name]
|
|
76
|
+
return typeof v === 'string' ? v : undefined
|
|
77
|
+
} catch (_) {
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function replyRows (reply) {
|
|
83
|
+
try {
|
|
84
|
+
const cur = reply && reply.cursor
|
|
85
|
+
if (cur && typeof cur === 'object') {
|
|
86
|
+
for (const k of ['firstBatch', 'nextBatch']) if (Array.isArray(cur[k])) return cur[k].length
|
|
87
|
+
}
|
|
88
|
+
if (reply && typeof reply.n === 'number') return reply.n
|
|
89
|
+
} catch (_) {}
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function peer (address) {
|
|
94
|
+
const tags = {}
|
|
95
|
+
if (typeof address !== 'string') return tags
|
|
96
|
+
const i = address.lastIndexOf(':')
|
|
97
|
+
if (address.startsWith('/') || i === -1) tags['net.peer.name'] = address
|
|
98
|
+
else {
|
|
99
|
+
tags['net.peer.name'] = address.slice(0, i).replace(/^\[|\]$/g, '')
|
|
100
|
+
tags['net.peer.port'] = address.slice(i + 1)
|
|
101
|
+
}
|
|
102
|
+
return tags
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function keyOf (ev) {
|
|
106
|
+
return String(ev.requestId) + '|' + String(ev.connectionId) + '|' + String(ev.address)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Per client: open spans by (requestId, connectionId, address). */
|
|
110
|
+
function observe (client) {
|
|
111
|
+
const open = new Map()
|
|
112
|
+
const emit = client.emit
|
|
113
|
+
if (typeof emit !== 'function') return
|
|
114
|
+
Object.defineProperty(client, 'emit', {
|
|
115
|
+
configurable: true,
|
|
116
|
+
writable: true,
|
|
117
|
+
enumerable: false,
|
|
118
|
+
value: function (event, ev) {
|
|
119
|
+
try {
|
|
120
|
+
if (event === 'commandStarted') started(open, ev)
|
|
121
|
+
else if (event === 'commandSucceeded') succeeded(open, ev)
|
|
122
|
+
else if (event === 'commandFailed') failed(open, ev)
|
|
123
|
+
} catch (_) {}
|
|
124
|
+
return emit.apply(this, arguments)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function started (open, ev) {
|
|
130
|
+
if (!ev || typeof ev.commandName !== 'string') return
|
|
131
|
+
const name = ev.commandName
|
|
132
|
+
if (SKIPPED.has(name.toLowerCase())) return
|
|
133
|
+
const t = c.dbTracer()
|
|
134
|
+
if (!t || c.suppressedLib('mongodb')) return
|
|
135
|
+
let command = ev.command || {}
|
|
136
|
+
// our trace comment (96 SVM-503): the span gets the id the comment carries, and the comment
|
|
137
|
+
// is not part of db.statement
|
|
138
|
+
let plan = null
|
|
139
|
+
if (typeof command.comment === 'string' && planned.has(command.comment)) {
|
|
140
|
+
plan = planned.get(command.comment)
|
|
141
|
+
planned.delete(command.comment)
|
|
142
|
+
const rest = {}
|
|
143
|
+
for (const k of Object.keys(command)) if (k !== 'comment') rest[k] = command[k]
|
|
144
|
+
command = rest
|
|
145
|
+
}
|
|
146
|
+
const coll = collectionOf(name, command)
|
|
147
|
+
const tags = { 'db.system': 'mongodb', 'db.operation': name }
|
|
148
|
+
if (ev.databaseName) tags['db.name'] = String(ev.databaseName)
|
|
149
|
+
if (coll) tags['db.mongodb.collection'] = coll
|
|
150
|
+
Object.assign(tags, peer(ev.address))
|
|
151
|
+
const st = statement(command, t.db.statement)
|
|
152
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
153
|
+
const span = c.clientSpan(t, coll ? name + ' ' + coll : name, tags)
|
|
154
|
+
// only when the span belongs to the trace the comment names (else the comment stays unmatched)
|
|
155
|
+
if (span && plan && !t.bridge && typeof span.spanId === 'string' && span.traceId === plan.traceId) {
|
|
156
|
+
span.spanId = plan.spanId
|
|
157
|
+
span._ctx = null
|
|
158
|
+
}
|
|
159
|
+
if (span) open.set(keyOf(ev), span)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function succeeded (open, ev) {
|
|
163
|
+
const k = keyOf(ev)
|
|
164
|
+
const span = open.get(k)
|
|
165
|
+
if (!span) return
|
|
166
|
+
open.delete(k)
|
|
167
|
+
const reply = ev.reply
|
|
168
|
+
// a write the server answers with writeErrors succeeded at the protocol level (same as pmx-io)
|
|
169
|
+
const we = reply && Array.isArray(reply.writeErrors) && reply.writeErrors[0]
|
|
170
|
+
if (we) {
|
|
171
|
+
const code = we.code
|
|
172
|
+
const err = { name: code === 11000 ? 'DuplicateKeyError' : 'WriteError', message: String(we.errmsg || '') }
|
|
173
|
+
c.end(span, err, code)
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
const rows = replyRows(reply)
|
|
177
|
+
if (rows !== undefined) span.setAttribute('db.rows', String(rows))
|
|
178
|
+
c.end(span)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function failed (open, ev) {
|
|
182
|
+
const k = keyOf(ev)
|
|
183
|
+
const span = open.get(k)
|
|
184
|
+
if (!span) return
|
|
185
|
+
open.delete(k)
|
|
186
|
+
const err = ev.failure || new Error('command failed')
|
|
187
|
+
c.end(span, err, err && err.code !== undefined ? err.code : undefined)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 96 SVM-503: comments added by the Connection.command wrapper → the span id they carry
|
|
191
|
+
const planned = new Map()
|
|
192
|
+
const PLANNED_MAX = 1024
|
|
193
|
+
// commands that accept `comment` from MongoDB 4.4 (wire version 9); getMore inherits it
|
|
194
|
+
const COMMENTED = new Set(['find', 'aggregate', 'count', 'distinct', 'insert', 'update', 'delete', 'findAndModify'])
|
|
195
|
+
const WIRE_4_4 = 9
|
|
196
|
+
|
|
197
|
+
function randomSpanId () {
|
|
198
|
+
let id
|
|
199
|
+
do id = crypto.randomBytes(8).toString('hex')
|
|
200
|
+
while (id === '0000000000000000')
|
|
201
|
+
return id
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function maxWire (conn) {
|
|
205
|
+
const d = conn && (conn.description || conn.hello)
|
|
206
|
+
return d && typeof d.maxWireVersion === 'number' ? d.maxWireVersion : 0
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The command document with the trace comment (a copy), or `cmd` unchanged. */
|
|
210
|
+
function withComment (conn, cmd) {
|
|
211
|
+
const t = c.dbTracer()
|
|
212
|
+
if (!t || t.bridge || c.suppressedLib('mongodb')) return cmd
|
|
213
|
+
const mode = t.db.comment
|
|
214
|
+
if (mode !== 'on' && mode !== 'force') return cmd
|
|
215
|
+
if (!cmd || typeof cmd !== 'object' || cmd.comment !== undefined) return cmd
|
|
216
|
+
// auto-encryption connections pass an already encrypted document down: left alone
|
|
217
|
+
if (conn && conn.constructor && conn.constructor.name === 'CryptoConnection') return cmd
|
|
218
|
+
const proto = Object.getPrototypeOf(cmd)
|
|
219
|
+
if (proto !== Object.prototype && proto !== null) return cmd
|
|
220
|
+
const name = Object.keys(cmd)[0]
|
|
221
|
+
if (!COMMENTED.has(name) || maxWire(conn) < WIRE_4_4) return cmd
|
|
222
|
+
const parent = dbc.spanIds(c.currentSpan())
|
|
223
|
+
if (!parent) return cmd
|
|
224
|
+
const id = randomSpanId()
|
|
225
|
+
if (!dbc.validIds(parent.traceId, id)) return cmd
|
|
226
|
+
const comment = dbc.mongoComment(t, parent.traceId, id)
|
|
227
|
+
if (planned.size >= PLANNED_MAX) planned.clear()
|
|
228
|
+
planned.set(comment, { traceId: parent.traceId, spanId: id })
|
|
229
|
+
return Object.assign({}, cmd, { comment })
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function wrapCommand (orig) {
|
|
233
|
+
return function command (ns, cmd) {
|
|
234
|
+
let next = cmd
|
|
235
|
+
try {
|
|
236
|
+
next = withComment(this, cmd)
|
|
237
|
+
} catch (_) {
|
|
238
|
+
next = cmd
|
|
239
|
+
}
|
|
240
|
+
if (next === cmd) return orig.apply(this, arguments)
|
|
241
|
+
const args = Array.prototype.slice.call(arguments)
|
|
242
|
+
args[1] = next
|
|
243
|
+
return orig.apply(this, args)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** 96 SVM-505: appName = the pmx app name unless the options or the URL name one. */
|
|
248
|
+
function withAppName (args, opts) {
|
|
249
|
+
const t = c.dbTracer()
|
|
250
|
+
const name = t && dbc.connectionAppName(t)
|
|
251
|
+
if (!name) return opts
|
|
252
|
+
if (opts && opts.appName !== undefined && opts.appName !== null) return opts
|
|
253
|
+
if (typeof args[0] === 'string' && /[?&]appname=/i.test(args[0])) return opts
|
|
254
|
+
return Object.assign({}, opts, { appName: name })
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function proxyClient (MongoClient) {
|
|
258
|
+
return new Proxy(MongoClient, {
|
|
259
|
+
construct (target, args, newTarget) {
|
|
260
|
+
let a = args
|
|
261
|
+
try {
|
|
262
|
+
if (c.dbTracer()) {
|
|
263
|
+
const opts = args[1]
|
|
264
|
+
if (opts === undefined || opts === null || typeof opts === 'object') {
|
|
265
|
+
let next = opts || {}
|
|
266
|
+
if (next.monitorCommands === undefined) next = Object.assign({}, next, { monitorCommands: true })
|
|
267
|
+
next = withAppName(args, next)
|
|
268
|
+
if (next !== (opts || {})) {
|
|
269
|
+
a = args.slice()
|
|
270
|
+
a[1] = next
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch (_) {
|
|
275
|
+
a = args
|
|
276
|
+
}
|
|
277
|
+
const client = Reflect.construct(target, a, newTarget)
|
|
278
|
+
try {
|
|
279
|
+
const o = client.options || {}
|
|
280
|
+
if (o.monitorCommands !== false) observe(client)
|
|
281
|
+
} catch (_) {}
|
|
282
|
+
return client
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** lib/mongo_client.js: exports.MongoClient. */
|
|
288
|
+
function patchClient (exports, info) {
|
|
289
|
+
const MongoClient = exports && exports.MongoClient
|
|
290
|
+
if (typeof MongoClient !== 'function') {
|
|
291
|
+
c.disabled('mongodb', 'MongoClient not found')
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
if (MongoClient[c.WRAPPED]) return
|
|
295
|
+
// 85 OTX-020: OpenTelemetry's MongoDB instrumentation wraps the connection's `command` (pmx
|
|
296
|
+
// observes events and wraps nothing there); resolved on the first command in bridge mode
|
|
297
|
+
const dir = info && info.dir
|
|
298
|
+
if (dir) {
|
|
299
|
+
c.watch('mongodb', () => {
|
|
300
|
+
const m = require(require('path').join(dir, 'lib', 'cmap', 'connection.js'))
|
|
301
|
+
return m && m.Connection && m.Connection.prototype ? [m.Connection.prototype, 'command'] : null
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
if (dir) {
|
|
305
|
+
try {
|
|
306
|
+
const m = require(require('path').join(dir, 'lib', 'cmap', 'connection.js'))
|
|
307
|
+
if (m && m.Connection && m.Connection.prototype) c.wrap(m.Connection.prototype, 'command', wrapCommand)
|
|
308
|
+
} catch (err) {
|
|
309
|
+
c.disabled('mongodb', 'trace comment: ' + (err && err.message))
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const b = require(require('path').join(info.dir, 'lib', 'bson.js'))
|
|
314
|
+
if (b && b.EJSON && typeof b.EJSON.stringify === 'function') ejson = b.EJSON
|
|
315
|
+
} catch (_) {}
|
|
316
|
+
const P = proxyClient(MongoClient)
|
|
317
|
+
const d = Object.getOwnPropertyDescriptor(exports, 'MongoClient')
|
|
318
|
+
if (d && d.writable) exports.MongoClient = P
|
|
319
|
+
else Object.defineProperty(exports, 'MongoClient', { value: P, writable: true, enumerable: true, configurable: true })
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = { patchClient, statement, wrapCommand, withAppName, planned }
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Copyright (c) 2026 Mike Warriner. All rights reserved. Proprietary — see LICENSE.
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// mysql2 (82 §7 ITG-133, ITG-134, ITG-031). `query` and `execute` of the connection class are one
|
|
5
|
+
// CLIENT span per call, in callback form, event form (`query(…).on(…)`, `.stream()`) and through
|
|
6
|
+
// the promise wrapper (`mysql2/promise` calls the callback API, so it adds no span). The span ends
|
|
7
|
+
// at the command's `end` event, observed through the command's own `emit` (no listener is added,
|
|
8
|
+
// so an unhandled `error` event still behaves as without pmx). The statement is the text the app
|
|
9
|
+
// passed — before mysql2 interpolates the values client-side — sanitised per ITG-031. Pools bind
|
|
10
|
+
// the `getConnection` callback to the caller's context (the query a pool issues after a checkout
|
|
11
|
+
// belongs to the right request) and, with `db.poolSpans`, make a `pool.acquire` span when the
|
|
12
|
+
// checkout has to wait.
|
|
13
|
+
|
|
14
|
+
const c = require('./common')
|
|
15
|
+
const sql = require('./sql')
|
|
16
|
+
const dbc = require('./dbcomment')
|
|
17
|
+
|
|
18
|
+
const TAGS = Symbol('pmx-mysql2-tags')
|
|
19
|
+
|
|
20
|
+
function connTags (conn) {
|
|
21
|
+
let tags = conn[TAGS]
|
|
22
|
+
if (tags) return tags
|
|
23
|
+
tags = { 'db.system': 'mysql' }
|
|
24
|
+
const cfg = conn.config || {}
|
|
25
|
+
if (cfg.database) tags['db.name'] = String(cfg.database)
|
|
26
|
+
if (cfg.user) tags['db.user'] = String(cfg.user)
|
|
27
|
+
if (cfg.socketPath) tags['net.peer.name'] = String(cfg.socketPath)
|
|
28
|
+
else {
|
|
29
|
+
tags['net.peer.name'] = String(cfg.host || 'localhost')
|
|
30
|
+
if (cfg.port) tags['net.peer.port'] = String(cfg.port)
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
Object.defineProperty(conn, TAGS, { value: tags, configurable: true })
|
|
34
|
+
} catch (_) {}
|
|
35
|
+
return tags
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function textOf (s) {
|
|
39
|
+
if (typeof s === 'string') return s
|
|
40
|
+
if (s && typeof s === 'object' && typeof s.sql === 'string') return s.sql
|
|
41
|
+
return ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function rowsOf (rows) {
|
|
45
|
+
if (Array.isArray(rows)) return rows.length
|
|
46
|
+
if (rows && typeof rows === 'object' && typeof rows.affectedRows === 'number') return rows.affectedRows
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Observe the command's completion through its own emit/onResult (instance properties). */
|
|
51
|
+
function observe (cmd, span) {
|
|
52
|
+
let err
|
|
53
|
+
let rows
|
|
54
|
+
let streamed = 0
|
|
55
|
+
const finish = () => {
|
|
56
|
+
if (span.ended) return
|
|
57
|
+
if (!err) {
|
|
58
|
+
const n = rows !== undefined ? rows : (streamed || undefined)
|
|
59
|
+
if (n !== undefined) span.setAttribute('db.rows', String(n))
|
|
60
|
+
}
|
|
61
|
+
c.end(span, err, err && err.errno !== undefined ? err.errno : undefined)
|
|
62
|
+
}
|
|
63
|
+
// callback mode: the result is delivered after `end` (on the next tick); the span ends then
|
|
64
|
+
const byCallback = typeof cmd.onResult === 'function'
|
|
65
|
+
const emit = cmd.emit
|
|
66
|
+
Object.defineProperty(cmd, 'emit', {
|
|
67
|
+
configurable: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
enumerable: false,
|
|
70
|
+
value: function (ev, a) {
|
|
71
|
+
try {
|
|
72
|
+
if (ev === 'error') err = err || a
|
|
73
|
+
else if (ev === 'result') {
|
|
74
|
+
if (a && typeof a === 'object' && !Array.isArray(a) && typeof a.affectedRows === 'number') rows = a.affectedRows
|
|
75
|
+
else streamed++
|
|
76
|
+
} else if (ev === 'end' && !byCallback) finish()
|
|
77
|
+
} catch (_) {}
|
|
78
|
+
return emit.apply(this, arguments)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
const onResult = cmd.onResult
|
|
82
|
+
if (typeof onResult === 'function') {
|
|
83
|
+
cmd.onResult = function (e, r) {
|
|
84
|
+
try {
|
|
85
|
+
if (e) err = e
|
|
86
|
+
else rows = rowsOf(r)
|
|
87
|
+
finish()
|
|
88
|
+
} catch (_) {}
|
|
89
|
+
return onResult.apply(this, arguments)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 96 SVM-501/502: prepend the trace comment (args[0] replaced by a copy). `execute()` uses
|
|
96
|
+
* mysql2's prepared-statement cache keyed by the text: skipped with `on`, commented with `force`.
|
|
97
|
+
*/
|
|
98
|
+
function comment (t, span, s, args, prepared) {
|
|
99
|
+
const text = textOf(s)
|
|
100
|
+
if (!dbc.wants(t, span, text, prepared)) return
|
|
101
|
+
const prefix = dbc.sqlPrefix(t, span)
|
|
102
|
+
if (typeof s === 'string') args[0] = prefix + s
|
|
103
|
+
else {
|
|
104
|
+
const proto = Object.getPrototypeOf(s)
|
|
105
|
+
if (proto === Object.prototype || proto === null) args[0] = Object.assign({}, s, { sql: prefix + s.sql })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function wrapCall (op) {
|
|
110
|
+
return (orig) => function (s, values, cb) {
|
|
111
|
+
const t = c.dbTracer()
|
|
112
|
+
if (!t || c.nested() || s === null || s === undefined) return orig.apply(this, arguments)
|
|
113
|
+
let span = null
|
|
114
|
+
try {
|
|
115
|
+
const text = textOf(s)
|
|
116
|
+
const base = connTags(this)
|
|
117
|
+
const operation = sql.operation(text)
|
|
118
|
+
const tags = Object.assign({}, base)
|
|
119
|
+
if (operation) tags['db.operation'] = operation
|
|
120
|
+
const st = sql.statementTag(text, t.db.statement, true)
|
|
121
|
+
if (st !== undefined) tags['db.statement'] = st
|
|
122
|
+
const name = operation ? (base['db.name'] ? operation + ' ' + base['db.name'] : operation) : (base['db.name'] || 'mysql')
|
|
123
|
+
span = c.clientSpan(t, name, tags)
|
|
124
|
+
} catch (_) {
|
|
125
|
+
span = null
|
|
126
|
+
}
|
|
127
|
+
if (!span) return orig.apply(this, arguments)
|
|
128
|
+
const args = Array.prototype.slice.call(arguments)
|
|
129
|
+
try {
|
|
130
|
+
comment(t, span, s, args, op === 'execute')
|
|
131
|
+
} catch (_) {}
|
|
132
|
+
// callbacks run later from the socket: give them the caller's context
|
|
133
|
+
for (let i = 1; i < args.length; i++) if (typeof args[i] === 'function') args[i] = c.bind(args[i])
|
|
134
|
+
let cmd
|
|
135
|
+
try {
|
|
136
|
+
cmd = c.outer(orig, this, args)
|
|
137
|
+
} catch (err) {
|
|
138
|
+
c.end(span, err)
|
|
139
|
+
throw err
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
if (cmd && typeof cmd.emit === 'function') observe(cmd, span)
|
|
143
|
+
else c.end(span)
|
|
144
|
+
} catch (_) {
|
|
145
|
+
c.end(span)
|
|
146
|
+
}
|
|
147
|
+
return cmd
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function wrapGetConnection (orig) {
|
|
152
|
+
return function getConnection (cb) {
|
|
153
|
+
const t = c.tracer()
|
|
154
|
+
if (!t || typeof cb !== 'function') return orig.apply(this, arguments)
|
|
155
|
+
let span = null
|
|
156
|
+
try {
|
|
157
|
+
const cfg = this.config || {}
|
|
158
|
+
const limit = cfg.connectionLimit
|
|
159
|
+
const free = this._freeConnections
|
|
160
|
+
const all = this._allConnections
|
|
161
|
+
const freeCount = free ? (typeof free.length === 'number' ? free.length : 0) : 0
|
|
162
|
+
const allCount = all ? (typeof all.length === 'number' ? all.length : 0) : 0
|
|
163
|
+
if (t.db && t.db.poolSpans && c.currentSpan() && freeCount === 0 && limit > 0 && allCount >= limit) {
|
|
164
|
+
const q = this._connectionQueue
|
|
165
|
+
span = c.clientSpan(t, 'pool.acquire', {
|
|
166
|
+
'db.system': 'mysql',
|
|
167
|
+
'db.pool.size': String(allCount),
|
|
168
|
+
'db.pool.max_size': String(limit),
|
|
169
|
+
'db.pool.waiting': String(q && typeof q.length === 'number' ? q.length : 0)
|
|
170
|
+
}, 'INTERNAL')
|
|
171
|
+
}
|
|
172
|
+
} catch (_) {
|
|
173
|
+
span = null
|
|
174
|
+
}
|
|
175
|
+
const bound = c.bind(cb)
|
|
176
|
+
return orig.call(this, function (err) {
|
|
177
|
+
c.end(span, err || undefined)
|
|
178
|
+
return bound.apply(this, arguments)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** lib/base/connection.js (≥ 3.10) or lib/connection.js (older 3.x): query and execute. */
|
|
184
|
+
function patchConnection (exports) {
|
|
185
|
+
const Conn = exports && (exports.default || exports)
|
|
186
|
+
const proto = Conn && Conn.prototype
|
|
187
|
+
if (!proto || typeof proto.query !== 'function') {
|
|
188
|
+
c.disabled('mysql2', 'Connection.prototype.query not found')
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
if (!Object.prototype.hasOwnProperty.call(proto, 'query')) return // inherited from base/connection.js
|
|
192
|
+
c.wrap(proto, 'query', wrapCall('query'))
|
|
193
|
+
if (typeof proto.execute === 'function') c.wrap(proto, 'execute', wrapCall('execute'))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** lib/base/pool.js or lib/pool.js: getConnection. */
|
|
197
|
+
function patchPool (exports) {
|
|
198
|
+
const Pool = exports && (exports.default || exports)
|
|
199
|
+
const proto = Pool && Pool.prototype
|
|
200
|
+
if (!proto || typeof proto.getConnection !== 'function') return
|
|
201
|
+
if (!Object.prototype.hasOwnProperty.call(proto, 'getConnection')) return
|
|
202
|
+
c.wrap(proto, 'getConnection', wrapGetConnection)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { patchConnection, patchPool }
|