@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.
Files changed (127) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +126 -0
  3. package/index.d.ts +464 -0
  4. package/index.js +11 -0
  5. package/lib/InflightEventSet.js +53 -0
  6. package/lib/activation-method.js +97 -0
  7. package/lib/agent.js +1226 -0
  8. package/lib/apm-client/apm-client.js +107 -0
  9. package/lib/apm-client/channel-writer.js +334 -0
  10. package/lib/apm-client/jsonl-file-client.js +241 -0
  11. package/lib/apm-client/ndjson.js +20 -0
  12. package/lib/apm-client/noop-apm-client.js +79 -0
  13. package/lib/apm-client/s3-uploader.js +308 -0
  14. package/lib/apm-client/truncate.js +507 -0
  15. package/lib/async-hooks-polyfill.js +58 -0
  16. package/lib/cloud-metadata/aws.js +175 -0
  17. package/lib/cloud-metadata/azure.js +123 -0
  18. package/lib/cloud-metadata/callback-coordination.js +159 -0
  19. package/lib/cloud-metadata/gcp.js +133 -0
  20. package/lib/cloud-metadata/index.js +175 -0
  21. package/lib/config/config.js +431 -0
  22. package/lib/config/normalizers.js +649 -0
  23. package/lib/config/schema.js +946 -0
  24. package/lib/constants.js +35 -0
  25. package/lib/errors.js +303 -0
  26. package/lib/filters/sanitize-field-names.js +69 -0
  27. package/lib/http-request.js +249 -0
  28. package/lib/instrumentation/context.js +56 -0
  29. package/lib/instrumentation/dropped-spans-stats.js +112 -0
  30. package/lib/instrumentation/elasticsearch-shared.js +63 -0
  31. package/lib/instrumentation/express-utils.js +91 -0
  32. package/lib/instrumentation/generic-span.js +322 -0
  33. package/lib/instrumentation/http-shared.js +424 -0
  34. package/lib/instrumentation/ids.js +39 -0
  35. package/lib/instrumentation/index.js +1078 -0
  36. package/lib/instrumentation/modules/@apollo/server.js +39 -0
  37. package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
  38. package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
  39. package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
  40. package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
  41. package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
  42. package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
  43. package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
  44. package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
  45. package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
  46. package/lib/instrumentation/modules/apollo-server-core.js +49 -0
  47. package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
  48. package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
  49. package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
  50. package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
  51. package/lib/instrumentation/modules/aws-sdk.js +76 -0
  52. package/lib/instrumentation/modules/bluebird.js +93 -0
  53. package/lib/instrumentation/modules/cassandra-driver.js +280 -0
  54. package/lib/instrumentation/modules/elasticsearch.js +200 -0
  55. package/lib/instrumentation/modules/express-graphql.js +66 -0
  56. package/lib/instrumentation/modules/express-queue.js +28 -0
  57. package/lib/instrumentation/modules/express.js +162 -0
  58. package/lib/instrumentation/modules/fastify.js +179 -0
  59. package/lib/instrumentation/modules/finalhandler.js +41 -0
  60. package/lib/instrumentation/modules/generic-pool.js +85 -0
  61. package/lib/instrumentation/modules/graphql.js +256 -0
  62. package/lib/instrumentation/modules/handlebars.js +33 -0
  63. package/lib/instrumentation/modules/http.js +112 -0
  64. package/lib/instrumentation/modules/http2.js +320 -0
  65. package/lib/instrumentation/modules/https.js +68 -0
  66. package/lib/instrumentation/modules/ioredis.js +94 -0
  67. package/lib/instrumentation/modules/jade.js +29 -0
  68. package/lib/instrumentation/modules/kafkajs.js +476 -0
  69. package/lib/instrumentation/modules/knex.js +91 -0
  70. package/lib/instrumentation/modules/koa-router.js +74 -0
  71. package/lib/instrumentation/modules/koa.js +15 -0
  72. package/lib/instrumentation/modules/memcached.js +100 -0
  73. package/lib/instrumentation/modules/mimic-response.js +45 -0
  74. package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
  75. package/lib/instrumentation/modules/mongodb-core.js +206 -0
  76. package/lib/instrumentation/modules/mongodb.js +259 -0
  77. package/lib/instrumentation/modules/mysql.js +200 -0
  78. package/lib/instrumentation/modules/mysql2.js +140 -0
  79. package/lib/instrumentation/modules/pg.js +148 -0
  80. package/lib/instrumentation/modules/pug.js +29 -0
  81. package/lib/instrumentation/modules/redis.js +176 -0
  82. package/lib/instrumentation/modules/restify.js +52 -0
  83. package/lib/instrumentation/modules/tedious.js +159 -0
  84. package/lib/instrumentation/modules/undici.js +270 -0
  85. package/lib/instrumentation/modules/ws.js +59 -0
  86. package/lib/instrumentation/noop-transaction.js +81 -0
  87. package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
  88. package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
  89. package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
  90. package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
  91. package/lib/instrumentation/run-context/RunContext.js +151 -0
  92. package/lib/instrumentation/run-context/index.js +23 -0
  93. package/lib/instrumentation/shimmer.js +123 -0
  94. package/lib/instrumentation/span-compression.js +239 -0
  95. package/lib/instrumentation/span.js +621 -0
  96. package/lib/instrumentation/template-shared.js +43 -0
  97. package/lib/instrumentation/timer.js +84 -0
  98. package/lib/instrumentation/transaction.js +571 -0
  99. package/lib/load-source-map.js +100 -0
  100. package/lib/logging.js +212 -0
  101. package/lib/metrics/index.js +92 -0
  102. package/lib/metrics/platforms/generic/index.js +40 -0
  103. package/lib/metrics/platforms/generic/process-cpu.js +22 -0
  104. package/lib/metrics/platforms/generic/process-top.js +157 -0
  105. package/lib/metrics/platforms/generic/stats.js +34 -0
  106. package/lib/metrics/platforms/generic/system-cpu.js +51 -0
  107. package/lib/metrics/platforms/linux/index.js +19 -0
  108. package/lib/metrics/platforms/linux/stats.js +213 -0
  109. package/lib/metrics/queue.js +90 -0
  110. package/lib/metrics/registry.js +52 -0
  111. package/lib/metrics/reporter.js +119 -0
  112. package/lib/metrics/runtime.js +77 -0
  113. package/lib/middleware/connect.js +16 -0
  114. package/lib/parsers.js +225 -0
  115. package/lib/propwrap.js +147 -0
  116. package/lib/stacktraces.js +537 -0
  117. package/lib/symbols.js +15 -0
  118. package/lib/tracecontext/index.js +115 -0
  119. package/lib/tracecontext/traceparent.js +185 -0
  120. package/lib/tracecontext/tracestate.js +388 -0
  121. package/lib/wildcard-matcher.js +52 -0
  122. package/loader.mjs +7 -0
  123. package/package.json +98 -0
  124. package/start.d.ts +8 -0
  125. package/start.js +29 -0
  126. package/types/aws-lambda.d.ts +98 -0
  127. package/types/connect.d.ts +23 -0
