@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,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
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
// Knex instrumentation exists to capture a more useful span stacktrace at
|
|
8
|
+
// the start of a Knex query, and use that stacktrace on the 'pg' or 'mysql'
|
|
9
|
+
// span.
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
var semver = require('semver');
|
|
14
|
+
|
|
15
|
+
var shimmer = require('../shimmer');
|
|
16
|
+
var symbols = require('../../symbols');
|
|
17
|
+
|
|
18
|
+
module.exports = function (Knex, agent, { version, enabled }) {
|
|
19
|
+
if (!enabled) {
|
|
20
|
+
return Knex;
|
|
21
|
+
}
|
|
22
|
+
if (!semver.satisfies(version, '>=0.10.0 <4.0.0')) {
|
|
23
|
+
agent.logger.debug(
|
|
24
|
+
`knex@${version} is not supported, skipping knex instrumentation`,
|
|
25
|
+
);
|
|
26
|
+
return Knex;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (agent._conf.spanStackTraceMinDuration < 0) {
|
|
30
|
+
agent.logger.trace(
|
|
31
|
+
'not instrumenting knex because not capturing span stack traces (spanStackTraceMinDuration=%s)',
|
|
32
|
+
agent._conf.spanStackTraceMinDuration,
|
|
33
|
+
);
|
|
34
|
+
return Knex;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function wrapQueryStartPoint(original) {
|
|
38
|
+
return function wrappedQueryStartPoint() {
|
|
39
|
+
var builder = original.apply(this, arguments);
|
|
40
|
+
|
|
41
|
+
agent.logger.debug('capturing custom stack trace for knex');
|
|
42
|
+
var obj = {};
|
|
43
|
+
Error.captureStackTrace(obj);
|
|
44
|
+
builder[symbols.knexStackObj] = obj;
|
|
45
|
+
|
|
46
|
+
return builder;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function wrapRunner(original) {
|
|
51
|
+
return function wrappedRunner() {
|
|
52
|
+
var runner = original.apply(this, arguments);
|
|
53
|
+
|
|
54
|
+
agent.logger.debug('shimming knex runner.query');
|
|
55
|
+
shimmer.wrap(runner, 'query', wrapQuery);
|
|
56
|
+
|
|
57
|
+
return runner;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function wrapQuery(original) {
|
|
62
|
+
return function wrappedQuery() {
|
|
63
|
+
agent.logger.debug('intercepted call to knex runner.query');
|
|
64
|
+
if (this.connection) {
|
|
65
|
+
this.connection[symbols.knexStackObj] = this.builder
|
|
66
|
+
? this.builder[symbols.knexStackObj]
|
|
67
|
+
: null;
|
|
68
|
+
}
|
|
69
|
+
return original.apply(this, arguments);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return function wrappedKnex() {
|
|
74
|
+
const knexInstance = Knex.apply(null, arguments);
|
|
75
|
+
|
|
76
|
+
if (knexInstance && knexInstance.client) {
|
|
77
|
+
const QUERY_FNS = ['queryBuilder', 'raw'];
|
|
78
|
+
agent.logger.debug('shimming knexInstance.client.runner');
|
|
79
|
+
shimmer.wrap(knexInstance.client, 'runner', wrapRunner);
|
|
80
|
+
agent.logger.debug(
|
|
81
|
+
'shimming Knex.Client.prototype functions: %j',
|
|
82
|
+
QUERY_FNS,
|
|
83
|
+
);
|
|
84
|
+
shimmer.massWrap(knexInstance.client, QUERY_FNS, wrapQueryStartPoint);
|
|
85
|
+
} else {
|
|
86
|
+
agent.logger.debug('could not shim Knex');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return knexInstance;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
|
|
13
|
+
module.exports = function (Router, agent, { version, enabled }) {
|
|
14
|
+
if (!enabled) return Router;
|
|
15
|
+
if (!semver.satisfies(version, '>=5.2.0 <14')) {
|
|
16
|
+
agent.logger.debug(
|
|
17
|
+
'koa-router version %s not supported - aborting...',
|
|
18
|
+
version,
|
|
19
|
+
);
|
|
20
|
+
return Router;
|
|
21
|
+
}
|
|
22
|
+
if (
|
|
23
|
+
semver.satisfies(version, '>=13') &&
|
|
24
|
+
semver.satisfies(process.version, '<18')
|
|
25
|
+
) {
|
|
26
|
+
agent.logger.debug(
|
|
27
|
+
'koa-router version %s not supported for node %s - aborting...',
|
|
28
|
+
version,
|
|
29
|
+
process.version,
|
|
30
|
+
);
|
|
31
|
+
return Router;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
agent.logger.debug('shimming koa-router prototype.match function');
|
|
35
|
+
shimmer.wrap(Router.prototype, 'match', function (orig) {
|
|
36
|
+
return function (_, method) {
|
|
37
|
+
var matched = orig.apply(this, arguments);
|
|
38
|
+
|
|
39
|
+
if (typeof method !== 'string') {
|
|
40
|
+
agent.logger.debug(
|
|
41
|
+
'unexpected method type in koa-router prototype.match: %s',
|
|
42
|
+
typeof method,
|
|
43
|
+
);
|
|
44
|
+
return matched;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(matched && matched.pathAndMethod)) {
|
|
48
|
+
const layer = matched.pathAndMethod.find(function (layer) {
|
|
49
|
+
return layer && layer.opts && layer.opts.end === true;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
var path = layer && layer.path;
|
|
53
|
+
if (typeof path === 'string') {
|
|
54
|
+
var name = method + ' ' + path;
|
|
55
|
+
agent._instrumentation.setDefaultTransactionName(name);
|
|
56
|
+
} else {
|
|
57
|
+
agent.logger.debug(
|
|
58
|
+
'unexpected path type in koa-router prototype.match: %s',
|
|
59
|
+
typeof path,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
agent.logger.debug(
|
|
64
|
+
'unexpected match result in koa-router prototype.match: %s',
|
|
65
|
+
typeof matched,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return matched;
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return Router;
|
|
74
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
module.exports = function (koa, agent, { version, enabled }) {
|
|
10
|
+
if (!enabled) return koa;
|
|
11
|
+
|
|
12
|
+
agent.setFramework({ name: 'koa', version, overwrite: false });
|
|
13
|
+
|
|
14
|
+
return koa;
|
|
15
|
+
};
|