@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,30 @@
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 shimmer = require('../../shimmer');
10
+
11
+ module.exports = function (apolloServer, agent, { enabled }) {
12
+ if (!enabled) {
13
+ return apolloServer;
14
+ }
15
+
16
+ function wrapExecuteHTTPGraphQLRequest(orig) {
17
+ return function wrappedExecuteHTTPGraphQLRequest() {
18
+ var trans = agent._instrumentation.currTransaction();
19
+ if (trans) trans._graphqlRoute = true;
20
+ return orig.apply(this, arguments);
21
+ };
22
+ }
23
+
24
+ shimmer.wrap(
25
+ apolloServer.ApolloServer.prototype,
26
+ 'executeHTTPGraphQLRequest',
27
+ wrapExecuteHTTPGraphQLRequest,
28
+ );
29
+ return apolloServer;
30
+ };
@@ -0,0 +1,143 @@
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 constants = require('../../../constants');
10
+ const NAME = 'DynamoDB';
11
+ const TYPE = 'db';
12
+ const SUBTYPE = 'dynamodb';
13
+ const elasticAPMStash = Symbol('elasticAPMStash');
14
+
15
+ /**
16
+ * Returns middlewares to instrument an S3Client instance
17
+ *
18
+ * @param {import('@aws-sdk/client-dynamodb').DynamoDBClient} client
19
+ * @param {any} agent
20
+ * @returns {import('../@smithy/smithy-client').AWSMiddlewareEntry[]}
21
+ */
22
+ function dynamoDBMiddlewareFactory(client, agent) {
23
+ return [
24
+ {
25
+ middleware: (next, context) => async (args) => {
26
+ // Ensure there is a span from the wrapped `client.send()`.
27
+ const span = agent._instrumentation.currSpan();
28
+ if (!span || !(span.type === TYPE && span.subtype === SUBTYPE)) {
29
+ return await next(args);
30
+ }
31
+
32
+ const input = args.input;
33
+ const table = input && input.TableName;
34
+ // The given span comes with the operation name and we need to
35
+ // add the table if applies
36
+ if (table) {
37
+ span.name += ' ' + table;
38
+ }
39
+
40
+ let err;
41
+ let result;
42
+ let response;
43
+ let statusCode;
44
+ try {
45
+ result = await next(args);
46
+ response = result && result.response;
47
+ statusCode = response && response.statusCode;
48
+ } catch (ex) {
49
+ // Save the error for use in `finally` below, but re-throw it to
50
+ // not impact code flow.
51
+ err = ex;
52
+
53
+ // This code path happens with a GetObject conditional request
54
+ // that returns a 304 Not Modified.
55
+ statusCode = err && err.$metadata && err.$metadata.httpStatusCode;
56
+ throw ex;
57
+ } finally {
58
+ if (statusCode) {
59
+ span._setOutcomeFromHttpStatusCode(statusCode);
60
+ } else {
61
+ span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
62
+ }
63
+ if (err && (!statusCode || statusCode >= 400)) {
64
+ agent.captureError(err, { skipOutcome: true });
65
+ }
66
+
67
+ const config = client.config;
68
+ const region = await config.region();
69
+
70
+ // Set the db context
71
+ const dbContext = { type: SUBTYPE }; // dynamodb
72
+ if (region) {
73
+ dbContext.instance = region;
74
+ }
75
+ if (input && input.KeyConditionExpression) {
76
+ dbContext.statement = input.KeyConditionExpression;
77
+ }
78
+ span.setDbContext(dbContext);
79
+
80
+ // Set destination context
81
+ const destContext = {};
82
+ if (context[elasticAPMStash]) {
83
+ destContext.address = context[elasticAPMStash].hostname;
84
+ destContext.port = context[elasticAPMStash].port;
85
+ }
86
+ if (region) {
87
+ destContext.service = { resource: `dynamodb/${region}` };
88
+ destContext.cloud = { region };
89
+ }
90
+ span._setDestinationContext(destContext);
91
+
92
+ // TODO: add OTel attributes when spec is merged
93
+ // https://github.com/elastic/apm/pull/817
94
+
95
+ span.end();
96
+ }
97
+
98
+ return result;
99
+ },
100
+ options: { step: 'initialize', priority: 'high', name: 'elasticAPMSpan' },
101
+ },
102
+ {
103
+ middleware: (next, context) => async (args) => {
104
+ const req = args.request;
105
+ let port = req.port;
106
+
107
+ // Resolve port for HTTP(S) protocols
108
+ if (port === undefined) {
109
+ if (req.protocol === 'https:') {
110
+ port = 443;
111
+ } else if (req.protocol === 'http:') {
112
+ port = 80;
113
+ }
114
+ }
115
+
116
+ context[elasticAPMStash] = {
117
+ hostname: req.hostname,
118
+ port,
119
+ };
120
+ return next(args);
121
+ },
122
+ options: { step: 'finalizeRequest', name: 'elasticAPMHTTPInfo' },
123
+ },
124
+ ];
125
+ }
126
+
127
+ /**
128
+ * Tells if the command needs to be ingored
129
+ * @param {import('@aws-sdk/types').Command} command the command sent by the SNS client
130
+ * @param {any} config the agent configuration
131
+ * @returns {boolean} false if the command should create a span
132
+ */
133
+ function dynamoDBShouldIgnoreCommand(command, config) {
134
+ return false;
135
+ }
136
+
137
+ module.exports = {
138
+ DYNAMODB_NAME: NAME,
139
+ DYNAMODB_TYPE: TYPE,
140
+ DYNAMODB_SUBTYPE: SUBTYPE,
141
+ dynamoDBMiddlewareFactory,
142
+ dynamoDBShouldIgnoreCommand,
143
+ };
@@ -0,0 +1,230 @@
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 constants = require('../../../constants');
10
+ const NAME = 'S3';
11
+ const TYPE = 'storage';
12
+ const SUBTYPE = 's3';
13
+ const elasticAPMStash = Symbol('elasticAPMStash');
14
+
15
+ /**
16
+ * Gets the region from the ARN
17
+ *
18
+ * @param {String} s3Arn
19
+ * @returns {String}
20
+ */
21
+ function regionFromS3Arn(s3Arn) {
22
+ return s3Arn.split(':')[3];
23
+ }
24
+
25
+ /**
26
+ * Return an APM "resource" string for the bucket, Access Point ARN, or Outpost
27
+ * ARN. ARNs are normalized to a shorter resource name.
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
+ *
39
+ * @param {String} bucket The bucket string
40
+ * @returns {String | null}
41
+ */
42
+ function resourceFromBucket(bucket) {
43
+ let resource = null;
44
+ if (bucket) {
45
+ resource = bucket;
46
+ if (resource.startsWith('arn:')) {
47
+ resource = bucket.split(':').slice(5).join(':');
48
+ }
49
+ }
50
+ return resource;
51
+ }
52
+
53
+ /**
54
+ * Returns middlewares to instrument an S3Client instance
55
+ *
56
+ * @param {import('@aws-sdk/client-s3').S3Client} client
57
+ * @param {any} agent
58
+ * @returns {import('./smithy-client').AWSMiddlewareEntry[]}
59
+ */
60
+ function s3MiddlewareFactory(client, agent) {
61
+ return [
62
+ {
63
+ middleware: (next, context) => async (args) => {
64
+ // Ensure there is a span from the wrapped `client.send()`.
65
+ const span = agent._instrumentation.currSpan();
66
+ if (!span || !(span.type === TYPE && span.subtype === SUBTYPE)) {
67
+ return await next(args);
68
+ }
69
+
70
+ const input = args.input;
71
+ const bucket = input && input.Bucket;
72
+ const resource = resourceFromBucket(bucket);
73
+ // The given span comes with the operation name and we need to
74
+ // add the resource if applies
75
+ if (resource) {
76
+ span.name += ' ' + resource;
77
+ span.setServiceTarget('s3', resource);
78
+ }
79
+
80
+ // As for now OTel spec defines attributes for operations that require a Bucket
81
+ // if that changes we should review this guard
82
+ // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/semantic_conventions/trace/instrumentation/aws-sdk.yml#L435
83
+ if (bucket) {
84
+ const otelAttrs = span._getOTelAttributes();
85
+
86
+ otelAttrs['aws.s3.bucket'] = bucket;
87
+
88
+ if (input.Key) {
89
+ otelAttrs['aws.s3.key'] = input.Key;
90
+ }
91
+ }
92
+
93
+ let err;
94
+ let result;
95
+ let response;
96
+ let statusCode;
97
+ try {
98
+ result = await next(args);
99
+ response = result && result.response;
100
+ statusCode = response && response.statusCode;
101
+ } catch (ex) {
102
+ // Save the error for use in `finally` below, but re-throw it to
103
+ // not impact code flow.
104
+ err = ex;
105
+
106
+ // This code path happens with a GetObject conditional request
107
+ // that returns a 304 Not Modified.
108
+ statusCode = err && err.$metadata && err.$metadata.httpStatusCode;
109
+ throw ex;
110
+ } finally {
111
+ if (statusCode) {
112
+ span._setOutcomeFromHttpStatusCode(statusCode);
113
+ } else {
114
+ span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
115
+ }
116
+ if (err && (!statusCode || statusCode >= 400)) {
117
+ agent.captureError(err, { skipOutcome: true });
118
+ }
119
+
120
+ // Set the httpContext
121
+ if (statusCode) {
122
+ const httpContext = {
123
+ status_code: statusCode,
124
+ };
125
+
126
+ if (
127
+ response &&
128
+ response.headers &&
129
+ response.headers['content-length']
130
+ ) {
131
+ const encodedBodySize = Number(
132
+ response.headers['content-length'],
133
+ );
134
+ if (!isNaN(encodedBodySize)) {
135
+ httpContext.response = { encoded_body_size: encodedBodySize };
136
+ }
137
+ }
138
+ span.setHttpContext(httpContext);
139
+ }
140
+
141
+ // Configuring `new S3Client({useArnRegion:true})` allows one to
142
+ // use an Access Point bucket ARN for a region *other* than the
143
+ // one for which the client is configured. Therefore, we attempt
144
+ // to get the bucket region from the ARN first.
145
+ const config = client.config;
146
+ let useArnRegion;
147
+ if (typeof config.useArnRegion === 'boolean') {
148
+ useArnRegion = config.useArnRegion;
149
+ } else {
150
+ useArnRegion = await config.useArnRegion();
151
+ }
152
+
153
+ let region;
154
+ if (useArnRegion && bucket && bucket.startsWith('arn:')) {
155
+ region = regionFromS3Arn(args.input.Bucket);
156
+ } else {
157
+ region =
158
+ typeof config.region === 'boolean'
159
+ ? region
160
+ : await config.region();
161
+ }
162
+
163
+ // Destination context.
164
+ const destContext = {
165
+ service: {
166
+ name: SUBTYPE,
167
+ type: TYPE,
168
+ },
169
+ };
170
+ if (context[elasticAPMStash]) {
171
+ destContext.address = context[elasticAPMStash].hostname;
172
+ destContext.port = context[elasticAPMStash].port;
173
+ }
174
+ if (resource) {
175
+ destContext.service.resource = resource;
176
+ }
177
+ if (region) {
178
+ destContext.cloud = { region };
179
+ }
180
+ span._setDestinationContext(destContext);
181
+
182
+ span.end();
183
+ }
184
+
185
+ return result;
186
+ },
187
+ options: { step: 'initialize', priority: 'high', name: 'elasticAPMSpan' },
188
+ },
189
+ {
190
+ middleware: (next, context) => async (args) => {
191
+ const req = args.request;
192
+ let port = req.port;
193
+
194
+ // Resolve port for HTTP(S) protocols
195
+ if (port === undefined) {
196
+ if (req.protocol === 'https:') {
197
+ port = 443;
198
+ } else if (req.protocol === 'http:') {
199
+ port = 80;
200
+ }
201
+ }
202
+
203
+ context[elasticAPMStash] = {
204
+ hostname: req.hostname,
205
+ port,
206
+ };
207
+ return next(args);
208
+ },
209
+ options: { step: 'finalizeRequest', name: 'elasticAPMHTTPInfo' },
210
+ },
211
+ ];
212
+ }
213
+
214
+ /**
215
+ * Tells if the command needs to be ingored
216
+ * @param {import('@aws-sdk/types').Command} command the command sent by the SNS client
217
+ * @param {any} config the agent configuration
218
+ * @returns {boolean} false if the command should create a span
219
+ */
220
+ function s3ShouldIgnoreCommand(command, config) {
221
+ return false;
222
+ }
223
+
224
+ module.exports = {
225
+ S3_NAME: NAME,
226
+ S3_TYPE: TYPE,
227
+ S3_SUBTYPE: SUBTYPE,
228
+ s3MiddlewareFactory,
229
+ s3ShouldIgnoreCommand,
230
+ };
@@ -0,0 +1,197 @@
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 constants = require('../../../constants');
10
+ const NAME = 'SNS';
11
+ const TYPE = 'messaging';
12
+ const SUBTYPE = 'sns';
13
+ const MAX_SNS_MESSAGE_ATTRIBUTES = 10;
14
+ const elasticAPMStash = Symbol('elasticAPMStash');
15
+
16
+ /**
17
+ * Returns middlewares to instrument an S3Client instance
18
+ *
19
+ * @param {import('@aws-sdk/client-sns').SNSClient} client
20
+ * @param {any} agent
21
+ * @returns {import('./smithy-client').AWSMiddlewareEntry[]}
22
+ */
23
+ function snsMiddlewareFactory(client, agent) {
24
+ return [
25
+ {
26
+ middleware: (next, context) => async (args) => {
27
+ const ins = agent._instrumentation;
28
+ const log = agent.logger;
29
+ const span = ins.currSpan();
30
+ const input = args.input;
31
+
32
+ // W3C trace-context propagation.
33
+ const runContext = ins.currRunContext();
34
+ const parentSpan =
35
+ span || runContext.currSpan() || runContext.currTransaction();
36
+ const targetId =
37
+ input && (input.TopicArn || input.TargetArn || input.PhoneNumber);
38
+
39
+ if (parentSpan) {
40
+ // Though our spec only mentions a 10-message-attribute limit for *SQS*, we'll
41
+ // do the same limit here, because
42
+ // https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html
43
+ // mentions the 10-message-attribute limit for SQS subscriptions.
44
+ input.MessageAttributes = input.MessageAttributes || {};
45
+ const attributesCount = Object.keys(input.MessageAttributes).length;
46
+
47
+ if (attributesCount + 2 > MAX_SNS_MESSAGE_ATTRIBUTES) {
48
+ log.warn(
49
+ 'cannot propagate trace-context with SNS message to %s, too many MessageAttributes',
50
+ targetId,
51
+ );
52
+ } else {
53
+ parentSpan.propagateTraceContextHeaders(
54
+ input.MessageAttributes,
55
+ function (msgAttrs, name, value) {
56
+ if (name.startsWith('elastic-')) {
57
+ return;
58
+ }
59
+ msgAttrs[name] = { DataType: 'String', StringValue: value };
60
+ },
61
+ );
62
+ }
63
+ }
64
+
65
+ // Ensure there is a span from the wrapped `client.send()`.
66
+ if (!span || !(span.type === TYPE && span.subtype === SUBTYPE)) {
67
+ return await next(args);
68
+ }
69
+
70
+ const destTargets = [
71
+ input.TopicArn && input.TopicArn.split(':').pop(),
72
+ input.TargetArn && input.TargetArn.split(':').pop().split('/').pop(),
73
+ input.PhoneNumber && '<PHONE_NUMBER>',
74
+ ];
75
+ const topicName = destTargets.filter((a) => a).join(', ');
76
+
77
+ if (topicName) {
78
+ span.name += ' to ' + topicName;
79
+ }
80
+
81
+ let err;
82
+ let result;
83
+ let response;
84
+ let statusCode;
85
+ try {
86
+ result = await next(args);
87
+ response = result && result.response;
88
+ statusCode = response && response.statusCode;
89
+ } catch (ex) {
90
+ // Save the error for use in `finally` below, but re-throw it to
91
+ // not impact code flow.
92
+ err = ex;
93
+
94
+ // This code path happens with a Publish command that returns a 404 Not Found.
95
+ statusCode = err && err.$metadata && err.$metadata.httpStatusCode;
96
+ throw ex;
97
+ } finally {
98
+ if (statusCode) {
99
+ span._setOutcomeFromHttpStatusCode(statusCode);
100
+ } else {
101
+ span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
102
+ }
103
+ if (err && (!statusCode || statusCode >= 400)) {
104
+ agent.captureError(err, { skipOutcome: true });
105
+ }
106
+
107
+ // Destination context.
108
+ const region = await client.config.region();
109
+ const service = { name: SUBTYPE, type: TYPE };
110
+ const destCtx = { service };
111
+
112
+ if (context[elasticAPMStash]) {
113
+ destCtx.address = context[elasticAPMStash].hostname;
114
+ destCtx.port = context[elasticAPMStash].port;
115
+ }
116
+
117
+ if (region) {
118
+ destCtx.cloud = { region };
119
+ }
120
+ span._setDestinationContext(destCtx);
121
+
122
+ // Message context
123
+ if (topicName) {
124
+ span.setMessageContext({ queue: { name: topicName } });
125
+ }
126
+
127
+ span.end();
128
+ }
129
+
130
+ return result;
131
+ },
132
+ options: { step: 'initialize', priority: 'high', name: 'elasticAPMSpan' },
133
+ },
134
+ {
135
+ middleware: (next, context) => async (args) => {
136
+ const req = args.request;
137
+ let port = req.port;
138
+
139
+ // Resolve port for HTTP(S) protocols
140
+ if (port === undefined) {
141
+ if (req.protocol === 'https:') {
142
+ port = 443;
143
+ } else if (req.protocol === 'http:') {
144
+ port = 80;
145
+ }
146
+ }
147
+
148
+ context[elasticAPMStash] = {
149
+ hostname: req.hostname,
150
+ port,
151
+ };
152
+ return next(args);
153
+ },
154
+ options: { step: 'finalizeRequest', name: 'elasticAPMHTTPInfo' },
155
+ },
156
+ ];
157
+ }
158
+
159
+ /**
160
+ * Tells if the command needs to be ingored
161
+ * @param {import('@aws-sdk/types').Command} command the command sent by the SNS client
162
+ * @param {any} config the agent configuration
163
+ * @returns {boolean} false if the command should create a span
164
+ */
165
+ function snsShouldIgnoreCommand(command, config) {
166
+ if (command.constructor.name !== 'PublishCommand') {
167
+ return true;
168
+ }
169
+
170
+ if (config.ignoreMessageQueuesRegExp) {
171
+ const input = command.input;
172
+ // It's unlikely but input can have multiple targets defined
173
+ // and having a priority list between them may result in
174
+ // not honoring `ignoreMessageQueues` config.
175
+ const topicNames = input
176
+ ? [input.TopicArn, input.TargetArn, input.PhoneNumber].filter((t) => t)
177
+ : [];
178
+
179
+ for (const topicName of topicNames) {
180
+ for (const rule of config.ignoreMessageQueuesRegExp) {
181
+ if (rule.test(topicName)) {
182
+ return true;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ return false;
189
+ }
190
+
191
+ module.exports = {
192
+ SNS_NAME: NAME,
193
+ SNS_TYPE: TYPE,
194
+ SNS_SUBTYPE: SUBTYPE,
195
+ snsMiddlewareFactory,
196
+ snsShouldIgnoreCommand,
197
+ };