@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,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,40 @@
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 propwrap = require('../../propwrap');
10
+
11
+ /**
12
+ * Return a patch handler, `function (module, agent, options)`, that will patch
13
+ * the Lambda handler function at the given property path.
14
+ *
15
+ * For example, a Lambda _HANDLER=index.handler indicates that a file "index.js"
16
+ * has a `handler` export that is the Lambda handler function. In this case
17
+ * `module` will be the imported "index.js" module and `propPath` will be
18
+ * "handler".
19
+ */
20
+ function createLambdaPatcher(propPath) {
21
+ return function lambdaHandlerPatcher(module, agent, { enabled }) {
22
+ if (!enabled) {
23
+ return module;
24
+ }
25
+
26
+ try {
27
+ const newMod = propwrap.wrap(module, propPath, (orig) => {
28
+ return agent.lambda(orig);
29
+ });
30
+ return newMod;
31
+ } catch (wrapErr) {
32
+ agent.logger.warn('could not wrap lambda handler: %s', wrapErr);
33
+ return module;
34
+ }
35
+ };
36
+ }
37
+
38
+ module.exports = {
39
+ createLambdaPatcher,
40
+ };
@@ -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
+ };
@@ -0,0 +1,155 @@
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 TYPE = 'db';
10
+ const SUBTYPE = 'dynamodb';
11
+ const ACTION = 'query';
12
+
13
+ function getRegionFromRequest(request) {
14
+ return (
15
+ request &&
16
+ request.service &&
17
+ request.service.config &&
18
+ request.service.config.region
19
+ );
20
+ }
21
+
22
+ function getPortFromRequest(request) {
23
+ return (
24
+ request &&
25
+ request.service &&
26
+ request.service.endpoint &&
27
+ request.service.endpoint.port
28
+ );
29
+ }
30
+
31
+ function getMethodFromRequest(request) {
32
+ const method = request && request.operation;
33
+ if (method) {
34
+ return method[0].toUpperCase() + method.slice(1);
35
+ }
36
+ }
37
+
38
+ function getStatementFromRequest(request) {
39
+ const method = getMethodFromRequest(request);
40
+ if (
41
+ method === 'Query' &&
42
+ request &&
43
+ request.params &&
44
+ request.params.KeyConditionExpression
45
+ ) {
46
+ return request.params.KeyConditionExpression;
47
+ }
48
+ return undefined;
49
+ }
50
+
51
+ function getAddressFromRequest(request) {
52
+ return (
53
+ request &&
54
+ request.service &&
55
+ request.service.endpoint &&
56
+ request.service.endpoint.hostname
57
+ );
58
+ }
59
+
60
+ function getTableFromRequest(request) {
61
+ const table = request && request.params && request.params.TableName;
62
+ if (!table) {
63
+ return '';
64
+ }
65
+ return ` ${table}`;
66
+ }
67
+
68
+ // Creates the span name from request information
69
+ function getSpanNameFromRequest(request) {
70
+ const method = getMethodFromRequest(request);
71
+ const table = getTableFromRequest(request);
72
+ const name = `DynamoDB ${method}${table}`;
73
+ return name;
74
+ }
75
+
76
+ function shouldIgnoreRequest(request, agent) {
77
+ return false;
78
+ }
79
+
80
+ // Main entrypoint for SQS instrumentation
81
+ //
82
+ // Must call (or one of its function calls must call) the
83
+ // `orig` function/method
84
+ function instrumentationDynamoDb(
85
+ orig,
86
+ origArguments,
87
+ request,
88
+ AWS,
89
+ agent,
90
+ { version, enabled },
91
+ ) {
92
+ if (shouldIgnoreRequest(request, agent)) {
93
+ return orig.apply(request, origArguments);
94
+ }
95
+
96
+ const ins = agent._instrumentation;
97
+ const name = getSpanNameFromRequest(request);
98
+ const span = ins.createSpan(name, TYPE, SUBTYPE, ACTION, { exitSpan: true });
99
+ if (!span) {
100
+ return orig.apply(request, origArguments);
101
+ }
102
+
103
+ const region = getRegionFromRequest(request);
104
+ const dbContext = {
105
+ type: SUBTYPE,
106
+ instance: region,
107
+ };
108
+ const dbStatement = getStatementFromRequest(request);
109
+ if (dbStatement) {
110
+ dbContext.statement = dbStatement;
111
+ }
112
+ span.setDbContext(dbContext);
113
+ span._setDestinationContext({
114
+ address: getAddressFromRequest(request),
115
+ port: getPortFromRequest(request),
116
+ cloud: {
117
+ region,
118
+ },
119
+ });
120
+
121
+ const onComplete = function (response) {
122
+ if (response && response.error) {
123
+ agent.captureError(response.error);
124
+ }
125
+ span.end();
126
+ };
127
+ // Bind onComplete to the span's run context so that `captureError` picks
128
+ // up the correct currentSpan.
129
+ const parentRunContext = ins.currRunContext();
130
+ const spanRunContext = parentRunContext.enterSpan(span);
131
+ request.on(
132
+ 'complete',
133
+ ins.bindFunctionToRunContext(spanRunContext, onComplete),
134
+ );
135
+
136
+ const cb = origArguments[origArguments.length - 1];
137
+ if (typeof cb === 'function') {
138
+ origArguments[origArguments.length - 1] = ins.bindFunctionToRunContext(
139
+ parentRunContext,
140
+ cb,
141
+ );
142
+ }
143
+ return ins.withRunContext(spanRunContext, orig, request, ...origArguments);
144
+ }
145
+
146
+ module.exports = {
147
+ instrumentationDynamoDb,
148
+
149
+ // exported for testing
150
+ getRegionFromRequest,
151
+ getPortFromRequest,
152
+ getStatementFromRequest,
153
+ getAddressFromRequest,
154
+ getMethodFromRequest,
155
+ };
@@ -0,0 +1,184 @@
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 AWS S3 operations via the 'aws-sdk' package.
10
+
11
+ const constants = require('../../../constants');
12
+
13
+ const TYPE = 'storage';
14
+ const SUBTYPE = 's3';
15
+
16
+ // Return the PascalCase operation name from `request.operation` by undoing to
17
+ // `lowerFirst()` from
18
+ // https://github.com/aws/aws-sdk-js/blob/c0c44b8a4e607aae521686898f39a3e359f727e4/lib/model/api.js#L63-L65
19
+ //
20
+ // For example: 'headBucket' -> 'HeadBucket'
21
+ function opNameFromOperation(operation) {
22
+ return operation[0].toUpperCase() + operation.slice(1);
23
+ }
24
+
25
+ // Return an APM "resource" string for the bucket, Access Point ARN, or Outpost
26
+ // ARN. ARNs are normalized to a shorter resource name.
27
+ //
28
+ // Known ARN patterns:
29
+ // - arn:aws:s3:<region>:<account-id>:accesspoint/<accesspoint-name>
30
+ // - arn:aws:s3-outposts:<region>:<account>:outpost/<outpost-id>/bucket/<bucket-name>
31
+ // - arn:aws:s3-outposts:<region>:<account>:outpost/<outpost-id>/accesspoint/<accesspoint-name>
32
+ //
33
+ // In general that is:
34
+ // arn:$partition:$service:$region:$accountId:$resource
35
+ //
36
+ // This parses using the same "split on colon" used by the JavaScript AWS SDK v3.
37
+ // https://github.com/aws/aws-sdk-js-v3/blob/v3.18.0/packages/util-arn-parser/src/index.ts#L14-L37
38
+ function resourceFromBucket(bucket) {
39
+ let resource = null;
40
+ if (bucket) {
41
+ resource = bucket;
42
+ if (resource.startsWith('arn:')) {
43
+ resource = bucket.split(':').slice(5).join(':');
44
+ }
45
+ }
46
+ return resource;
47
+ }
48
+
49
+ // Instrument an awk-sdk@2.x operation (i.e. a AWS.Request.send or
50
+ // AWS.Request.promise).
51
+ //
52
+ // @param {AWS.Request} request https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html
53
+ function instrumentationS3(
54
+ orig,
55
+ origArguments,
56
+ request,
57
+ AWS,
58
+ agent,
59
+ { version, enabled },
60
+ ) {
61
+ const opName = opNameFromOperation(request.operation);
62
+ const params = request.params;
63
+ const bucket = params && params.Bucket;
64
+ const resource = resourceFromBucket(bucket);
65
+ let name = 'S3 ' + opName;
66
+ if (resource) {
67
+ name += ' ' + resource;
68
+ }
69
+
70
+ const ins = agent._instrumentation;
71
+
72
+ const span = ins.createSpan(name, TYPE, SUBTYPE, opName, { exitSpan: true });
73
+ if (!span) {
74
+ return orig.apply(request, origArguments);
75
+ }
76
+
77
+ // As for now OTel spec defines attributes for operations that require a Bucket
78
+ // if that changes we should review this guard
79
+ // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/semantic_conventions/trace/instrumentation/aws-sdk.yml#L435
80
+ if (bucket) {
81
+ const otelAttrs = span._getOTelAttributes();
82
+
83
+ otelAttrs['aws.s3.bucket'] = bucket;
84
+
85
+ if (params.Key) {
86
+ otelAttrs['aws.s3.key'] = params.Key;
87
+ }
88
+ }
89
+
90
+ const onComplete = function (response) {
91
+ // `response` is an AWS.Response
92
+ // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Response.html
93
+
94
+ // Determining the bucket's region.
95
+ // `request.httpRequest.region` isn't documented, but the aws-sdk@2
96
+ // lib/services/s3.js will set it to the bucket's determined region.
97
+ // This can be asynchronously determined -- e.g. if it differs from the
98
+ // configured service endpoint region -- so this won't be set until
99
+ // 'complete'.
100
+ const httpRequest = request.httpRequest;
101
+ const region = httpRequest && httpRequest.region;
102
+
103
+ span.setServiceTarget('s3', resource);
104
+ const destContext = {};
105
+ // '.httpRequest.endpoint' might differ from '.service.endpoint' if
106
+ // the bucket is in a different region.
107
+ const endpoint = httpRequest && httpRequest.endpoint;
108
+ if (endpoint) {
109
+ destContext.address = endpoint.hostname;
110
+ destContext.port = endpoint.port;
111
+ }
112
+ if (region) {
113
+ destContext.cloud = { region };
114
+ }
115
+ span._setDestinationContext(destContext);
116
+
117
+ if (response) {
118
+ // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/HttpResponse.html
119
+ const httpResponse = response.httpResponse;
120
+ let statusCode;
121
+ if (httpResponse) {
122
+ statusCode = httpResponse.statusCode;
123
+
124
+ // Set HTTP context. Some context not being set, though it is available:
125
+ // - method: Not that helpful.
126
+ // - url: Mostly redundant with context.destination.address.
127
+ // - response.headers: A lot of added size for uncertain utility. The
128
+ // inclusion of Amazon's request ID headers might be worth it.
129
+ const httpContext = {
130
+ status_code: statusCode,
131
+ };
132
+ const encodedBodySize =
133
+ Buffer.isBuffer(httpResponse.body) && httpResponse.body.byteLength;
134
+ if (encodedBodySize) {
135
+ // I'm not actually sure if this might be decoded_body_size.
136
+ httpContext.response = { encoded_body_size: encodedBodySize };
137
+ }
138
+ span.setHttpContext(httpContext);
139
+ }
140
+
141
+ // Follow the spec for HTTP client span outcome.
142
+ // https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-http.md#outcome
143
+ //
144
+ // For example, a S3 GetObject conditional request (e.g. using the
145
+ // IfNoneMatch param) will respond with response.error=NotModifed and
146
+ // statusCode=304. This is a *successful* outcome.
147
+ if (statusCode) {
148
+ span._setOutcomeFromHttpStatusCode(statusCode);
149
+ } else {
150
+ // `statusCode` will be undefined for errors before sending a request, e.g.:
151
+ // InvalidConfiguration: Custom endpoint is not compatible with access point ARN
152
+ span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
153
+ }
154
+
155
+ if (response.error && (!statusCode || statusCode >= 400)) {
156
+ agent.captureError(response.error, { skipOutcome: true });
157
+ }
158
+ }
159
+
160
+ span.end();
161
+ };
162
+
163
+ // Run context notes: The `orig` should run in the context of the S3 span,
164
+ // because that is the point. The user's callback `cb` should run outside of
165
+ // the S3 span.
166
+ const parentRunContext = ins.currRunContext();
167
+ const spanRunContext = parentRunContext.enterSpan(span);
168
+ const cb = origArguments[origArguments.length - 1];
169
+ if (typeof cb === 'function') {
170
+ origArguments[origArguments.length - 1] = ins.bindFunctionToRunContext(
171
+ parentRunContext,
172
+ cb,
173
+ );
174
+ }
175
+ request.on(
176
+ 'complete',
177
+ ins.bindFunctionToRunContext(spanRunContext, onComplete),
178
+ );
179
+ return ins.withRunContext(spanRunContext, orig, request, ...origArguments);
180
+ }
181
+
182
+ module.exports = {
183
+ instrumentationS3,
184
+ };