@redthreadlabs/tracelog 1.4.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/README.md +126 -0
- package/index.d.ts +464 -0
- package/index.js +11 -0
- package/lib/InflightEventSet.js +53 -0
- package/lib/activation-method.js +97 -0
- package/lib/agent.js +1226 -0
- package/lib/apm-client/apm-client.js +107 -0
- package/lib/apm-client/channel-writer.js +334 -0
- package/lib/apm-client/jsonl-file-client.js +241 -0
- package/lib/apm-client/ndjson.js +20 -0
- package/lib/apm-client/noop-apm-client.js +79 -0
- package/lib/apm-client/s3-uploader.js +308 -0
- package/lib/apm-client/truncate.js +507 -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 +431 -0
- package/lib/config/normalizers.js +649 -0
- package/lib/config/schema.js +946 -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/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 +1078 -0
- package/lib/instrumentation/modules/@apollo/server.js +39 -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/@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/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 +200 -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 +179 -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 +33 -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 +29 -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 +100 -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 +29 -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/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/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 +115 -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 +98 -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,100 @@
|
|
|
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
|
+
|
|
11
|
+
var shimmer = require('../shimmer');
|
|
12
|
+
var { getDBDestination } = require('../context');
|
|
13
|
+
|
|
14
|
+
module.exports = function (memcached, agent, { version, enabled }) {
|
|
15
|
+
if (!enabled) {
|
|
16
|
+
return memcached;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!semver.satisfies(version, '>=2.2.0 <3')) {
|
|
20
|
+
agent.logger.debug(
|
|
21
|
+
'memcached version %s not supported, skipping memcached instrumentation',
|
|
22
|
+
version,
|
|
23
|
+
);
|
|
24
|
+
return memcached;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ins = agent._instrumentation;
|
|
28
|
+
|
|
29
|
+
agent.logger.debug('shimming memcached.prototype.command');
|
|
30
|
+
shimmer.wrap(memcached.prototype, 'command', wrapCommand);
|
|
31
|
+
shimmer.wrap(memcached.prototype, 'connect', wrapConnect);
|
|
32
|
+
return memcached;
|
|
33
|
+
|
|
34
|
+
function wrapConnect(original) {
|
|
35
|
+
return function wrappedConnect() {
|
|
36
|
+
const currentSpan = ins.currSpan();
|
|
37
|
+
const server = arguments[0];
|
|
38
|
+
agent.logger.debug('intercepted call to memcached.prototype.connect %o', {
|
|
39
|
+
server,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (currentSpan) {
|
|
43
|
+
const [host, port = 11211] = server.split(':');
|
|
44
|
+
currentSpan._setDestinationContext(getDBDestination(host, port));
|
|
45
|
+
}
|
|
46
|
+
return original.apply(this, arguments);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Wrap the generic command that is used to build touch, get, gets etc
|
|
51
|
+
function wrapCommand(original) {
|
|
52
|
+
return function wrappedCommand(queryCompiler, _server) {
|
|
53
|
+
if (typeof queryCompiler !== 'function') {
|
|
54
|
+
return original.apply(this, arguments);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var query = queryCompiler();
|
|
58
|
+
// Replace the queryCompiler function so it isn't called a second time.
|
|
59
|
+
arguments[0] = function prerunQueryCompiler() {
|
|
60
|
+
return query;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// If the callback is not a function the user doesn't care about result.
|
|
64
|
+
if (!query && typeof query.callback !== 'function') {
|
|
65
|
+
return original.apply(this, arguments);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const span = ins.createSpan(
|
|
69
|
+
`memcached.${query.type}`,
|
|
70
|
+
'db',
|
|
71
|
+
'memcached',
|
|
72
|
+
query.type,
|
|
73
|
+
{ exitSpan: true },
|
|
74
|
+
);
|
|
75
|
+
if (!span) {
|
|
76
|
+
return original.apply(this, arguments);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
agent.logger.debug('intercepted call to memcached.prototype.command %o', {
|
|
80
|
+
id: span.id,
|
|
81
|
+
type: query.type,
|
|
82
|
+
});
|
|
83
|
+
span.setDbContext({
|
|
84
|
+
statement: `${query.type} ${query.key}`,
|
|
85
|
+
type: 'memcached',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const spanRunContext = ins.currRunContext().enterSpan(span);
|
|
89
|
+
const origCallback = query.callback;
|
|
90
|
+
query.callback = ins.bindFunctionToRunContext(
|
|
91
|
+
spanRunContext,
|
|
92
|
+
function tracedCallback() {
|
|
93
|
+
span.end();
|
|
94
|
+
return origCallback.apply(this, arguments);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
return ins.withRunContext(spanRunContext, original, this, ...arguments);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
/*
|
|
10
|
+
* This instrumentation exists to work around an issue in mimic-response@1.0.0
|
|
11
|
+
* that was fixed in mimic-response@1.0.1.
|
|
12
|
+
* See https://github.com/elastic/apm-agent-nodejs/issues/423.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
var semver = require('semver');
|
|
16
|
+
|
|
17
|
+
module.exports = function (mimicResponse, agent, { version, enabled }) {
|
|
18
|
+
if (!enabled) return mimicResponse;
|
|
19
|
+
|
|
20
|
+
if (semver.gte(version, '1.0.1')) {
|
|
21
|
+
agent.logger.debug(
|
|
22
|
+
"mimic-response version %s doesn't need to be patched - ignoring...",
|
|
23
|
+
version,
|
|
24
|
+
);
|
|
25
|
+
return mimicResponse;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var ins = agent._instrumentation;
|
|
29
|
+
|
|
30
|
+
return function wrappedMimicResponse(fromStream, toStream) {
|
|
31
|
+
// If we bound the `fromStream` emitter, but not the `toStream` emitter, we
|
|
32
|
+
// need to do so as else the `on`, `addListener`, and `prependListener`
|
|
33
|
+
// functions of the `fromStream` will be copied over to the `toStream` but
|
|
34
|
+
// run in the context of the `fromStream`.
|
|
35
|
+
if (
|
|
36
|
+
fromStream &&
|
|
37
|
+
toStream &&
|
|
38
|
+
ins.isEventEmitterBound(fromStream) &&
|
|
39
|
+
!ins.isEventEmitterBound(toStream)
|
|
40
|
+
) {
|
|
41
|
+
ins.bindEmitter(toStream);
|
|
42
|
+
}
|
|
43
|
+
return mimicResponse.apply(null, arguments);
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { AsyncResource } = require('../../../../../async-hooks-polyfill');
|
|
10
|
+
|
|
11
|
+
const semver = require('semver');
|
|
12
|
+
|
|
13
|
+
module.exports = (mod, agent, { version, enabled }) => {
|
|
14
|
+
if (!enabled) return mod;
|
|
15
|
+
if (!semver.satisfies(version, '>=3.3 <6.4.0')) {
|
|
16
|
+
// - mongodb <3.3 is instrumented via mongodb-core
|
|
17
|
+
// - mongodb >=6.4.0 now longer requires ConnectionPool#checkOut to be
|
|
18
|
+
// patched to fix async context tracking. See discussion at
|
|
19
|
+
// https://github.com/elastic/apm-agent-nodejs/pull/3897
|
|
20
|
+
return mod;
|
|
21
|
+
}
|
|
22
|
+
agent.logger.debug('instrumenting mongodb ConnectionPool#checkOut');
|
|
23
|
+
|
|
24
|
+
if (mod.ConnectionPool) {
|
|
25
|
+
class ConnectionPoolTraced extends mod.ConnectionPool {
|
|
26
|
+
checkOut(callback) {
|
|
27
|
+
return super.checkOut(AsyncResource.bind(callback));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Object.defineProperty(mod, 'ConnectionPool', {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get: function () {
|
|
34
|
+
return ConnectionPoolTraced;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return mod;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
|
|
11
|
+
const { getDBDestination } = require('../context');
|
|
12
|
+
var shimmer = require('../shimmer');
|
|
13
|
+
|
|
14
|
+
var SERVER_FNS = ['insert', 'update', 'remove', 'auth'];
|
|
15
|
+
var CURSOR_FNS_FIRST = ['next', '_getmore'];
|
|
16
|
+
|
|
17
|
+
const firstSpan = Symbol('first-span');
|
|
18
|
+
|
|
19
|
+
module.exports = function (mongodb, agent, { version, enabled }) {
|
|
20
|
+
if (!enabled) return mongodb;
|
|
21
|
+
if (!semver.satisfies(version, '>=1.2.19 <4')) {
|
|
22
|
+
agent.logger.debug(
|
|
23
|
+
'mongodb-core version %s not supported - aborting...',
|
|
24
|
+
version,
|
|
25
|
+
);
|
|
26
|
+
return mongodb;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ins = agent._instrumentation;
|
|
30
|
+
|
|
31
|
+
if (mongodb.Server) {
|
|
32
|
+
agent.logger.debug('shimming mongodb-core.Server.prototype.command');
|
|
33
|
+
shimmer.wrap(mongodb.Server.prototype, 'command', wrapCommand);
|
|
34
|
+
agent.logger.debug(
|
|
35
|
+
'shimming mongodb-core.Server.prototype functions: %j',
|
|
36
|
+
SERVER_FNS,
|
|
37
|
+
);
|
|
38
|
+
shimmer.massWrap(mongodb.Server.prototype, SERVER_FNS, wrapQuery);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (mongodb.Cursor) {
|
|
42
|
+
agent.logger.debug(
|
|
43
|
+
'shimming mongodb-core.Cursor.prototype functions: %j',
|
|
44
|
+
CURSOR_FNS_FIRST,
|
|
45
|
+
);
|
|
46
|
+
shimmer.massWrap(mongodb.Cursor.prototype, CURSOR_FNS_FIRST, wrapCursor);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return mongodb;
|
|
50
|
+
|
|
51
|
+
function wrapCommand(orig) {
|
|
52
|
+
return function wrappedFunction(ns, cmd) {
|
|
53
|
+
var trans = agent._instrumentation.currTransaction();
|
|
54
|
+
var id = trans && trans.id;
|
|
55
|
+
var span;
|
|
56
|
+
|
|
57
|
+
agent.logger.debug(
|
|
58
|
+
'intercepted call to mongodb-core.Server.prototype.command %o',
|
|
59
|
+
{ id, ns },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (trans && arguments.length > 0) {
|
|
63
|
+
var index = arguments.length - 1;
|
|
64
|
+
var cb = arguments[index];
|
|
65
|
+
if (typeof cb === 'function') {
|
|
66
|
+
var type;
|
|
67
|
+
if (cmd.findAndModify) type = 'findAndModify';
|
|
68
|
+
else if (cmd.createIndexes) type = 'createIndexes';
|
|
69
|
+
else if (cmd.ismaster) type = 'ismaster';
|
|
70
|
+
else if (cmd.count) type = 'count';
|
|
71
|
+
else type = 'command';
|
|
72
|
+
|
|
73
|
+
span = ins.createSpan(ns + '.' + type, 'db', 'mongodb', 'query', {
|
|
74
|
+
exitSpan: true,
|
|
75
|
+
});
|
|
76
|
+
if (span) {
|
|
77
|
+
span.setDbContext({ type: 'mongodb', instance: ns });
|
|
78
|
+
arguments[index] = ins.bindFunctionToRunContext(
|
|
79
|
+
ins.currRunContext(),
|
|
80
|
+
wrappedCallback,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return orig.apply(this, arguments);
|
|
87
|
+
|
|
88
|
+
function wrappedCallback(_err, commandResult) {
|
|
89
|
+
agent.logger.debug(
|
|
90
|
+
'intercepted mongodb-core.Server.prototype.command callback %o',
|
|
91
|
+
{ id },
|
|
92
|
+
);
|
|
93
|
+
if (commandResult && commandResult.connection) {
|
|
94
|
+
span._setDestinationContext(
|
|
95
|
+
getDBDestination(
|
|
96
|
+
commandResult.connection.host,
|
|
97
|
+
commandResult.connection.port,
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
span.end();
|
|
102
|
+
return cb.apply(this, arguments);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function wrapQuery(orig, name) {
|
|
108
|
+
return function wrappedFunction(ns) {
|
|
109
|
+
var trans = agent._instrumentation.currTransaction();
|
|
110
|
+
var id = trans && trans.id;
|
|
111
|
+
var span;
|
|
112
|
+
|
|
113
|
+
agent.logger.debug(
|
|
114
|
+
'intercepted call to mongodb-core.Server.prototype.%s %o',
|
|
115
|
+
name,
|
|
116
|
+
{ id, ns },
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (trans && arguments.length > 0) {
|
|
120
|
+
var index = arguments.length - 1;
|
|
121
|
+
var cb = arguments[index];
|
|
122
|
+
if (typeof cb === 'function') {
|
|
123
|
+
span = ins.createSpan(ns + '.' + name, 'db', 'mongodb', 'query', {
|
|
124
|
+
exitSpan: true,
|
|
125
|
+
});
|
|
126
|
+
if (span) {
|
|
127
|
+
span.setDbContext({ type: 'mongodb', instance: ns });
|
|
128
|
+
arguments[index] = ins.bindFunctionToRunContext(
|
|
129
|
+
ins.currRunContext(),
|
|
130
|
+
wrappedCallback,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return orig.apply(this, arguments);
|
|
137
|
+
|
|
138
|
+
function wrappedCallback(_err, commandResult) {
|
|
139
|
+
agent.logger.debug(
|
|
140
|
+
'intercepted mongodb-core.Server.prototype.%s callback %o',
|
|
141
|
+
name,
|
|
142
|
+
{ id },
|
|
143
|
+
);
|
|
144
|
+
if (commandResult && commandResult.connection) {
|
|
145
|
+
span._setDestinationContext(
|
|
146
|
+
getDBDestination(
|
|
147
|
+
commandResult.connection.host,
|
|
148
|
+
commandResult.connection.port,
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
span.end();
|
|
153
|
+
return cb.apply(this, arguments);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function wrapCursor(orig, name) {
|
|
158
|
+
return function wrappedFunction() {
|
|
159
|
+
var trans = agent._instrumentation.currTransaction();
|
|
160
|
+
var id = trans && trans.id;
|
|
161
|
+
var span;
|
|
162
|
+
|
|
163
|
+
agent.logger.debug(
|
|
164
|
+
'intercepted call to mongodb-core.Cursor.prototype.%s %o',
|
|
165
|
+
name,
|
|
166
|
+
{ id },
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (trans && arguments.length > 0) {
|
|
170
|
+
var cb = arguments[0];
|
|
171
|
+
if (typeof cb === 'function') {
|
|
172
|
+
if (name !== 'next' || !this[firstSpan]) {
|
|
173
|
+
var spanName = `${this.ns}.${this.cmd.find ? 'find' : name}`;
|
|
174
|
+
span = ins.createSpan(spanName, 'db', 'mongodb', 'query', {
|
|
175
|
+
exitSpan: true,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (span) {
|
|
179
|
+
span.setDbContext({ type: 'mongodb', instance: this.ns });
|
|
180
|
+
// Limitation: Currently not getting destination address/port for
|
|
181
|
+
// cursor calls.
|
|
182
|
+
arguments[0] = ins.bindFunctionToRunContext(
|
|
183
|
+
ins.currRunContext(),
|
|
184
|
+
wrappedCallback,
|
|
185
|
+
);
|
|
186
|
+
if (name === 'next') {
|
|
187
|
+
this[firstSpan] = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return orig.apply(this, arguments);
|
|
194
|
+
|
|
195
|
+
function wrappedCallback() {
|
|
196
|
+
agent.logger.debug(
|
|
197
|
+
'intercepted mongodb-core.Cursor.prototype.%s callback %o',
|
|
198
|
+
name,
|
|
199
|
+
{ id },
|
|
200
|
+
);
|
|
201
|
+
span.end();
|
|
202
|
+
return cb.apply(this, arguments);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
@@ -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
|
+
};
|