@@ -0,0 +1,221 @@
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
+ var onPreAuthSym = Symbol('ElasticAPMOnPreAuth');
14
+
15
+ // Collect simple data a Hapi `event.data` object, typically from a Hapi
16
+ // 'log' or 'request' server event (https://hapi.dev/api/#server.events). This
17
+ // limits to including simple property values (bool, string, number, Date) to
18
+ // limit the possibility of accidentally capturing huge data in `captureError`
19
+ // below.
20
+ //
21
+ // This implementation is based on lib/errors.js#attributesFromErr.
22
+ function simpleDataFromEventData(agent, eventData) {
23
+ try {
24
+ let simpleRepr = simpleReprFromVal(eventData);
25
+ if (simpleRepr !== undefined) {
26
+ return simpleRepr;
27
+ }
28
+
29
+ let n = 0;
30
+ const attrs = {};
31
+ const keys = Object.keys(eventData);
32
+ for (let i = 0; i < keys.length; i++) {
33
+ const key = keys[i];
34
+ let val = eventData[key];
35
+ simpleRepr = simpleReprFromVal(val);
36
+ if (simpleRepr) {
37
+ attrs[key] = simpleRepr;
38
+ n++;
39
+ }
40
+ }
41
+ return n ? attrs : undefined;
42
+ } catch (err) {
43
+ agent.logger.trace(
44
+ 'hapi: could not gather simple attrs from event data: ' + err.message,
45
+ );
46
+ }
47
+ }
48
+
49
+ // If `val` is a "simple" type (bool, string, number, Date), then return a
50
+ // reasonable value to represent it in a JSON serialization. Otherwise, return
51
+ // undefined.
52
+ function simpleReprFromVal(val) {
53
+ switch (typeof val) {
54
+ case 'boolean':
55
+ case 'string':
56
+ case 'number':
57
+ break;
58
+ case 'object':
59
+ // Ignore all objects except Dates.
60
+ if (
61
+ val === null ||
62
+ typeof val.toISOString !== 'function' ||
63
+ typeof val.getTime !== 'function'
64
+ ) {
65
+ return;
66
+ } else if (Number.isNaN(val.getTime())) {
67
+ val = 'Invalid Date'; // calling toISOString() on invalid dates throws
68
+ } else {
69
+ val = val.toISOString();
70
+ }
71
+ break;
72
+ default:
73
+ return;
74
+ }
75
+ return val;
76
+ }
77
+
78
+ module.exports = function (hapi, agent, { version, enabled }) {
79
+ if (!enabled) {
80
+ return hapi;
81
+ }
82
+ if (!semver.satisfies(version, '>=17.9.0 <22.0.0')) {
83
+ agent.logger.debug('@hapi/hapi@%s not supported, skipping', version);
84
+ return hapi;
85
+ }
86
+
87
+ agent.setFramework({ name: 'hapi', version, overwrite: false });
88
+
89
+ agent.logger.debug('shimming hapi.Server, hapi.server');
90
+ shimmer.massWrap(hapi, ['Server', 'server'], function (orig) {
91
+ return function (options) {
92
+ var res = orig.apply(this, arguments);
93
+ patchServer(res);
94
+ return res;
95
+ };
96
+ });
97
+
98
+ function patchServer(server) {
99
+ // Hooks that are always allowed
100
+ if (typeof server.on === 'function') {
101
+ attachEvents(server);
102
+ } else if (typeof server.events.on === 'function') {
103
+ attachEvents(server.events);
104
+ } else {
105
+ agent.logger.debug('unable to enable hapi error tracking');
106
+ }
107
+
108
+ server.ext('onPreAuth', onPreAuth);
109
+ server.ext('onPreResponse', onPreResponse);
110
+ if (agent._conf.captureBody !== 'off') {
111
+ server.ext('onPostAuth', onPostAuth);
112
+ }
113
+ }
114
+
115
+ function attachEvents(emitter) {
116
+ emitter.on('log', function (event, tags) {
117
+ captureError('log', null, event, tags);
118
+ });
119
+
120
+ emitter.on('request', function (req, event, tags) {
121
+ captureError('request', req, event, tags);
122
+ });
123
+ }
124
+
125
+ function captureError(type, req, event, tags) {
126
+ if (!event || !tags.error || event.channel === 'internal') {
127
+ return;
128
+ }
129
+
130
+ // Hapi 'log' and 'request' events (https://hapi.dev/api/#server.events)
131
+ // have `event.error`, `event.data`, or neither.
132
+ // `agent.captureError` requires an Error instance or string for its first
133
+ // arg: bias to getting that, then any other data add to `opts.custom.data`.
134
+ const info = event.error || event.data;
135
+ let errOrStr, data;
136
+ if (info instanceof Error || typeof info === 'string') {
137
+ errOrStr = info;
138
+ } else if (info) {
139
+ data = simpleDataFromEventData(agent, info);
140
+ }
141
+ if (!errOrStr) {
142
+ errOrStr = 'hapi server emitted a "' + type + '" event tagged "error"';
143
+ }
144
+
145
+ agent.captureError(errOrStr, {
146
+ custom: {
147
+ tags: event.tags,
148
+ data,
149
+ },
150
+ request: req && req.raw && req.raw.req,
151
+ });
152
+ }
153
+
154
+ function onPreAuth(request, reply) {
155
+ agent.logger.debug('received hapi onPreAuth event');
156
+
157
+ // Record the fact that the preAuth extension have been called. This
158
+ // info is useful later to know if this is a CORS preflight request
159
+ // that is automatically handled by hapi (as those will not trigger
160
+ // the onPreAuth extention)
161
+ request[onPreAuthSym] = true;
162
+
163
+ if (request.route) {
164
+ // fingerprint was introduced in hapi 11 and is a little more
165
+ // stable in case the param names change
166
+ // - path example: /foo/{bar*2}
167
+ // - fingerprint example: /foo/?/?
168
+ var fingerprint = request.route.fingerprint || request.route.path;
169
+
170
+ if (fingerprint) {
171
+ var name =
172
+ (request.raw && request.raw.req && request.raw.req.method) ||
173
+ (request.route.method && request.route.method.toUpperCase());
174
+
175
+ if (typeof name === 'string') {
176
+ name = name + ' ' + fingerprint;
177
+ } else {
178
+ name = fingerprint;
179
+ }
180
+
181
+ agent._instrumentation.setDefaultTransactionName(name);
182
+ }
183
+ }
184
+
185
+ return reply.continue;
186
+ }
187
+
188
+ function onPostAuth(request, reply) {
189
+ if (request.payload && request.raw && request.raw.req) {
190
+ // Save the parsed req body to be picked up by getContextFromRequest().
191
+ request.raw.req.payload = request.payload;
192
+ }
193
+ return reply.continue;
194
+ }
195
+
196
+ function onPreResponse(request, reply) {
197
+ agent.logger.debug('received hapi onPreResponse event');
198
+
199
+ // Detection of CORS preflight requests:
200
+ // There is no easy way in hapi to get the matched route for a
201
+ // CORS preflight request that matches any of the autogenerated
202
+ // routes created by hapi when `cors: true`. The best solution is to
203
+ // detect the request "fingerprint" using the magic if-sentence below
204
+ // and group all those requests into on type of transaction
205
+ if (
206
+ !request[onPreAuthSym] &&
207
+ request.route &&
208
+ request.route.path === '/{p*}' &&
209
+ request.raw &&
210
+ request.raw.req &&
211
+ request.raw.req.method === 'OPTIONS' &&
212
+ request.raw.req.headers['access-control-request-method']
213
+ ) {
214
+ agent._instrumentation.setDefaultTransactionName('CORS preflight');
215
+ }
216
+
217
+ return reply.continue;
218
+ }
219
+
220
+ return hapi;
221
+ };
@@ -0,0 +1,178 @@
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
+ const semver = require('semver');
8
+ const { getDBDestination } = require('../../../../../../context');
9
+ const { redisClientOptions } = require('../../../../../../../../lib/symbols');
10
+ const destinationContext = Symbol('destinationContext');
11
+
12
+ const TYPE = 'db';
13
+ const SUBTYPE = 'redis';
14
+ const ACTION = 'query';
15
+
16
+ // From https://redis.io/commands/ this is the set of redis commands that have
17
+ // *two* tokens for the full command name.
18
+ const IS_COMMAND_PREFIX = new Set([
19
+ 'ACL',
20
+ 'CLIENT',
21
+ 'CLUSTER',
22
+ 'COMMAND',
23
+ 'CONFIG',
24
+ 'FUNCTION', // e.g. FUNCTION LOAD
25
+ 'LATENCY',
26
+ 'MEMORY',
27
+ 'MODULE',
28
+ 'OBJECT',
29
+ 'PUBSUB',
30
+ 'SCRIPT',
31
+ 'SLOWLOG',
32
+ 'XGROUP',
33
+ 'XINFO',
34
+ ]);
35
+
36
+ /**
37
+ * Return the redis command name from the given `RedisCommandArguments`.
38
+ *
39
+ * export type RedisCommandArgument = string | Buffer;
40
+ * export type RedisCommandArguments = Array<RedisCommandArgument> & { preserve?: unknown };
41
+ *
42
+ * Examples:
43
+ * > commandNameFromArgs(['PING'])
44
+ * 'PING'
45
+ * > commandNameFromArgs(['SET', 'foo', 'bar'])
46
+ * 'SET'
47
+ * > commandNameFromArgs(['ACL', 'SETUSER', 'karin', 'on', '+@all', '-@dangerous'])
48
+ * 'ACL SETUSER'
49
+ */
50
+ function commandNameFromArgs(args) {
51
+ if (IS_COMMAND_PREFIX.has(args[0]) && args.length >= 2) {
52
+ return args.slice(0, 2).join(' ');
53
+ } else {
54
+ return args[0];
55
+ }
56
+ }
57
+
58
+ module.exports = function (mod, agent, { version, enabled }) {
59
+ if (!enabled) {
60
+ return mod;
61
+ }
62
+ if (!semver.satisfies(version, '>=1 <2')) {
63
+ agent.logger.debug(
64
+ '@redis/client/dist/lib/client version %s not supported - aborting...',
65
+ version,
66
+ );
67
+ return mod;
68
+ }
69
+
70
+ const RedisCommandsQueue = mod.default;
71
+ if (
72
+ !(
73
+ RedisCommandsQueue &&
74
+ RedisCommandsQueue.name === 'RedisCommandsQueue' &&
75
+ RedisCommandsQueue.prototype &&
76
+ typeof RedisCommandsQueue.prototype.addCommand === 'function'
77
+ )
78
+ ) {
79
+ agent.logger.debug('cannot instrument @redis/client');
80
+ return mod;
81
+ }
82
+
83
+ const ins = agent._instrumentation;
84
+
85
+ agent.logger.debug(
86
+ 'shimming @redis/client RedisCommandsQueue.prototype.addCommand',
87
+ );
88
+
89
+ class RedisCommandsQueueTraced extends RedisCommandsQueue {
90
+ constructor() {
91
+ super(arguments);
92
+
93
+ // Determine destination context to use for Redis spans, using the
94
+ // RedisClient `options` passed down from RedisClient.create instrumentation.
95
+ if (ins[redisClientOptions]) {
96
+ // https://github.com/redis/node-redis/blob/master/docs/client-configuration.md
97
+ // Reproducing determination of 'address' and 'port' per
98
+ // `RedisClient.#initializeOptions()`
99
+ let address = 'localhost';
100
+ let port = 6379;
101
+ if (ins[redisClientOptions].url) {
102
+ const parsed = new URL(ins[redisClientOptions].url);
103
+ address = parsed.hostname;
104
+ if (parsed.port) {
105
+ port = parsed.port;
106
+ }
107
+ }
108
+ if (ins[redisClientOptions].socket) {
109
+ if (ins[redisClientOptions].socket.host) {
110
+ address = ins[redisClientOptions].socket.host;
111
+ }
112
+ if (
113
+ ins[redisClientOptions].socket.port &&
114
+ !isNaN(Number(ins[redisClientOptions].socket.port))
115
+ ) {
116
+ port = Number(ins[redisClientOptions].socket.port);
117
+ }
118
+ }
119
+
120
+ this[destinationContext] = getDBDestination(address, port);
121
+ }
122
+ }
123
+
124
+ addCommand(args) {
125
+ const commandName = commandNameFromArgs(args);
126
+ agent.logger.debug(
127
+ { commandName },
128
+ 'intercepted call to @redis/client RedisCommandsQueue.prototype.addCommand',
129
+ );
130
+ const span = ins.createSpan(
131
+ commandName.toUpperCase(),
132
+ TYPE,
133
+ SUBTYPE,
134
+ ACTION,
135
+ { exitSpan: true },
136
+ );
137
+ if (!span) {
138
+ return super.addCommand.apply(this, arguments);
139
+ }
140
+
141
+ span.setDbContext({ type: 'redis' });
142
+
143
+ if (this[destinationContext]) {
144
+ span._setDestinationContext(this[destinationContext]);
145
+ }
146
+
147
+ const parentRunContext = ins.currRunContext();
148
+ const spanRunContext = parentRunContext.enterSpan(span);
149
+ const finish = ins.bindFunctionToRunContext(spanRunContext, (err) => {
150
+ if (err) {
151
+ agent.captureError(err);
152
+ }
153
+ span.end();
154
+ });
155
+
156
+ const promise = super.addCommand.apply(this, arguments);
157
+ if (promise.then) {
158
+ promise.then(
159
+ function onResolve(_result) {
160
+ finish(null);
161
+ },
162
+ function onReject(err) {
163
+ finish(err);
164
+ },
165
+ );
166
+ } else {
167
+ // didn't get back the expected promose -- just end the
168
+ // span now and accept that those spans will be bogus
169
+ // (sync, zero-ish elapsed time).
170
+ span.end();
171
+ }
172
+ return promise;
173
+ }
174
+ }
175
+ mod.default = RedisCommandsQueueTraced;
176
+
177
+ return mod;
178
+ };
@@ -0,0 +1,49 @@
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
+ // Shim @redis/client's `RedisClient.create` to pass client options down to
8
+ // the `RedisCommandsQueue` instrumentation in ./client/commands-queue.js.
9
+ const semver = require('semver');
10
+ const shimmer = require('../../../../../../shimmer');
11
+ const { redisClientOptions } = require('../../../../../../../../lib/symbols');
12
+
13
+ module.exports = function (mod, agent, { version, enabled }) {
14
+ if (!enabled) {
15
+ return mod;
16
+ }
17
+ if (!semver.satisfies(version, '>=1 <2')) {
18
+ agent.logger.debug(
19
+ '@redis/client/dist/lib/client version %s not supported - aborting...',
20
+ version,
21
+ );
22
+ return mod;
23
+ }
24
+
25
+ const RedisClient = mod.default;
26
+ if (!(RedisClient && typeof RedisClient.create === 'function')) {
27
+ agent.logger.debug('cannot instrument @redis/client RedisClient');
28
+ return mod;
29
+ }
30
+
31
+ const ins = agent._instrumentation;
32
+
33
+ agent.logger.debug('shimming @redis/client RedisClient.create');
34
+ shimmer.wrap(RedisClient, 'create', function wrap(origCreate) {
35
+ return function wrappedCreate(options) {
36
+ // Capture the RedisClient options for just the synchronous run of the
37
+ // create function. We are relying on the RedisClient constructor
38
+ // synchronously creating its RedisCommandsQueue, which we've wrapped
39
+ // to pick up these options. This allows the queue instance to determine
40
+ // the appropriate destination context for spans it creates.
41
+ ins[redisClientOptions] = options;
42
+ const rv = origCreate.apply(this, arguments);
43
+ delete ins[redisClientOptions];
44
+ return rv;
45
+ };
46
+ });
47
+
48
+ return mod;
49
+ };
@@ -0,0 +1,198 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and other contributors where applicable.
3
+ * Licensed under the BSD 2-Clause License; you may not use this file except in
4
+ * compliance with the BSD 2-Clause License.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const semver = require('semver');
10
+ const shimmer = require('../../shimmer');
11
+ const elasticAPMMiddlewares = Symbol('elasticAPMMiddlewares');
12
+ const {
13
+ DYNAMODB_NAME,
14
+ DYNAMODB_TYPE,
15
+ DYNAMODB_SUBTYPE,
16
+ dynamoDBMiddlewareFactory,
17
+ dynamoDBShouldIgnoreCommand,
18
+ } = require('../@aws-sdk/client-dynamodb');
19
+ const {
20
+ S3_NAME,
21
+ S3_TYPE,
22
+ S3_SUBTYPE,
23
+ s3MiddlewareFactory,
24
+ s3ShouldIgnoreCommand,
25
+ } = require('../@aws-sdk/client-s3');
26
+ const {
27
+ SNS_NAME,
28
+ SNS_TYPE,
29
+ SNS_SUBTYPE,
30
+ snsMiddlewareFactory,
31
+ snsShouldIgnoreCommand,
32
+ } = require('../@aws-sdk/client-sns');
33
+
34
+ const {
35
+ SQS_NAME,
36
+ SQS_TYPE,
37
+ SQS_SUBTYPE,
38
+ sqsMiddlewareFactory,
39
+ sqsShouldIgnoreCommand,
40
+ } = require('../@aws-sdk/client-sqs');
41
+
42
+ /**
43
+ * We do alias them to a local type
44
+ * @typedef {import('@aws-sdk/types').InitializeMiddleware} InitializeMiddleware
45
+ * @typedef {import('@aws-sdk/types').FinalizeRequestMiddleware } FinalizeRequestMiddleware
46
+ * @typedef {import('@aws-sdk/types').InitializeHandlerOptions} InitializeHandlerOptions
47
+ * @typedef {import('@aws-sdk/types').FinalizeRequestHandlerOptions } FinalizeRequestHandlerOptions
48
+ *
49
+ * Then create our types
50
+ * @typedef {InitializeMiddleware | FinalizeRequestMiddleware} AWSMiddleware
51
+ * @typedef {InitializeHandlerOptions | FinalizeRequestHandlerOptions} AWSMiddlewareOptions
52
+
53
+ * @typedef {object} AWSMiddlewareEntry
54
+ * @property {AWSMiddleware} middleware
55
+ * @property {AWSMiddlewareOptions} options
56
+ */
57
+
58
+ const COMMAND_NAME_RE = /^(\w+)Command$/;
59
+ /**
60
+ * TODO: this method may be shared with other instrumentations
61
+ * For a HeadObject API call, `context.commandName === 'HeadObjectCommand'`.
62
+ *
63
+ * @param {String} commandName
64
+ * @returns {String}
65
+ */
66
+ function opNameFromCommandName(commandName) {
67
+ const match = COMMAND_NAME_RE.exec(commandName);
68
+ if (match) {
69
+ return match[1];
70
+ } else {
71
+ return '<unknown command>';
72
+ }
73
+ }
74
+
75
+ const clientsConfig = {
76
+ DynamoDBClient: {
77
+ NAME: DYNAMODB_NAME,
78
+ TYPE: DYNAMODB_TYPE,
79
+ SUBTYPE: DYNAMODB_SUBTYPE,
80
+ factory: dynamoDBMiddlewareFactory,
81
+ shouldIgnoreCommand: dynamoDBShouldIgnoreCommand,
82
+ },
83
+ S3Client: {
84
+ NAME: S3_NAME,
85
+ TYPE: S3_TYPE,
86
+ SUBTYPE: S3_SUBTYPE,
87
+ factory: s3MiddlewareFactory,
88
+ shouldIgnoreCommand: s3ShouldIgnoreCommand,
89
+ },
90
+ SNSClient: {
91
+ NAME: SNS_NAME,
92
+ TYPE: SNS_TYPE,
93
+ SUBTYPE: SNS_SUBTYPE,
94
+ factory: snsMiddlewareFactory,
95
+ shouldIgnoreCommand: snsShouldIgnoreCommand,
96
+ },
97
+ SQSClient: {
98
+ NAME: SQS_NAME,
99
+ TYPE: SQS_TYPE,
100
+ SUBTYPE: SQS_SUBTYPE,
101
+ factory: sqsMiddlewareFactory,
102
+ shouldIgnoreCommand: sqsShouldIgnoreCommand,
103
+ },
104
+ };
105
+
106
+ module.exports = function (mod, agent, { name, version, enabled }) {
107
+ if (!enabled) return mod;
108
+
109
+ // As of `@aws-sdk/*@3.363.0` the underlying smithy-client is under the
110
+ // `@smithy/` npm org.
111
+ if (
112
+ name === '@smithy/smithy-client' &&
113
+ !semver.satisfies(version, '>=1 <5')
114
+ ) {
115
+ agent.logger.debug(
116
+ 'cannot instrument @aws-sdk/client-*: @smithy/smithy-client version %s not supported',
117
+ version,
118
+ );
119
+ return mod;
120
+ } else if (
121
+ name === '@aws-sdk/smithy-client' &&
122
+ !semver.satisfies(version, '>=3 <4')
123
+ ) {
124
+ agent.logger.debug(
125
+ 'cannot instrument @aws-sdk/client-*: @aws-sdk/smithy-client version %s not supported',
126
+ version,
127
+ );
128
+ return mod;
129
+ }
130
+
131
+ shimmer.wrap(mod.Client.prototype, 'send', function (orig) {
132
+ return function _wrappedSmithyClientSend() {
133
+ const clientName = this.constructor && this.constructor.name;
134
+ const clientConfig = clientsConfig[clientName];
135
+
136
+ if (!clientConfig) {
137
+ return orig.apply(this, arguments);
138
+ }
139
+
140
+ if (!this[elasticAPMMiddlewares]) {
141
+ const factory = clientConfig && clientConfig.factory;
142
+ const middlewares =
143
+ typeof factory === 'function' ? factory(this, agent) : [];
144
+
145
+ // We do the instrumentation by leveraging the middleware mechanism provided by the
146
+ // middlewareStack property of the Client instance. We add the instrumentation middlewares
147
+ // once at the client level so they persist for the whole life of the client instance
148
+ // https://github.com/aws/aws-sdk-js-v3/tree/main/packages/middleware-stack
149
+ this[elasticAPMMiddlewares] = middlewares;
150
+ for (const item of this[elasticAPMMiddlewares]) {
151
+ this.middlewareStack.add(item.middleware, item.options);
152
+ }
153
+ }
154
+
155
+ const command = arguments[0];
156
+
157
+ if (clientConfig.shouldIgnoreCommand(command, agent._conf)) {
158
+ return orig.apply(this, arguments);
159
+ }
160
+
161
+ const opName = opNameFromCommandName(command.constructor.name);
162
+ const name = clientConfig.NAME + ' ' + opName;
163
+
164
+ const ins = agent._instrumentation;
165
+ const span = ins.createSpan(
166
+ name,
167
+ clientConfig.TYPE,
168
+ clientConfig.SUBTYPE,
169
+ opName,
170
+ { exitSpan: true },
171
+ );
172
+
173
+ if (!span) {
174
+ return orig.apply(this, arguments);
175
+ }
176
+
177
+ // Run context notes: The `orig` should run in the context of the S3 span,
178
+ // because that is the point. The user's callback `cb` should run outside of
179
+ // the S3 span.
180
+ const parentRunContext = ins.currRunContext();
181
+ const spanRunContext = parentRunContext.enterSpan(span);
182
+
183
+ // Although the client consumer may use the Promise API `S3Client.send(command).then(...)`
184
+ // the clients may make use of the callback parameter on the super class method (SmithyClient)
185
+ // therefore we need to have this check
186
+ const cb = arguments[arguments.length - 1];
187
+ if (typeof cb === 'function') {
188
+ arguments[arguments.length - 1] = ins.bindFunctionToRunContext(
189
+ parentRunContext,
190
+ cb,
191
+ );
192
+ }
193
+
194
+ return ins.withRunContext(spanRunContext, orig, this, ...arguments);
195
+ };
196
+ });
197
+ return mod;
198
+ };
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Copyright Elasticsearch B.V. and other contributors where applicable.
3
+ * Licensed under the BSD 2-Clause License; you may not use this file except in
4
+ * compliance with the BSD 2-Clause License.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const semver = require('semver');
10
+ const shimmer = require('../shimmer');
11
+ const clone = require('shallow-clone-shim');
12
+
13
+ module.exports = function (apolloServerCore, agent, { version, enabled }) {
14
+ if (!enabled) return apolloServerCore;
15
+
16
+ if (!semver.satisfies(version, '^2.0.2 || ^3.0.0')) {
17
+ agent.logger.debug(
18
+ 'apollo-server-core version %s not supported - aborting...',
19
+ version,
20
+ );
21
+ return apolloServerCore;
22
+ }
23
+
24
+ function wrapRunHttpQuery(orig) {
25
+ return function wrappedRunHttpQuery() {
26
+ var trans = agent._instrumentation.currTransaction();
27
+ if (trans) trans._graphqlRoute = true;
28
+ return orig.apply(this, arguments);
29
+ };
30
+ }
31
+
32
+ if (semver.satisfies(version, '<2.14')) {
33
+ shimmer.wrap(apolloServerCore, 'runHttpQuery', wrapRunHttpQuery);
34
+ return apolloServerCore;
35
+ }
36
+
37
+ // apollo-server-core >= 2.14 does not allow overriding the exports object
38
+ return clone({}, apolloServerCore, {
39
+ runHttpQuery(descriptor) {
40
+ const getter = descriptor.get;
41
+ if (getter) {
42
+ descriptor.get = function get() {
43
+ return wrapRunHttpQuery(getter());
44
+ };
45
+ }
46
+ return descriptor;
47
+ },
48
+ });
49
+ };