@depup/elastic-apm-node 4.15.0-depup.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/LICENSE +26 -0
- package/NOTICE.md +442 -0
- package/README.md +48 -0
- package/changes.json +78 -0
- package/index.d.ts +398 -0
- package/index.js +11 -0
- package/lib/InflightEventSet.js +53 -0
- package/lib/activation-method.js +119 -0
- package/lib/agent.js +941 -0
- package/lib/apm-client/apm-client.js +313 -0
- package/lib/apm-client/http-apm-client/CHANGELOG.md +271 -0
- package/lib/apm-client/http-apm-client/README.md +485 -0
- package/lib/apm-client/http-apm-client/central-config.js +41 -0
- package/lib/apm-client/http-apm-client/container-info.js +111 -0
- package/lib/apm-client/http-apm-client/detect-hostname.js +96 -0
- package/lib/apm-client/http-apm-client/index.js +1975 -0
- package/lib/apm-client/http-apm-client/logging.js +31 -0
- package/lib/apm-client/http-apm-client/ndjson.js +20 -0
- package/lib/apm-client/http-apm-client/truncate.js +434 -0
- package/lib/apm-client/noop-apm-client.js +73 -0
- package/lib/async-hooks-polyfill.js +58 -0
- package/lib/cloud-metadata/aws.js +175 -0
- package/lib/cloud-metadata/azure.js +123 -0
- package/lib/cloud-metadata/callback-coordination.js +159 -0
- package/lib/cloud-metadata/gcp.js +133 -0
- package/lib/cloud-metadata/index.js +175 -0
- package/lib/config/config.js +458 -0
- package/lib/config/normalizers.js +701 -0
- package/lib/config/schema.js +1007 -0
- package/lib/constants.js +35 -0
- package/lib/errors.js +303 -0
- package/lib/filters/sanitize-field-names.js +69 -0
- package/lib/http-request.js +249 -0
- package/lib/instrumentation/azure-functions.js +519 -0
- package/lib/instrumentation/context.js +56 -0
- package/lib/instrumentation/dropped-spans-stats.js +112 -0
- package/lib/instrumentation/elasticsearch-shared.js +63 -0
- package/lib/instrumentation/express-utils.js +91 -0
- package/lib/instrumentation/generic-span.js +322 -0
- package/lib/instrumentation/http-shared.js +424 -0
- package/lib/instrumentation/ids.js +39 -0
- package/lib/instrumentation/index.js +1127 -0
- package/lib/instrumentation/modules/@apollo/server.js +30 -0
- package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
- package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
- package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
- package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
- package/lib/instrumentation/modules/@opentelemetry/api.js +86 -0
- package/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +79 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
- package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
- package/lib/instrumentation/modules/_lambda-handler.js +40 -0
- package/lib/instrumentation/modules/apollo-server-core.js +49 -0
- package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
- package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
- package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
- package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
- package/lib/instrumentation/modules/aws-sdk.js +76 -0
- package/lib/instrumentation/modules/bluebird.js +93 -0
- package/lib/instrumentation/modules/cassandra-driver.js +280 -0
- package/lib/instrumentation/modules/elasticsearch.js +191 -0
- package/lib/instrumentation/modules/express-graphql.js +66 -0
- package/lib/instrumentation/modules/express-queue.js +28 -0
- package/lib/instrumentation/modules/express.js +162 -0
- package/lib/instrumentation/modules/fastify.js +172 -0
- package/lib/instrumentation/modules/finalhandler.js +41 -0
- package/lib/instrumentation/modules/generic-pool.js +85 -0
- package/lib/instrumentation/modules/graphql.js +256 -0
- package/lib/instrumentation/modules/handlebars.js +22 -0
- package/lib/instrumentation/modules/http.js +112 -0
- package/lib/instrumentation/modules/http2.js +320 -0
- package/lib/instrumentation/modules/https.js +68 -0
- package/lib/instrumentation/modules/ioredis.js +94 -0
- package/lib/instrumentation/modules/jade.js +18 -0
- package/lib/instrumentation/modules/kafkajs.js +476 -0
- package/lib/instrumentation/modules/knex.js +91 -0
- package/lib/instrumentation/modules/koa-router.js +74 -0
- package/lib/instrumentation/modules/koa.js +15 -0
- package/lib/instrumentation/modules/memcached.js +99 -0
- package/lib/instrumentation/modules/mimic-response.js +45 -0
- package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
- package/lib/instrumentation/modules/mongodb-core.js +206 -0
- package/lib/instrumentation/modules/mongodb.js +259 -0
- package/lib/instrumentation/modules/mysql.js +200 -0
- package/lib/instrumentation/modules/mysql2.js +140 -0
- package/lib/instrumentation/modules/pg.js +148 -0
- package/lib/instrumentation/modules/pug.js +18 -0
- package/lib/instrumentation/modules/redis.js +176 -0
- package/lib/instrumentation/modules/restify.js +52 -0
- package/lib/instrumentation/modules/tedious.js +159 -0
- package/lib/instrumentation/modules/undici.js +270 -0
- package/lib/instrumentation/modules/ws.js +59 -0
- package/lib/instrumentation/noop-transaction.js +81 -0
- package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
- package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
- package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
- package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
- package/lib/instrumentation/run-context/RunContext.js +151 -0
- package/lib/instrumentation/run-context/index.js +23 -0
- package/lib/instrumentation/shimmer.js +123 -0
- package/lib/instrumentation/span-compression.js +239 -0
- package/lib/instrumentation/span.js +621 -0
- package/lib/instrumentation/template-shared.js +43 -0
- package/lib/instrumentation/timer.js +84 -0
- package/lib/instrumentation/transaction.js +571 -0
- package/lib/lambda.js +992 -0
- package/lib/load-source-map.js +100 -0
- package/lib/logging.js +212 -0
- package/lib/metrics/index.js +92 -0
- package/lib/metrics/platforms/generic/index.js +40 -0
- package/lib/metrics/platforms/generic/process-cpu.js +22 -0
- package/lib/metrics/platforms/generic/process-top.js +157 -0
- package/lib/metrics/platforms/generic/stats.js +34 -0
- package/lib/metrics/platforms/generic/system-cpu.js +51 -0
- package/lib/metrics/platforms/linux/index.js +19 -0
- package/lib/metrics/platforms/linux/stats.js +213 -0
- package/lib/metrics/queue.js +90 -0
- package/lib/metrics/registry.js +52 -0
- package/lib/metrics/reporter.js +119 -0
- package/lib/metrics/runtime.js +77 -0
- package/lib/middleware/connect.js +16 -0
- package/lib/opentelemetry-bridge/OTelBridgeNonRecordingSpan.js +150 -0
- package/lib/opentelemetry-bridge/OTelBridgeRunContext.js +124 -0
- package/lib/opentelemetry-bridge/OTelContextManager.js +82 -0
- package/lib/opentelemetry-bridge/OTelSpan.js +344 -0
- package/lib/opentelemetry-bridge/OTelTracer.js +201 -0
- package/lib/opentelemetry-bridge/OTelTracerProvider.js +25 -0
- package/lib/opentelemetry-bridge/README.md +244 -0
- package/lib/opentelemetry-bridge/index.js +15 -0
- package/lib/opentelemetry-bridge/oblog.js +23 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/README.md +3 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/internal/validators.js +52 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/trace/TraceState.js +109 -0
- package/lib/opentelemetry-bridge/otelutils.js +99 -0
- package/lib/opentelemetry-bridge/setup.js +76 -0
- package/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +285 -0
- package/lib/opentelemetry-metrics/index.js +50 -0
- package/lib/parsers.js +225 -0
- package/lib/propwrap.js +147 -0
- package/lib/stacktraces.js +537 -0
- package/lib/symbols.js +15 -0
- package/lib/tracecontext/index.js +118 -0
- package/lib/tracecontext/traceparent.js +185 -0
- package/lib/tracecontext/tracestate.js +388 -0
- package/lib/wildcard-matcher.js +52 -0
- package/loader.mjs +7 -0
- package/package.json +299 -0
- package/start.d.ts +8 -0
- package/start.js +29 -0
- package/types/aws-lambda.d.ts +98 -0
- package/types/connect.d.ts +23 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const semver = require('semver');
|
|
10
|
+
|
|
11
|
+
const { OUTCOME_SUCCESS, OUTCOME_FAILURE } = require('../../constants');
|
|
12
|
+
const { getDBDestination } = require('../context');
|
|
13
|
+
const shimmer = require('../shimmer');
|
|
14
|
+
const kListenersAdded = Symbol('kListenersAdded');
|
|
15
|
+
|
|
16
|
+
// Match expected `<hostname>:<port>`, e.g. "mongo:27017", "::1:27017",
|
|
17
|
+
// "127.0.0.1:27017".
|
|
18
|
+
const HOSTNAME_PORT_RE = /^(.+):(\d+)$/;
|
|
19
|
+
|
|
20
|
+
module.exports = (mongodb, agent, { version, enabled }) => {
|
|
21
|
+
if (!enabled) return mongodb;
|
|
22
|
+
if (!semver.satisfies(version, '>=3.3 <7')) {
|
|
23
|
+
agent.logger.debug(
|
|
24
|
+
'mongodb version %s not instrumented (mongodb <3.3 is instrumented via mongodb-core)',
|
|
25
|
+
version,
|
|
26
|
+
);
|
|
27
|
+
return mongodb;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ins = agent._instrumentation;
|
|
31
|
+
|
|
32
|
+
const activeSpans = new Map();
|
|
33
|
+
if (mongodb.instrument) {
|
|
34
|
+
const listener = mongodb.instrument();
|
|
35
|
+
listener.on('started', onStart);
|
|
36
|
+
listener.on('succeeded', onSuccess);
|
|
37
|
+
listener.on('failed', onFailure);
|
|
38
|
+
} else if (mongodb.MongoClient) {
|
|
39
|
+
// mongodb 4.0+ removed the instrument() method in favor of
|
|
40
|
+
// listeners on the instantiated client objects. There are two mechanisms
|
|
41
|
+
// to get a client:
|
|
42
|
+
// 1. const client = new mongodb.MongoClient(...)
|
|
43
|
+
// 2. const client = await MongoClient.connect(...)
|
|
44
|
+
class MongoClientTraced extends mongodb.MongoClient {
|
|
45
|
+
constructor() {
|
|
46
|
+
// The `command*` events are only emitted if `options.monitorCommands: true`.
|
|
47
|
+
const args = Array.prototype.slice.call(arguments);
|
|
48
|
+
if (!args[1]) {
|
|
49
|
+
args[1] = { monitorCommands: true };
|
|
50
|
+
} else if (args[1].monitorCommands !== true) {
|
|
51
|
+
args[1] = Object.assign({}, args[1], { monitorCommands: true });
|
|
52
|
+
}
|
|
53
|
+
super(...args);
|
|
54
|
+
this.on('commandStarted', onStart);
|
|
55
|
+
this.on('commandSucceeded', onSuccess);
|
|
56
|
+
this.on('commandFailed', onFailure);
|
|
57
|
+
this[kListenersAdded] = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
Object.defineProperty(mongodb, 'MongoClient', {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
get: function () {
|
|
63
|
+
return MongoClientTraced;
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
shimmer.wrap(mongodb.MongoClient, 'connect', wrapConnect);
|
|
68
|
+
} else {
|
|
69
|
+
agent.logger.warn('could not instrument mongodb@%s', version);
|
|
70
|
+
}
|
|
71
|
+
return mongodb;
|
|
72
|
+
|
|
73
|
+
// Wrap the MongoClient.connect(url, options?, callback?) static method.
|
|
74
|
+
// It calls back with `function (err, client)` or returns a Promise that
|
|
75
|
+
// resolves to the client.
|
|
76
|
+
// https://github.com/mongodb/node-mongodb-native/blob/v4.2.1/src/mongo_client.ts#L503-L511
|
|
77
|
+
//
|
|
78
|
+
// From versions >=4.11.0 the method uses `new this` to create the client instance. Hence
|
|
79
|
+
// our `MongoClientTraced`'s constructor is used and the command listeners are already
|
|
80
|
+
// registered. We should check if the client has already added the listeners to avoid handling
|
|
81
|
+
// the same events twice
|
|
82
|
+
// NOTE: prefering to use a Symbol over version check since internal implementation may
|
|
83
|
+
// change in future versions
|
|
84
|
+
// https://github.com/mongodb/node-mongodb-native/blob/v4.11.0/src/mongo_client.ts#L618
|
|
85
|
+
function wrapConnect(origConnect) {
|
|
86
|
+
return function wrappedConnect(url, options, callback) {
|
|
87
|
+
if (typeof options === 'function') {
|
|
88
|
+
callback = options;
|
|
89
|
+
options = {};
|
|
90
|
+
}
|
|
91
|
+
options = options || {};
|
|
92
|
+
if (!options.monitorCommands) {
|
|
93
|
+
options.monitorCommands = true;
|
|
94
|
+
}
|
|
95
|
+
if (typeof callback === 'function') {
|
|
96
|
+
return origConnect.call(
|
|
97
|
+
this,
|
|
98
|
+
url,
|
|
99
|
+
options,
|
|
100
|
+
function wrappedCallback(err, client) {
|
|
101
|
+
if (err) {
|
|
102
|
+
callback(err);
|
|
103
|
+
} else {
|
|
104
|
+
if (!client[kListenersAdded]) {
|
|
105
|
+
client.on('commandStarted', onStart);
|
|
106
|
+
client.on('commandSucceeded', onSuccess);
|
|
107
|
+
client.on('commandFailed', onFailure);
|
|
108
|
+
client[kListenersAdded] = true;
|
|
109
|
+
}
|
|
110
|
+
callback(err, client);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
const p = origConnect.call(this, url, options, callback);
|
|
116
|
+
p.then((client) => {
|
|
117
|
+
if (!client[kListenersAdded]) {
|
|
118
|
+
client.on('commandStarted', onStart);
|
|
119
|
+
client.on('commandSucceeded', onSuccess);
|
|
120
|
+
client.on('commandFailed', onFailure);
|
|
121
|
+
client[kListenersAdded] = true;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return p;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function onStart(event) {
|
|
130
|
+
// `event` is a `CommandStartedEvent`
|
|
131
|
+
// https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst#command-started-message
|
|
132
|
+
// E.g. with mongodb@6.2.0:
|
|
133
|
+
// CommandStartedEvent {
|
|
134
|
+
// connectionId: '127.0.0.1:27017',
|
|
135
|
+
// requestId: 1,
|
|
136
|
+
// databaseName: 'test',
|
|
137
|
+
// commandName: 'insert',
|
|
138
|
+
// command:
|
|
139
|
+
// { ... } }
|
|
140
|
+
const name = [
|
|
141
|
+
event.databaseName,
|
|
142
|
+
collectionFor(event),
|
|
143
|
+
event.commandName,
|
|
144
|
+
].join('.');
|
|
145
|
+
|
|
146
|
+
const span = ins.createSpan(name, 'db', 'mongodb', event.commandName, {
|
|
147
|
+
exitSpan: true,
|
|
148
|
+
});
|
|
149
|
+
if (span) {
|
|
150
|
+
activeSpans.set(event.requestId, span);
|
|
151
|
+
|
|
152
|
+
// Destination context.
|
|
153
|
+
// Per the following code it looks like "<hostname>:<port>" should be
|
|
154
|
+
// available via the `address` or `connectionId` field.
|
|
155
|
+
// https://github.com/mongodb/node-mongodb-native/blob/dd356f0ede/lib/core/connection/apm.js#L155-L169
|
|
156
|
+
const address = event.address || event.connectionId;
|
|
157
|
+
let match;
|
|
158
|
+
if (
|
|
159
|
+
address &&
|
|
160
|
+
typeof address === 'string' &&
|
|
161
|
+
(match = HOSTNAME_PORT_RE.exec(address))
|
|
162
|
+
) {
|
|
163
|
+
span._setDestinationContext(getDBDestination(match[1], match[2]));
|
|
164
|
+
} else {
|
|
165
|
+
agent.logger.trace(
|
|
166
|
+
'could not set destination context on mongodb span from address=%j',
|
|
167
|
+
address,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const dbContext = { type: 'mongodb', instance: event.databaseName };
|
|
172
|
+
span.setDbContext(dbContext);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function onSuccess(event) {
|
|
177
|
+
// `event` is a `CommandSucceededEvent`
|
|
178
|
+
// https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst#command-succeeded-message
|
|
179
|
+
// E.g. with mongodb@6.2.0:
|
|
180
|
+
// CommandSucceededEvent {
|
|
181
|
+
// connectionId: '127.0.0.1:27017',
|
|
182
|
+
// requestId: 1,
|
|
183
|
+
// duration: 1,
|
|
184
|
+
// reply: { ... }
|
|
185
|
+
// }
|
|
186
|
+
if (!activeSpans.has(event.requestId)) return;
|
|
187
|
+
|
|
188
|
+
const span = activeSpans.get(event.requestId);
|
|
189
|
+
activeSpans.delete(event.requestId);
|
|
190
|
+
|
|
191
|
+
// From mongodb@3.6.0 and up some commands like `deleteOne` may contain
|
|
192
|
+
// error data inside the `reply` property. It makes sense to capture it.
|
|
193
|
+
const writeErrors = event?.reply?.writeErrors;
|
|
194
|
+
|
|
195
|
+
if (writeErrors && writeErrors.length) {
|
|
196
|
+
agent.captureError(writeErrors[0].errmsg, {
|
|
197
|
+
skipOutcome: true,
|
|
198
|
+
parent: span,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
span.setOutcome(OUTCOME_SUCCESS);
|
|
202
|
+
span.end(span._timer.start / 1000 + event.duration);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function onFailure(event) {
|
|
206
|
+
// `event` is a `CommandFailedEvent`
|
|
207
|
+
// https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst#command-failed-message
|
|
208
|
+
// E.g. with mongodb@6.2.0:
|
|
209
|
+
// CommandFailedEvent {
|
|
210
|
+
// connectionId: '127.0.0.1:27017',
|
|
211
|
+
// requestId: 6,
|
|
212
|
+
// commandName: "find",
|
|
213
|
+
// duration: 1,
|
|
214
|
+
// "failure": { ... }
|
|
215
|
+
// }
|
|
216
|
+
if (!activeSpans.has(event.requestId)) return;
|
|
217
|
+
|
|
218
|
+
const span = activeSpans.get(event.requestId);
|
|
219
|
+
activeSpans.delete(event.requestId);
|
|
220
|
+
span.setOutcome(OUTCOME_FAILURE);
|
|
221
|
+
span.end(span._timer.start / 1000 + event.duration);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function collectionFor(event) {
|
|
225
|
+
// `getMore` command is a special case where the collection name is
|
|
226
|
+
// placed in a property named `collection`. The types exported
|
|
227
|
+
// do not ensure the property is there so we are defensive on its
|
|
228
|
+
// retrieval.
|
|
229
|
+
//
|
|
230
|
+
// Example of `getMore` payload:
|
|
231
|
+
// {
|
|
232
|
+
// event: CommandStartedEvent {
|
|
233
|
+
// name: 'commandStarted',
|
|
234
|
+
// address: '172.20.0.2:27017',
|
|
235
|
+
// connectionId: 12,
|
|
236
|
+
// requestId: 686,
|
|
237
|
+
// databaseName: 'mydatabase',
|
|
238
|
+
// commandName: 'getMore',
|
|
239
|
+
// command: {
|
|
240
|
+
// getMore: new Long('1769182660590360229'),
|
|
241
|
+
// collection: 'Interaction',
|
|
242
|
+
// ...
|
|
243
|
+
// }
|
|
244
|
+
// },
|
|
245
|
+
// commandName: 'getMore',
|
|
246
|
+
// collection: new Long('1769182660590360229')
|
|
247
|
+
// }
|
|
248
|
+
// ref: https://github.com/elastic/apm-agent-nodejs/issues/3834
|
|
249
|
+
if (
|
|
250
|
+
event.commandName === 'getMore' &&
|
|
251
|
+
typeof event.command.collection === 'string'
|
|
252
|
+
) {
|
|
253
|
+
return event.command.collection;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const collection = event.command[event.commandName];
|
|
257
|
+
return typeof collection === 'string' ? collection : '$cmd';
|
|
258
|
+
}
|
|
259
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const EventEmitter = require('events');
|
|
10
|
+
|
|
11
|
+
var semver = require('semver');
|
|
12
|
+
var sqlSummary = require('sql-summary');
|
|
13
|
+
|
|
14
|
+
var shimmer = require('../shimmer');
|
|
15
|
+
var symbols = require('../../symbols');
|
|
16
|
+
var { getDBDestination } = require('../context');
|
|
17
|
+
|
|
18
|
+
module.exports = function (mysql, agent, { version, enabled }) {
|
|
19
|
+
if (!enabled) {
|
|
20
|
+
return mysql;
|
|
21
|
+
}
|
|
22
|
+
if (!semver.satisfies(version, '^2.0.0')) {
|
|
23
|
+
agent.logger.debug('mysql version %s not supported - aborting...', version);
|
|
24
|
+
return mysql;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
agent.logger.debug('shimming mysql.createPool');
|
|
28
|
+
shimmer.wrap(mysql, 'createPool', wrapCreatePool);
|
|
29
|
+
|
|
30
|
+
agent.logger.debug('shimming mysql.createPoolCluster');
|
|
31
|
+
shimmer.wrap(mysql, 'createPoolCluster', wrapCreatePoolCluster);
|
|
32
|
+
|
|
33
|
+
agent.logger.debug('shimming mysql.createConnection');
|
|
34
|
+
shimmer.wrap(mysql, 'createConnection', wrapCreateConnection);
|
|
35
|
+
|
|
36
|
+
return mysql;
|
|
37
|
+
|
|
38
|
+
function wrapCreateConnection(original) {
|
|
39
|
+
return function wrappedCreateConnection() {
|
|
40
|
+
var connection = original.apply(this, arguments);
|
|
41
|
+
|
|
42
|
+
wrapQueryable(connection, 'connection', agent);
|
|
43
|
+
|
|
44
|
+
return connection;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function wrapCreatePool(original) {
|
|
49
|
+
return function wrappedCreatePool() {
|
|
50
|
+
var pool = original.apply(this, arguments);
|
|
51
|
+
|
|
52
|
+
agent.logger.debug('shimming mysql pool.getConnection');
|
|
53
|
+
shimmer.wrap(pool, 'getConnection', wrapGetConnection);
|
|
54
|
+
|
|
55
|
+
return pool;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function wrapCreatePoolCluster(original) {
|
|
60
|
+
return function wrappedCreatePoolCluster() {
|
|
61
|
+
var cluster = original.apply(this, arguments);
|
|
62
|
+
|
|
63
|
+
agent.logger.debug('shimming mysql cluster.of');
|
|
64
|
+
shimmer.wrap(cluster, 'of', function wrapOf(original) {
|
|
65
|
+
return function wrappedOf() {
|
|
66
|
+
var ofCluster = original.apply(this, arguments);
|
|
67
|
+
|
|
68
|
+
agent.logger.debug('shimming mysql cluster of.getConnection');
|
|
69
|
+
shimmer.wrap(ofCluster, 'getConnection', wrapGetConnection);
|
|
70
|
+
|
|
71
|
+
return ofCluster;
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return cluster;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function wrapGetConnection(original) {
|
|
80
|
+
return function wrappedGetConnection() {
|
|
81
|
+
var cb = arguments[0];
|
|
82
|
+
|
|
83
|
+
if (typeof cb === 'function') {
|
|
84
|
+
arguments[0] = agent._instrumentation.bindFunction(
|
|
85
|
+
function wrapedCallback(_err, connection) {
|
|
86
|
+
if (connection)
|
|
87
|
+
wrapQueryable(connection, 'getConnection() > connection', agent);
|
|
88
|
+
return cb.apply(this, arguments);
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return original.apply(this, arguments);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function wrapQueryable(connection, objType, agent) {
|
|
99
|
+
const ins = agent._instrumentation;
|
|
100
|
+
|
|
101
|
+
agent.logger.debug('shimming mysql %s.query', objType);
|
|
102
|
+
shimmer.wrap(connection, 'query', wrapQuery);
|
|
103
|
+
|
|
104
|
+
let host, port, user, database;
|
|
105
|
+
if (typeof connection.config === 'object') {
|
|
106
|
+
({ host, port, user, database } = connection.config);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function wrapQuery(original) {
|
|
110
|
+
return function wrappedQuery(sql, values, cb) {
|
|
111
|
+
agent.logger.debug('intercepted call to mysql %s.query', objType);
|
|
112
|
+
|
|
113
|
+
var span = ins.createSpan(null, 'db', 'mysql', 'query', {
|
|
114
|
+
exitSpan: true,
|
|
115
|
+
});
|
|
116
|
+
if (!span) {
|
|
117
|
+
return original.apply(this, arguments);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
var hasCallback = false;
|
|
121
|
+
var sqlStr;
|
|
122
|
+
|
|
123
|
+
if (this[symbols.knexStackObj]) {
|
|
124
|
+
span.customStackTrace(this[symbols.knexStackObj]);
|
|
125
|
+
this[symbols.knexStackObj] = null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const wrapCallback = function (origCallback) {
|
|
129
|
+
hasCallback = true;
|
|
130
|
+
return ins.bindFunction(function wrappedCallback(_err) {
|
|
131
|
+
span.end();
|
|
132
|
+
return origCallback.apply(this, arguments);
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
switch (typeof sql) {
|
|
137
|
+
case 'string':
|
|
138
|
+
sqlStr = sql;
|
|
139
|
+
break;
|
|
140
|
+
case 'object':
|
|
141
|
+
if (typeof sql._callback === 'function') {
|
|
142
|
+
sql._callback = wrapCallback(sql._callback);
|
|
143
|
+
}
|
|
144
|
+
sqlStr = sql.sql;
|
|
145
|
+
break;
|
|
146
|
+
case 'function':
|
|
147
|
+
arguments[0] = wrapCallback(sql);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (sqlStr) {
|
|
152
|
+
agent.logger.debug({ sql: sqlStr }, 'extracted sql from mysql query');
|
|
153
|
+
span.setDbContext({
|
|
154
|
+
statement: sqlStr,
|
|
155
|
+
type: 'sql',
|
|
156
|
+
user,
|
|
157
|
+
instance: database,
|
|
158
|
+
});
|
|
159
|
+
span.name = sqlSummary(sqlStr);
|
|
160
|
+
}
|
|
161
|
+
span._setDestinationContext(getDBDestination(host, port));
|
|
162
|
+
|
|
163
|
+
if (typeof values === 'function') {
|
|
164
|
+
arguments[1] = wrapCallback(values);
|
|
165
|
+
} else if (typeof cb === 'function') {
|
|
166
|
+
arguments[2] = wrapCallback(cb);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const spanRunContext = ins.currRunContext().enterSpan(span);
|
|
170
|
+
const result = ins.withRunContext(
|
|
171
|
+
spanRunContext,
|
|
172
|
+
original,
|
|
173
|
+
this,
|
|
174
|
+
...arguments,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (!hasCallback && result instanceof EventEmitter) {
|
|
178
|
+
// Wrap `result.emit` instead of `result.once('error', ...)` to avoid
|
|
179
|
+
// changing app behaviour by possibly setting the only 'error' handler.
|
|
180
|
+
shimmer.wrap(result, 'emit', function (origEmit) {
|
|
181
|
+
return function wrappedEmit(event, data) {
|
|
182
|
+
// The 'mysql' module emits 'end' even after an 'error' event.
|
|
183
|
+
switch (event) {
|
|
184
|
+
case 'error':
|
|
185
|
+
break;
|
|
186
|
+
case 'end':
|
|
187
|
+
span.end();
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
return origEmit.apply(this, arguments);
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
// Ensure event handlers execute in the caller run context.
|
|
194
|
+
ins.bindEmitter(result);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var semver = require('semver');
|
|
10
|
+
var sqlSummary = require('sql-summary');
|
|
11
|
+
|
|
12
|
+
var shimmer = require('../shimmer');
|
|
13
|
+
var symbols = require('../../symbols');
|
|
14
|
+
var { getDBDestination } = require('../context');
|
|
15
|
+
|
|
16
|
+
module.exports = function (mysql2, agent, { version, enabled }) {
|
|
17
|
+
if (!enabled) {
|
|
18
|
+
return mysql2;
|
|
19
|
+
}
|
|
20
|
+
if (!semver.satisfies(version, '>=1 <4')) {
|
|
21
|
+
agent.logger.debug(
|
|
22
|
+
'mysql2 version %s not supported - aborting...',
|
|
23
|
+
version,
|
|
24
|
+
);
|
|
25
|
+
return mysql2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var ins = agent._instrumentation;
|
|
29
|
+
|
|
30
|
+
// mysql2@3.11.5 added BaseConnection class which is extended by Connection
|
|
31
|
+
// but is not in the public API so we need to extract it via prototype chain
|
|
32
|
+
// ref: https://github.com/sidorares/node-mysql2/pull/3081
|
|
33
|
+
const baseClass = Object.getPrototypeOf(mysql2.Connection);
|
|
34
|
+
const baseProto = baseClass.prototype;
|
|
35
|
+
const hasQuery = typeof baseProto?.query === 'function';
|
|
36
|
+
const hasExec = typeof baseProto?.execute === 'function';
|
|
37
|
+
const shouldPatchBase = hasQuery && hasExec;
|
|
38
|
+
|
|
39
|
+
if (shouldPatchBase) {
|
|
40
|
+
shimmer.wrap(baseProto, 'query', wrapQuery);
|
|
41
|
+
shimmer.wrap(baseProto, 'execute', wrapQuery);
|
|
42
|
+
} else {
|
|
43
|
+
shimmer.wrap(mysql2.Connection.prototype, 'query', wrapQuery);
|
|
44
|
+
shimmer.wrap(mysql2.Connection.prototype, 'execute', wrapQuery);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return mysql2;
|
|
48
|
+
|
|
49
|
+
function wrapQuery(original) {
|
|
50
|
+
return function wrappedQuery(sql, values, cb) {
|
|
51
|
+
agent.logger.debug('intercepted call to mysql2.%s', original.name);
|
|
52
|
+
|
|
53
|
+
var span = ins.createSpan(null, 'db', 'mysql', 'query', {
|
|
54
|
+
exitSpan: true,
|
|
55
|
+
});
|
|
56
|
+
if (!span) {
|
|
57
|
+
return original.apply(this, arguments);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (this[symbols.knexStackObj]) {
|
|
61
|
+
span.customStackTrace(this[symbols.knexStackObj]);
|
|
62
|
+
this[symbols.knexStackObj] = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let hasCallback = false;
|
|
66
|
+
const wrapCallback = function (origCallback) {
|
|
67
|
+
hasCallback = true;
|
|
68
|
+
return ins.bindFunction(function wrappedCallback(_err) {
|
|
69
|
+
span.end();
|
|
70
|
+
return origCallback.apply(this, arguments);
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let host, port, user, database;
|
|
75
|
+
if (typeof this.config === 'object') {
|
|
76
|
+
({ host, port, user, database } = this.config);
|
|
77
|
+
}
|
|
78
|
+
span._setDestinationContext(getDBDestination(host, port));
|
|
79
|
+
|
|
80
|
+
let sqlStr;
|
|
81
|
+
switch (typeof sql) {
|
|
82
|
+
case 'string':
|
|
83
|
+
sqlStr = sql;
|
|
84
|
+
break;
|
|
85
|
+
case 'object':
|
|
86
|
+
// `mysql2.{query,execute}` support the only arg being an instance
|
|
87
|
+
// of the internal mysql2 `Command` object.
|
|
88
|
+
if (typeof sql.onResult === 'function') {
|
|
89
|
+
sql.onResult = wrapCallback(sql.onResult);
|
|
90
|
+
}
|
|
91
|
+
sqlStr = sql.sql;
|
|
92
|
+
break;
|
|
93
|
+
case 'function':
|
|
94
|
+
arguments[0] = wrapCallback(sql);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
if (sqlStr) {
|
|
98
|
+
span.setDbContext({
|
|
99
|
+
type: 'sql',
|
|
100
|
+
instance: database,
|
|
101
|
+
user,
|
|
102
|
+
statement: sqlStr,
|
|
103
|
+
});
|
|
104
|
+
span.name = sqlSummary(sqlStr);
|
|
105
|
+
} else {
|
|
106
|
+
span.setDbContext({ type: 'sql', instance: database, user });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof values === 'function') {
|
|
110
|
+
arguments[1] = wrapCallback(values);
|
|
111
|
+
} else if (typeof cb === 'function') {
|
|
112
|
+
arguments[2] = wrapCallback(cb);
|
|
113
|
+
}
|
|
114
|
+
const spanRunContext = ins.currRunContext().enterSpan(span);
|
|
115
|
+
const result = ins.withRunContext(
|
|
116
|
+
spanRunContext,
|
|
117
|
+
original,
|
|
118
|
+
this,
|
|
119
|
+
...arguments,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (result && !hasCallback) {
|
|
123
|
+
ins.bindEmitter(result);
|
|
124
|
+
shimmer.wrap(result, 'emit', function (origEmit) {
|
|
125
|
+
return function (event) {
|
|
126
|
+
switch (event) {
|
|
127
|
+
case 'error':
|
|
128
|
+
case 'close':
|
|
129
|
+
case 'end':
|
|
130
|
+
span.end();
|
|
131
|
+
}
|
|
132
|
+
return origEmit.apply(this, arguments);
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return result;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
};
|