@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,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,86 @@
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
+ // Instrument `.metrics.getMeterProvider()` from `@opentelemetry/api` to
10
+ // provide an Elastic APM provider if user code hasn't registered one itself.
11
+ //
12
+ // This covers use case 2 in the OTel metrics spec:
13
+ // https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#exporter-installation
14
+
15
+ const semver = require('semver');
16
+
17
+ const {
18
+ isOTelMetricsFeatSupported,
19
+ } = require('../../../opentelemetry-metrics');
20
+ const shimmer = require('../../shimmer');
21
+
22
+ /**
23
+ * `otel.metrics.getMeterProvider()` returns a singleton instance of the
24
+ * internal `NoopMeterProvider` class if no global meter provider has been
25
+ * set. There isn't an explicitly API to determine if a provider is a noop
26
+ * one. This function attempts to sniff that out.
27
+ *
28
+ * We cannot rely on comparing to the `NOOP_METER_PROVIDER` exported by
29
+ * "src/metrics/NoopMeterProvider.ts" because there might be multiple
30
+ * "@opentelemetry/api" packages in play.
31
+ *
32
+ * @param {import('@opentelemetry/api').MeterProvider}
33
+ * @returns {boolean}
34
+ */
35
+ function isNoopMeterProvider(provider) {
36
+ return !!(
37
+ provider &&
38
+ provider.constructor &&
39
+ provider.constructor.name === 'NoopMeterProvider'
40
+ );
41
+ }
42
+
43
+ module.exports = function (mod, agent, { version, enabled }) {
44
+ const log = agent.logger;
45
+
46
+ if (!enabled) {
47
+ return mod;
48
+ }
49
+ if (!agent._isMetricsEnabled()) {
50
+ log.trace(
51
+ 'metrics are not enabled, skipping @opentelemetry/api instrumentation',
52
+ version,
53
+ );
54
+ return mod;
55
+ }
56
+ if (!semver.satisfies(version, '>=1.3.0 <2', { includePrerelease: true })) {
57
+ log.debug(
58
+ '@opentelemetry/api version %s not supported, skipping @opentelemetry/api instrumentation',
59
+ version,
60
+ );
61
+ return mod;
62
+ }
63
+ if (!isOTelMetricsFeatSupported) {
64
+ log.debug(
65
+ 'elastic-apm-node OTel Metrics support does not support node %s, skipping @opentelemetry/api instrumentation',
66
+ process.version,
67
+ );
68
+ return mod;
69
+ }
70
+
71
+ log.debug('shimming @opentelemetry/api .metrics.getMeterProvider()');
72
+ shimmer.wrap(mod.metrics, 'getMeterProvider', wrapGetMeterProvider);
73
+
74
+ return mod;
75
+
76
+ function wrapGetMeterProvider(orig) {
77
+ return function wrappedGetMeterProvider() {
78
+ const provider = orig.apply(this, arguments);
79
+ if (!isNoopMeterProvider(provider)) {
80
+ return provider;
81
+ }
82
+ const elMeterProvider = agent._getOrCreateOTelMeterProvider();
83
+ return elMeterProvider || provider;
84
+ };
85
+ }
86
+ };
@@ -0,0 +1,79 @@
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
+ // This instruments '@opentelemetry/sdk-metrics' to automatically add a metric
10
+ // reader to any `MeterProvider` created by user code. The added metric
11
+ // reader will export metrics to the configured APM server.
12
+ //
13
+ // This covers use case 1 in the OTel metrics spec:
14
+ // https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#exporter-installation
15
+ //
16
+ // Dev Note: This avoids instrumenting the `MeterProvider` used *internally*
17
+ // by the APM agent itself (see "lib/opentelemetry-metrics/index.js") because
18
+ // that file imports `MeterProvider` before the APM agent is started.
19
+
20
+ const semver = require('semver');
21
+
22
+ const {
23
+ isOTelMetricsFeatSupported,
24
+ createOTelMetricReader,
25
+ } = require('../../../opentelemetry-metrics');
26
+
27
+ module.exports = function (mod, agent, { version, enabled }) {
28
+ const log = agent.logger;
29
+
30
+ if (!enabled) {
31
+ return mod;
32
+ }
33
+ if (!agent._isMetricsEnabled()) {
34
+ log.trace(
35
+ 'metrics are not enabled, skipping @opentelemetry/sdk-metrics instrumentation',
36
+ version,
37
+ );
38
+ return mod;
39
+ }
40
+ // Minimum supported version is 1.11.0 because that version included the fix
41
+ // for side-effects from having two MetricReaders.
42
+ // https://github.com/open-telemetry/opentelemetry-js/issues/3664
43
+ if (!semver.satisfies(version, '>=1.11.0 <2', { includePrerelease: true })) {
44
+ log.debug(
45
+ '@opentelemetry/sdk-metrics@%s is not supported, skipping @opentelemetry/sdk-metrics instrumentation',
46
+ version,
47
+ );
48
+ return mod;
49
+ }
50
+ if (!isOTelMetricsFeatSupported) {
51
+ log.debug(
52
+ 'elastic-apm-node OTel Metrics feature does not support node %s, skipping @opentelemetry/sdk-metrics instrumentation',
53
+ process.version,
54
+ );
55
+ return mod;
56
+ }
57
+
58
+ class ApmMeterProvider extends mod.MeterProvider {
59
+ constructor(...args) {
60
+ super(...args);
61
+ // We create a new metric reader for each new MeterProvider instance,
62
+ // because they shutdown independently -- they cannot be shared between
63
+ // multiple MeterProviders.
64
+ log.trace(
65
+ '@opentelemetry/sdk-metrics ins: create Elastic APM MetricReader',
66
+ );
67
+ this.addMetricReader(createOTelMetricReader(agent));
68
+ }
69
+ }
70
+ Object.defineProperty(mod, 'MeterProvider', {
71
+ configurable: true,
72
+ enumerable: true,
73
+ get: function () {
74
+ return ApmMeterProvider;
75
+ },
76
+ });
77
+
78
+ return mod;
79
+ };
@@ -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
+ };