@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.
Files changed (154) hide show
  1. package/LICENSE +26 -0
  2. package/NOTICE.md +442 -0
  3. package/README.md +48 -0
  4. package/changes.json +78 -0
  5. package/index.d.ts +398 -0
  6. package/index.js +11 -0
  7. package/lib/InflightEventSet.js +53 -0
  8. package/lib/activation-method.js +119 -0
  9. package/lib/agent.js +941 -0
  10. package/lib/apm-client/apm-client.js +313 -0
  11. package/lib/apm-client/http-apm-client/CHANGELOG.md +271 -0
  12. package/lib/apm-client/http-apm-client/README.md +485 -0
  13. package/lib/apm-client/http-apm-client/central-config.js +41 -0
  14. package/lib/apm-client/http-apm-client/container-info.js +111 -0
  15. package/lib/apm-client/http-apm-client/detect-hostname.js +96 -0
  16. package/lib/apm-client/http-apm-client/index.js +1975 -0
  17. package/lib/apm-client/http-apm-client/logging.js +31 -0
  18. package/lib/apm-client/http-apm-client/ndjson.js +20 -0
  19. package/lib/apm-client/http-apm-client/truncate.js +434 -0
  20. package/lib/apm-client/noop-apm-client.js +73 -0
  21. package/lib/async-hooks-polyfill.js +58 -0
  22. package/lib/cloud-metadata/aws.js +175 -0
  23. package/lib/cloud-metadata/azure.js +123 -0
  24. package/lib/cloud-metadata/callback-coordination.js +159 -0
  25. package/lib/cloud-metadata/gcp.js +133 -0
  26. package/lib/cloud-metadata/index.js +175 -0
  27. package/lib/config/config.js +458 -0
  28. package/lib/config/normalizers.js +701 -0
  29. package/lib/config/schema.js +1007 -0
  30. package/lib/constants.js +35 -0
  31. package/lib/errors.js +303 -0
  32. package/lib/filters/sanitize-field-names.js +69 -0
  33. package/lib/http-request.js +249 -0
  34. package/lib/instrumentation/azure-functions.js +519 -0
  35. package/lib/instrumentation/context.js +56 -0
  36. package/lib/instrumentation/dropped-spans-stats.js +112 -0
  37. package/lib/instrumentation/elasticsearch-shared.js +63 -0
  38. package/lib/instrumentation/express-utils.js +91 -0
  39. package/lib/instrumentation/generic-span.js +322 -0
  40. package/lib/instrumentation/http-shared.js +424 -0
  41. package/lib/instrumentation/ids.js +39 -0
  42. package/lib/instrumentation/index.js +1127 -0
  43. package/lib/instrumentation/modules/@apollo/server.js +30 -0
  44. package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
  45. package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
  46. package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
  47. package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
  48. package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
  49. package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
  50. package/lib/instrumentation/modules/@opentelemetry/api.js +86 -0
  51. package/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +79 -0
  52. package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
  53. package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
  54. package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
  55. package/lib/instrumentation/modules/_lambda-handler.js +40 -0
  56. package/lib/instrumentation/modules/apollo-server-core.js +49 -0
  57. package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
  58. package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
  59. package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
  60. package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
  61. package/lib/instrumentation/modules/aws-sdk.js +76 -0
  62. package/lib/instrumentation/modules/bluebird.js +93 -0
  63. package/lib/instrumentation/modules/cassandra-driver.js +280 -0
  64. package/lib/instrumentation/modules/elasticsearch.js +191 -0
  65. package/lib/instrumentation/modules/express-graphql.js +66 -0
  66. package/lib/instrumentation/modules/express-queue.js +28 -0
  67. package/lib/instrumentation/modules/express.js +162 -0
  68. package/lib/instrumentation/modules/fastify.js +172 -0
  69. package/lib/instrumentation/modules/finalhandler.js +41 -0
  70. package/lib/instrumentation/modules/generic-pool.js +85 -0
  71. package/lib/instrumentation/modules/graphql.js +256 -0
  72. package/lib/instrumentation/modules/handlebars.js +22 -0
  73. package/lib/instrumentation/modules/http.js +112 -0
  74. package/lib/instrumentation/modules/http2.js +320 -0
  75. package/lib/instrumentation/modules/https.js +68 -0
  76. package/lib/instrumentation/modules/ioredis.js +94 -0
  77. package/lib/instrumentation/modules/jade.js +18 -0
  78. package/lib/instrumentation/modules/kafkajs.js +476 -0
  79. package/lib/instrumentation/modules/knex.js +91 -0
  80. package/lib/instrumentation/modules/koa-router.js +74 -0
  81. package/lib/instrumentation/modules/koa.js +15 -0
  82. package/lib/instrumentation/modules/memcached.js +99 -0
  83. package/lib/instrumentation/modules/mimic-response.js +45 -0
  84. package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
  85. package/lib/instrumentation/modules/mongodb-core.js +206 -0
  86. package/lib/instrumentation/modules/mongodb.js +259 -0
  87. package/lib/instrumentation/modules/mysql.js +200 -0
  88. package/lib/instrumentation/modules/mysql2.js +140 -0
  89. package/lib/instrumentation/modules/pg.js +148 -0
  90. package/lib/instrumentation/modules/pug.js +18 -0
  91. package/lib/instrumentation/modules/redis.js +176 -0
  92. package/lib/instrumentation/modules/restify.js +52 -0
  93. package/lib/instrumentation/modules/tedious.js +159 -0
  94. package/lib/instrumentation/modules/undici.js +270 -0
  95. package/lib/instrumentation/modules/ws.js +59 -0
  96. package/lib/instrumentation/noop-transaction.js +81 -0
  97. package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
  98. package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
  99. package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
  100. package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
  101. package/lib/instrumentation/run-context/RunContext.js +151 -0
  102. package/lib/instrumentation/run-context/index.js +23 -0
  103. package/lib/instrumentation/shimmer.js +123 -0
  104. package/lib/instrumentation/span-compression.js +239 -0
  105. package/lib/instrumentation/span.js +621 -0
  106. package/lib/instrumentation/template-shared.js +43 -0
  107. package/lib/instrumentation/timer.js +84 -0
  108. package/lib/instrumentation/transaction.js +571 -0
  109. package/lib/lambda.js +992 -0
  110. package/lib/load-source-map.js +100 -0
  111. package/lib/logging.js +212 -0
  112. package/lib/metrics/index.js +92 -0
  113. package/lib/metrics/platforms/generic/index.js +40 -0
  114. package/lib/metrics/platforms/generic/process-cpu.js +22 -0
  115. package/lib/metrics/platforms/generic/process-top.js +157 -0
  116. package/lib/metrics/platforms/generic/stats.js +34 -0
  117. package/lib/metrics/platforms/generic/system-cpu.js +51 -0
  118. package/lib/metrics/platforms/linux/index.js +19 -0
  119. package/lib/metrics/platforms/linux/stats.js +213 -0
  120. package/lib/metrics/queue.js +90 -0
  121. package/lib/metrics/registry.js +52 -0
  122. package/lib/metrics/reporter.js +119 -0
  123. package/lib/metrics/runtime.js +77 -0
  124. package/lib/middleware/connect.js +16 -0
  125. package/lib/opentelemetry-bridge/OTelBridgeNonRecordingSpan.js +150 -0
  126. package/lib/opentelemetry-bridge/OTelBridgeRunContext.js +124 -0
  127. package/lib/opentelemetry-bridge/OTelContextManager.js +82 -0
  128. package/lib/opentelemetry-bridge/OTelSpan.js +344 -0
  129. package/lib/opentelemetry-bridge/OTelTracer.js +201 -0
  130. package/lib/opentelemetry-bridge/OTelTracerProvider.js +25 -0
  131. package/lib/opentelemetry-bridge/README.md +244 -0
  132. package/lib/opentelemetry-bridge/index.js +15 -0
  133. package/lib/opentelemetry-bridge/oblog.js +23 -0
  134. package/lib/opentelemetry-bridge/opentelemetry-core-mini/README.md +3 -0
  135. package/lib/opentelemetry-bridge/opentelemetry-core-mini/internal/validators.js +52 -0
  136. package/lib/opentelemetry-bridge/opentelemetry-core-mini/trace/TraceState.js +109 -0
  137. package/lib/opentelemetry-bridge/otelutils.js +99 -0
  138. package/lib/opentelemetry-bridge/setup.js +76 -0
  139. package/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +285 -0
  140. package/lib/opentelemetry-metrics/index.js +50 -0
  141. package/lib/parsers.js +225 -0
  142. package/lib/propwrap.js +147 -0
  143. package/lib/stacktraces.js +537 -0
  144. package/lib/symbols.js +15 -0
  145. package/lib/tracecontext/index.js +118 -0
  146. package/lib/tracecontext/traceparent.js +185 -0
  147. package/lib/tracecontext/tracestate.js +388 -0
  148. package/lib/wildcard-matcher.js +52 -0
  149. package/loader.mjs +7 -0
  150. package/package.json +299 -0
  151. package/start.d.ts +8 -0
  152. package/start.js +29 -0
  153. package/types/aws-lambda.d.ts +98 -0
  154. 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
+ }