@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,68 @@
|
|
|
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 httpShared = require('../http-shared');
|
|
12
|
+
var shimmer = require('../shimmer');
|
|
13
|
+
|
|
14
|
+
module.exports = function (
|
|
15
|
+
modExports,
|
|
16
|
+
agent,
|
|
17
|
+
{ version, enabled, isImportMod },
|
|
18
|
+
) {
|
|
19
|
+
if (agent._conf.instrumentIncomingHTTPRequests) {
|
|
20
|
+
agent.logger.debug('shimming https.Server.prototype.emit function');
|
|
21
|
+
shimmer.wrap(
|
|
22
|
+
modExports && modExports.Server && modExports.Server.prototype,
|
|
23
|
+
'emit',
|
|
24
|
+
httpShared.instrumentRequest(agent, 'https'),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!enabled) {
|
|
29
|
+
return modExports;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// From Node.js v9.0.0 and onwards, https requests no longer just call the
|
|
33
|
+
// http.request function. So to correctly instrument outgoing HTTPS requests
|
|
34
|
+
// in all supported Node.js versions, we'll only only instrument the
|
|
35
|
+
// https.request function if the Node version is v9.0.0 or above.
|
|
36
|
+
//
|
|
37
|
+
// This change was introduced in:
|
|
38
|
+
// https://github.com/nodejs/node/commit/5118f3146643dc55e7e7bd3082d1de4d0e7d5426
|
|
39
|
+
if (semver.lt(version, '9.0.0')) {
|
|
40
|
+
// We must ensure that the `http` module is instrumented to intercept
|
|
41
|
+
// `http.{request,get}` that `https.{request,get}` are using.
|
|
42
|
+
require('http');
|
|
43
|
+
return modExports;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
agent.logger.debug('shimming https.request function');
|
|
47
|
+
let wrapped = shimmer.wrap(
|
|
48
|
+
modExports,
|
|
49
|
+
'request',
|
|
50
|
+
httpShared.traceOutgoingRequest(agent, 'https', 'request'),
|
|
51
|
+
);
|
|
52
|
+
if (isImportMod && wrapped) {
|
|
53
|
+
// Handle `import https from 'https'`. See comment in "http.js".
|
|
54
|
+
modExports.default.request = wrapped;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
agent.logger.debug('shimming https.get function');
|
|
58
|
+
wrapped = shimmer.wrap(
|
|
59
|
+
modExports,
|
|
60
|
+
'get',
|
|
61
|
+
httpShared.traceOutgoingRequest(agent, 'https', 'get'),
|
|
62
|
+
);
|
|
63
|
+
if (isImportMod && wrapped) {
|
|
64
|
+
modExports.default.get = wrapped;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return modExports;
|
|
68
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
// Instrumentation of the 'ioredis' package:
|
|
10
|
+
// https://github.com/luin/ioredis
|
|
11
|
+
// https://github.com/luin/ioredis/blob/master/API.md
|
|
12
|
+
|
|
13
|
+
const semver = require('semver');
|
|
14
|
+
|
|
15
|
+
const constants = require('../../constants');
|
|
16
|
+
const { getDBDestination } = require('../context');
|
|
17
|
+
const shimmer = require('../shimmer');
|
|
18
|
+
|
|
19
|
+
const TYPE = 'db';
|
|
20
|
+
const SUBTYPE = 'redis';
|
|
21
|
+
const ACTION = 'query';
|
|
22
|
+
const hasIoredisSpanSym = Symbol('ElasticAPMHasIoredisSpan');
|
|
23
|
+
|
|
24
|
+
module.exports = function (ioredis, agent, { version, enabled }) {
|
|
25
|
+
if (!enabled) {
|
|
26
|
+
return ioredis;
|
|
27
|
+
}
|
|
28
|
+
if (!semver.satisfies(version, '>=2.0.0 <6.0.0')) {
|
|
29
|
+
agent.logger.debug(
|
|
30
|
+
'ioredis version %s not supported - aborting...',
|
|
31
|
+
version,
|
|
32
|
+
);
|
|
33
|
+
return ioredis;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ins = agent._instrumentation;
|
|
37
|
+
|
|
38
|
+
agent.logger.debug('shimming ioredis.prototype.sendCommand');
|
|
39
|
+
shimmer.wrap(ioredis.prototype, 'sendCommand', wrapSendCommand);
|
|
40
|
+
return ioredis;
|
|
41
|
+
|
|
42
|
+
function wrapSendCommand(origSendCommand) {
|
|
43
|
+
return function wrappedSendCommand(command) {
|
|
44
|
+
if (!command || !command.name || !command.promise) {
|
|
45
|
+
// Doesn't look like an ioredis.Command, skip instrumenting.
|
|
46
|
+
return origSendCommand.apply(this, arguments);
|
|
47
|
+
}
|
|
48
|
+
if (command[hasIoredisSpanSym]) {
|
|
49
|
+
// Avoid double-instrumenting a command when ioredis *re*-calls
|
|
50
|
+
// sendCommand for queued commands when "ready".
|
|
51
|
+
return origSendCommand.apply(this, arguments);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
agent.logger.debug(
|
|
55
|
+
{ command: command.name },
|
|
56
|
+
'intercepted call to ioredis.prototype.sendCommand',
|
|
57
|
+
);
|
|
58
|
+
const span = ins.createSpan(
|
|
59
|
+
command.name.toUpperCase(),
|
|
60
|
+
TYPE,
|
|
61
|
+
SUBTYPE,
|
|
62
|
+
ACTION,
|
|
63
|
+
{ exitSpan: true },
|
|
64
|
+
);
|
|
65
|
+
if (!span) {
|
|
66
|
+
return origSendCommand.apply(this, arguments);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
command[hasIoredisSpanSym] = true;
|
|
70
|
+
|
|
71
|
+
const options = this.options || {}; // `this` is the `Redis` client.
|
|
72
|
+
span._setDestinationContext(getDBDestination(options.host, options.port));
|
|
73
|
+
span.setDbContext({ type: 'redis' });
|
|
74
|
+
|
|
75
|
+
const spanRunContext = ins.currRunContext().enterSpan(span);
|
|
76
|
+
command.promise.then(
|
|
77
|
+
() => {
|
|
78
|
+
span.end();
|
|
79
|
+
},
|
|
80
|
+
ins.bindFunctionToRunContext(spanRunContext, (err) => {
|
|
81
|
+
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
|
|
82
|
+
agent.captureError(err, { skipOutcome: true });
|
|
83
|
+
span.end();
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
return ins.withRunContext(
|
|
87
|
+
spanRunContext,
|
|
88
|
+
origSendCommand,
|
|
89
|
+
this,
|
|
90
|
+
...arguments,
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
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 shimmer = require('../shimmer');
|
|
10
|
+
var templateShared = require('../template-shared');
|
|
11
|
+
|
|
12
|
+
module.exports = function (jade, agent, { enabled }) {
|
|
13
|
+
if (!enabled) return jade;
|
|
14
|
+
agent.logger.debug('shimming jade.compile');
|
|
15
|
+
shimmer.wrap(jade, 'compile', templateShared.wrapCompile(agent, 'jade'));
|
|
16
|
+
|
|
17
|
+
return jade;
|
|
18
|
+
};
|
|
@@ -0,0 +1,476 @@
|
|
|
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 { Buffer } = require('buffer');
|
|
10
|
+
|
|
11
|
+
const semver = require('semver');
|
|
12
|
+
|
|
13
|
+
const constants = require('../../constants');
|
|
14
|
+
const shimmer = require('../shimmer');
|
|
15
|
+
const { redactKeysFromObject } = require('../../filters/sanitize-field-names');
|
|
16
|
+
|
|
17
|
+
const NAME = 'Kafka';
|
|
18
|
+
const TYPE = 'messaging';
|
|
19
|
+
const SUBTYPE = 'kafka';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{ Kafka: import('kafkajs').Kafka}} KafkaModule
|
|
23
|
+
* @typedef {(config: any) => Consumer} ConsumerFactory
|
|
24
|
+
* @typedef {import('kafkajs').Consumer} Consumer
|
|
25
|
+
* @typedef {import('kafkajs').ConsumerRunConfig} ConsumerRunConfig
|
|
26
|
+
* @typedef {(config: any) => Producer} ProducerFactory
|
|
27
|
+
* @typedef {import('kafkajs').Producer} Producer
|
|
28
|
+
* @typedef {import('kafkajs').ProducerRecord} ProducerRecord
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {KafkaModule} mod
|
|
33
|
+
* @param {any} agent
|
|
34
|
+
* @param {Object} options
|
|
35
|
+
* @param {string} options.version
|
|
36
|
+
* @param {boolean} options.enabled
|
|
37
|
+
*/
|
|
38
|
+
module.exports = function (mod, agent, { version, enabled }) {
|
|
39
|
+
if (!enabled || !semver.satisfies(version, '>=2 <3')) {
|
|
40
|
+
return mod;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const config = agent._conf;
|
|
44
|
+
const ins = agent._instrumentation;
|
|
45
|
+
|
|
46
|
+
agent.logger.debug('shimming Kafka.prototype.consumer');
|
|
47
|
+
shimmer.wrap(mod.Kafka.prototype, 'consumer', wrapConsumer);
|
|
48
|
+
agent.logger.debug('shimming Kafka.prototype.producer');
|
|
49
|
+
shimmer.wrap(mod.Kafka.prototype, 'producer', wrapProducer);
|
|
50
|
+
return mod;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns the patched version of `Kafka.consumer` which creates a new
|
|
54
|
+
* consumer with its `run` method patched to instrument message handling
|
|
55
|
+
*
|
|
56
|
+
* @param {ConsumerFactory} origConsumer
|
|
57
|
+
* @returns {ConsumerFactory}
|
|
58
|
+
*/
|
|
59
|
+
function wrapConsumer(origConsumer) {
|
|
60
|
+
return function wrappedConsumer() {
|
|
61
|
+
const consumer = origConsumer.apply(this, arguments);
|
|
62
|
+
|
|
63
|
+
shimmer.wrap(consumer, 'run', wrapConsumerRun);
|
|
64
|
+
return consumer;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Return the patched version of `run` which instruments the
|
|
70
|
+
* `eachMessage` & `eachBatch` callbacks.
|
|
71
|
+
*
|
|
72
|
+
* @param {Consumer['run']} origRun
|
|
73
|
+
* @returns {Consumer['run']}
|
|
74
|
+
*/
|
|
75
|
+
function wrapConsumerRun(origRun) {
|
|
76
|
+
return function wrappedConsumerRun() {
|
|
77
|
+
const runConfig = arguments[0];
|
|
78
|
+
|
|
79
|
+
if (typeof runConfig.eachMessage === 'function') {
|
|
80
|
+
shimmer.wrap(runConfig, 'eachMessage', wrapEachMessage);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof runConfig.eachBatch === 'function') {
|
|
84
|
+
shimmer.wrap(runConfig, 'eachBatch', wrapEachBatch);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return origRun.apply(this, arguments);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns the instrumented version of `eachMessage` which
|
|
93
|
+
* - creates a transaction each time is called
|
|
94
|
+
* - add trace context into the transaction if present in message headers
|
|
95
|
+
*
|
|
96
|
+
* @param {ConsumerRunConfig['eachMessage']} origEachMessage
|
|
97
|
+
* @returns {ConsumerRunConfig['eachMessage']}
|
|
98
|
+
*/
|
|
99
|
+
function wrapEachMessage(origEachMessage) {
|
|
100
|
+
return async function (payload) {
|
|
101
|
+
const { topic, message } = payload;
|
|
102
|
+
|
|
103
|
+
if (shouldIgnoreTopic(topic, config)) {
|
|
104
|
+
return origEachMessage.apply(this, arguments);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// For distributed tracing this instrumentation is going to check
|
|
108
|
+
// the headers defined by opentelemetry and ignore the propietary
|
|
109
|
+
// `elasticaapmtraceparent` header
|
|
110
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-distributed-tracing.md#binary-fields
|
|
111
|
+
const traceparent = message.headers && message.headers.traceparent;
|
|
112
|
+
const tracestate = message.headers && message.headers.tracestate;
|
|
113
|
+
const opts = {};
|
|
114
|
+
|
|
115
|
+
// According to `kafkajs` types a header value might be
|
|
116
|
+
// a string or Buffer
|
|
117
|
+
// https://github.com/tulios/kafkajs/blob/ff3b1117f316d527ae170b550bc0f772614338e9/types/index.d.ts#L148
|
|
118
|
+
if (typeof traceparent === 'string') {
|
|
119
|
+
opts.childOf = traceparent;
|
|
120
|
+
} else if (traceparent instanceof Buffer) {
|
|
121
|
+
opts.childOf = traceparent.toString('utf-8');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof tracestate === 'string') {
|
|
125
|
+
opts.tracestate = tracestate;
|
|
126
|
+
} else if (tracestate instanceof Buffer) {
|
|
127
|
+
opts.tracestate = tracestate.toString('utf-8');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const trans = ins.startTransaction(
|
|
131
|
+
`${NAME} RECEIVE from ${topic}`,
|
|
132
|
+
TYPE,
|
|
133
|
+
opts,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const messageCtx = { queue: { name: topic } };
|
|
137
|
+
if (
|
|
138
|
+
config.captureBody === 'all' ||
|
|
139
|
+
config.captureBody === 'transactions'
|
|
140
|
+
) {
|
|
141
|
+
messageCtx.body = message.value?.toString();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (message.headers && config.captureHeaders) {
|
|
145
|
+
// Make sure there is no sensitive data
|
|
146
|
+
// and transform non-redacted buffers
|
|
147
|
+
messageCtx.headers = redactKeysFromObject(
|
|
148
|
+
message.headers,
|
|
149
|
+
config.sanitizeFieldNamesRegExp,
|
|
150
|
+
);
|
|
151
|
+
Object.keys(messageCtx.headers).forEach((key) => {
|
|
152
|
+
const value = messageCtx.headers[key];
|
|
153
|
+
if (value instanceof Buffer) {
|
|
154
|
+
messageCtx.headers[key] = value.toString('utf-8');
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (message.timestamp) {
|
|
160
|
+
messageCtx.age = {
|
|
161
|
+
ms: Date.now() - Number(message.timestamp),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
trans.setMessageContext(messageCtx);
|
|
166
|
+
|
|
167
|
+
let result, err;
|
|
168
|
+
try {
|
|
169
|
+
result = await origEachMessage.apply(this, arguments);
|
|
170
|
+
} catch (ex) {
|
|
171
|
+
// Save the error for use in `finally` below, but re-throw it to
|
|
172
|
+
// not impact code flow.
|
|
173
|
+
err = ex;
|
|
174
|
+
throw ex;
|
|
175
|
+
} finally {
|
|
176
|
+
trans.setOutcome(
|
|
177
|
+
err ? constants.OUTCOME_FAILURE : constants.OUTCOME_SUCCESS,
|
|
178
|
+
);
|
|
179
|
+
trans.end();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Returns the instrumented version of `eachBatch` which
|
|
188
|
+
* - creates a transaction each time is called
|
|
189
|
+
* - if trace context present in messages inks them to the transaction
|
|
190
|
+
*
|
|
191
|
+
* @param {ConsumerRunConfig['eachBatch']} origEachBatch
|
|
192
|
+
* @returns {ConsumerRunConfig['eachBatch']}
|
|
193
|
+
*/
|
|
194
|
+
function wrapEachBatch(origEachBatch) {
|
|
195
|
+
return async function ({ batch }) {
|
|
196
|
+
if (shouldIgnoreTopic(batch.topic, config)) {
|
|
197
|
+
return origEachBatch.apply(this, arguments);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const trans = ins.startTransaction(
|
|
201
|
+
`${NAME} RECEIVE from ${batch.topic}`,
|
|
202
|
+
TYPE,
|
|
203
|
+
);
|
|
204
|
+
const messageCtx = { queue: { name: batch.topic } };
|
|
205
|
+
trans.setMessageContext(messageCtx);
|
|
206
|
+
|
|
207
|
+
const serviceContext = {
|
|
208
|
+
framework: { name: 'Kafka' },
|
|
209
|
+
};
|
|
210
|
+
trans.setServiceContext(serviceContext);
|
|
211
|
+
|
|
212
|
+
// Extract span links from up to 1000 messages in this batch.
|
|
213
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-messaging.md#receiving-trace-context
|
|
214
|
+
// A span link is created from a `traceparent` header in a message.
|
|
215
|
+
const messages = batch && batch.messages;
|
|
216
|
+
|
|
217
|
+
if (messages) {
|
|
218
|
+
const traceparentsSeen = new Set();
|
|
219
|
+
const links = [];
|
|
220
|
+
const limit = Math.min(
|
|
221
|
+
messages.length,
|
|
222
|
+
constants.MAX_MESSAGES_PROCESSED_FOR_TRACE_CONTEXT,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < messages.length; i++) {
|
|
226
|
+
const msg = messages[i];
|
|
227
|
+
const traceparent =
|
|
228
|
+
msg.headers &&
|
|
229
|
+
msg.headers.traceparent &&
|
|
230
|
+
msg.headers.traceparent.toString();
|
|
231
|
+
|
|
232
|
+
if (traceparent && !traceparentsSeen.has(traceparent)) {
|
|
233
|
+
links.push({ context: traceparent });
|
|
234
|
+
traceparentsSeen.add(traceparent);
|
|
235
|
+
|
|
236
|
+
if (links.length >= limit) {
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
trans.addLinks(links);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let result, err;
|
|
245
|
+
try {
|
|
246
|
+
result = await origEachBatch.apply(this, arguments);
|
|
247
|
+
} catch (ex) {
|
|
248
|
+
// Save the error for use in `finally` below, but re-throw it to
|
|
249
|
+
// not impact code flow.
|
|
250
|
+
err = ex;
|
|
251
|
+
throw ex;
|
|
252
|
+
} finally {
|
|
253
|
+
trans.setOutcome(
|
|
254
|
+
err ? constants.OUTCOME_FAILURE : constants.OUTCOME_SUCCESS,
|
|
255
|
+
);
|
|
256
|
+
trans.end();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return result;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Returns the patched version of `Kafka.producer` which creates a new
|
|
265
|
+
* producer with `send` & `sendBatch` methods patched to instrument message sending
|
|
266
|
+
*
|
|
267
|
+
* @param {ProducerFactory} origProducer
|
|
268
|
+
* @returns {ProducerFactory}
|
|
269
|
+
*/
|
|
270
|
+
function wrapProducer(origProducer) {
|
|
271
|
+
return function wrappedProducer() {
|
|
272
|
+
const producer = origProducer.apply(this, arguments);
|
|
273
|
+
|
|
274
|
+
shimmer.wrap(producer, 'send', wrapProducerSend);
|
|
275
|
+
shimmer.wrap(producer, 'sendBatch', wrapProducerSendBatch);
|
|
276
|
+
return producer;
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Returns the instrumented version of `send` which
|
|
282
|
+
* - creates an exit span each time is called
|
|
283
|
+
* - propagates trace context through message headers
|
|
284
|
+
*
|
|
285
|
+
* @param {Producer['send']} origSend
|
|
286
|
+
* @returns {Producer['send']}
|
|
287
|
+
*/
|
|
288
|
+
function wrapProducerSend(origSend) {
|
|
289
|
+
return async function (record) {
|
|
290
|
+
const { topic } = record;
|
|
291
|
+
let span;
|
|
292
|
+
|
|
293
|
+
if (!shouldIgnoreTopic(topic, config)) {
|
|
294
|
+
span = ins.createSpan(
|
|
295
|
+
`${NAME} SEND to ${topic}`,
|
|
296
|
+
TYPE,
|
|
297
|
+
SUBTYPE,
|
|
298
|
+
'send',
|
|
299
|
+
{ exitSpan: true },
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// W3C trace-context propagation.
|
|
304
|
+
const runContext = ins.currRunContext();
|
|
305
|
+
const parentSpan =
|
|
306
|
+
span || runContext.currSpan() || runContext.currTransaction();
|
|
307
|
+
|
|
308
|
+
if (parentSpan) {
|
|
309
|
+
record.messages.forEach((msg) => {
|
|
310
|
+
const newHeaders = Object.assign({}, msg.headers);
|
|
311
|
+
parentSpan.propagateTraceContextHeaders(
|
|
312
|
+
newHeaders,
|
|
313
|
+
function (carrier, name, value) {
|
|
314
|
+
if (name.startsWith('elastic-')) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
carrier[name] = value;
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
msg.headers = newHeaders;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!span) {
|
|
325
|
+
return origSend.apply(this, arguments);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// We do not add headers or body because:
|
|
329
|
+
// - `record.messages` is a list
|
|
330
|
+
// - spec says is for transactions (https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-messaging.md#transaction-context-fields)
|
|
331
|
+
span.setMessageContext({ queue: { name: topic } });
|
|
332
|
+
|
|
333
|
+
const service = {
|
|
334
|
+
resource: `${SUBTYPE}/${topic}`,
|
|
335
|
+
type: SUBTYPE,
|
|
336
|
+
name: topic,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
span._setDestinationContext({ service });
|
|
340
|
+
|
|
341
|
+
let result, err;
|
|
342
|
+
try {
|
|
343
|
+
result = await origSend.apply(this, arguments);
|
|
344
|
+
} catch (ex) {
|
|
345
|
+
// Save the error for use in `finally` below, but re-throw it to
|
|
346
|
+
// not impact code flow.
|
|
347
|
+
err = ex;
|
|
348
|
+
throw ex;
|
|
349
|
+
} finally {
|
|
350
|
+
span.setOutcome(
|
|
351
|
+
err ? constants.OUTCOME_FAILURE : constants.OUTCOME_SUCCESS,
|
|
352
|
+
);
|
|
353
|
+
span.end();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return result;
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Returns the patched version of `Producer.sendBatch` which
|
|
362
|
+
* - creates an exit span for the operation
|
|
363
|
+
* - propagates trace context via message headers
|
|
364
|
+
*
|
|
365
|
+
* @param {Producer['sendBatch']} origSendBatch
|
|
366
|
+
* @returns {Producer['sendBatch']}
|
|
367
|
+
*/
|
|
368
|
+
function wrapProducerSendBatch(origSendBatch) {
|
|
369
|
+
return async function (batch) {
|
|
370
|
+
let span;
|
|
371
|
+
let topicForContext;
|
|
372
|
+
let shouldIgnoreBatch = true;
|
|
373
|
+
const messages = batch.topicMessages || [];
|
|
374
|
+
const topics = new Set();
|
|
375
|
+
|
|
376
|
+
// Remove possible topic duplications
|
|
377
|
+
for (const msg of messages) {
|
|
378
|
+
topics.add(msg.topic);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for (const t of topics) {
|
|
382
|
+
const topicIgnored = shouldIgnoreTopic(t, config);
|
|
383
|
+
|
|
384
|
+
shouldIgnoreBatch = shouldIgnoreBatch && topicIgnored;
|
|
385
|
+
|
|
386
|
+
// When a topic is not ignored we keep a copy for context unless
|
|
387
|
+
// we find a 2nd topic also not ignored.
|
|
388
|
+
if (!topicIgnored) {
|
|
389
|
+
if (topicForContext) {
|
|
390
|
+
topicForContext = undefined;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
topicForContext = t;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!shouldIgnoreBatch) {
|
|
398
|
+
const suffix = topicForContext ? ` to ${topicForContext}` : '';
|
|
399
|
+
span = ins.createSpan(`${NAME} SEND${suffix}`, TYPE, SUBTYPE, 'send', {
|
|
400
|
+
exitSpan: true,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// W3C trace-context propagation.
|
|
405
|
+
const runContext = ins.currRunContext();
|
|
406
|
+
const parentSpan =
|
|
407
|
+
span || runContext.currSpan() || runContext.currTransaction();
|
|
408
|
+
|
|
409
|
+
if (parentSpan && batch.topicMessages) {
|
|
410
|
+
batch.topicMessages.forEach((topicMessage) => {
|
|
411
|
+
topicMessage.messages.forEach((msg) => {
|
|
412
|
+
const newHeaders = Object.assign({}, msg.headers);
|
|
413
|
+
parentSpan.propagateTraceContextHeaders(
|
|
414
|
+
newHeaders,
|
|
415
|
+
function (carrier, name, value) {
|
|
416
|
+
if (name.startsWith('elastic-')) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
carrier[name] = value;
|
|
420
|
+
},
|
|
421
|
+
);
|
|
422
|
+
msg.headers = newHeaders;
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!span) {
|
|
428
|
+
return origSendBatch.apply(this, arguments);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (topicForContext) {
|
|
432
|
+
// We do not add headers or body because:
|
|
433
|
+
// - `record.messages` is a list
|
|
434
|
+
// - spec says is for transactions (https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-messaging.md#transaction-context-fields)
|
|
435
|
+
span.setMessageContext({ queue: { name: topicForContext } });
|
|
436
|
+
}
|
|
437
|
+
span.setServiceTarget(SUBTYPE, topicForContext);
|
|
438
|
+
|
|
439
|
+
let result, err;
|
|
440
|
+
try {
|
|
441
|
+
result = await origSendBatch.apply(this, arguments);
|
|
442
|
+
} catch (ex) {
|
|
443
|
+
// Save the error for use in `finally` below, but re-throw it to
|
|
444
|
+
// not impact code flow.
|
|
445
|
+
err = ex;
|
|
446
|
+
throw ex;
|
|
447
|
+
} finally {
|
|
448
|
+
span.setOutcome(
|
|
449
|
+
err ? constants.OUTCOME_FAILURE : constants.OUTCOME_SUCCESS,
|
|
450
|
+
);
|
|
451
|
+
span.end();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return result;
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Returns true if we have to ignore messages on the given topic
|
|
461
|
+
*
|
|
462
|
+
* @param {string} topic the topic where client is publishing/subscribing
|
|
463
|
+
* @param {{ ignoreMessageQueuesRegExp: RegExp[] }} config the agent's configuration object
|
|
464
|
+
* @returns {boolean}
|
|
465
|
+
*/
|
|
466
|
+
function shouldIgnoreTopic(topic, config) {
|
|
467
|
+
if (config.ignoreMessageQueuesRegExp) {
|
|
468
|
+
for (const rule of config.ignoreMessageQueuesRegExp) {
|
|
469
|
+
if (rule.test(topic)) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return false;
|
|
476
|
+
}
|