@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,35 @@
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
+ /**
8
+ * Central location for shared constants
9
+ */
10
+
11
+ module.exports = {
12
+ // The default span or transaction `type`.
13
+ DEFAULT_SPAN_TYPE: 'custom',
14
+
15
+ REDACTED: '[REDACTED]',
16
+ OUTCOME_FAILURE: 'failure',
17
+ OUTCOME_SUCCESS: 'success',
18
+ OUTCOME_UNKNOWN: 'unknown',
19
+ RESULT_SUCCESS: 'success',
20
+ RESULT_FAILURE: 'failure',
21
+
22
+ // https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-messaging.md#receiving-trace-context
23
+ MAX_MESSAGES_PROCESSED_FOR_TRACE_CONTEXT: 1000,
24
+
25
+ // Config constants
26
+ INTAKE_STRING_MAX_SIZE: 1024,
27
+ CAPTURE_ERROR_LOG_STACK_TRACES_NEVER: 'never',
28
+ CAPTURE_ERROR_LOG_STACK_TRACES_MESSAGES: 'messages',
29
+ CAPTURE_ERROR_LOG_STACK_TRACES_ALWAYS: 'always',
30
+ CONTEXT_MANAGER_ASYNCHOOKS: 'asynchooks',
31
+ CONTEXT_MANAGER_ASYNCLOCALSTORAGE: 'asynclocalstorage',
32
+ TRACE_CONTINUATION_STRATEGY_CONTINUE: 'continue',
33
+ TRACE_CONTINUATION_STRATEGY_RESTART: 'restart',
34
+ TRACE_CONTINUATION_STRATEGY_RESTART_EXTERNAL: 'restart_external',
35
+ };
package/lib/errors.js ADDED
@@ -0,0 +1,303 @@
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
+ // Handle creating error event objects to be sent to APM server.
10
+ // https://github.com/elastic/apm-server/blob/master/docs/spec/v2/error.json
11
+
12
+ const crypto = require('crypto');
13
+ var path = require('path');
14
+ const util = require('util');
15
+
16
+ const { gatherStackTrace } = require('./stacktraces');
17
+
18
+ const MYSQL_ERROR_MSG_RE = /(ER_[A-Z_]+): /;
19
+
20
+ // ---- internal support functions
21
+
22
+ // Default `culprit` to the top of the stack or the highest non `library_frame`
23
+ // frame if such exists
24
+ function culpritFromStacktrace(frames) {
25
+ if (frames.length === 0) return;
26
+
27
+ var filename = frames[0].filename;
28
+ var fnName = frames[0].function;
29
+ for (var n = 0; n < frames.length; n++) {
30
+ if (!frames[n].library_frame) {
31
+ filename = frames[n].filename;
32
+ fnName = frames[n].function;
33
+ break;
34
+ }
35
+ }
36
+
37
+ return filename ? fnName + ' (' + filename + ')' : fnName;
38
+ }
39
+
40
+ // Infer the node.js module name from the top frame filename, if possible.
41
+ // Here `frames` is a data structure as returned by `parseStackTrace`.
42
+ //
43
+ // Examples:
44
+ // node_modules/mymodule/index.js
45
+ // ^^^^^^^^
46
+ // node_modules/@myorg/mymodule/index.js
47
+ // ^^^^^^^^^^^^^^^
48
+ // or on Windows:
49
+ // node_modules\@myorg\mymodule\lib\subpath\index.js
50
+ // ^^^^^^^^^^^^^^^
51
+ function _moduleNameFromFrames(frames) {
52
+ if (frames.length === 0) {
53
+ return null;
54
+ }
55
+ var frame = frames[0];
56
+ if (!frame.library_frame) {
57
+ return null;
58
+ }
59
+ var idx = frame.filename.lastIndexOf('node_modules' + path.sep);
60
+ if (idx === -1) {
61
+ return null;
62
+ }
63
+ var parts = frame.filename.slice(idx).split(path.sep);
64
+ if (!parts[1]) {
65
+ return null;
66
+ } else if (parts[1].startsWith('@')) {
67
+ if (!parts[2]) {
68
+ // node_modules/@foo
69
+ return null;
70
+ } else {
71
+ // Normalize the module name separator to '/', even on Windows.
72
+ return parts[1] + '/' + parts[2];
73
+ }
74
+ } else {
75
+ return parts[1];
76
+ }
77
+ }
78
+
79
+ // Gather properties from `err` to be used for `error.exception.attributes`.
80
+ // If there are no properties to include, then it returns undefined.
81
+ function attributesFromErr(err) {
82
+ let n = 0;
83
+ const attrs = {};
84
+ const keys = Object.keys(err);
85
+ for (let i = 0; i < keys.length; i++) {
86
+ const key = keys[i];
87
+ if (key === 'stack') {
88
+ continue; // 'stack' seems to be enumerable in Node 0.11
89
+ }
90
+ if (key === 'code') {
91
+ continue; // 'code' is already used for `error.exception.code`
92
+ }
93
+
94
+ let val = err[key];
95
+ if (val === null) {
96
+ continue; // null is typeof object and well break the switch below
97
+ }
98
+ switch (typeof val) {
99
+ case 'function':
100
+ continue;
101
+ case 'object':
102
+ // Ignore all objects except Dates.
103
+ if (
104
+ typeof val.toISOString !== 'function' ||
105
+ typeof val.getTime !== 'function'
106
+ ) {
107
+ continue;
108
+ } else if (Number.isNaN(val.getTime())) {
109
+ val = 'Invalid Date'; // calling toISOString() on invalid dates throws
110
+ } else {
111
+ val = val.toISOString();
112
+ }
113
+ }
114
+ attrs[key] = val;
115
+ n++;
116
+ }
117
+ return n ? attrs : undefined;
118
+ }
119
+
120
+ // ---- exports
121
+
122
+ function generateErrorId() {
123
+ return crypto.randomBytes(16).toString('hex');
124
+ }
125
+
126
+ // Create an "error" APM event object to be sent to APM server.
127
+ //
128
+ // Required args:
129
+ // - Exactly one of `args.exception` or `args.logMessage` must be set.
130
+ // `args.exception` is an Error instance. `args.logMessage` is a log message
131
+ // string, or an object of the form `{ message: 'template', params: [ ... ]}`
132
+ // which will be formated with `util.format()`.
133
+ // - `args.id` - An ID for the error. It should be created with
134
+ // `errors.generateErrorId()`.
135
+ // - `args.log` {Logger}
136
+ // - `args.shouldCaptureAttributes` {Boolean}
137
+ // - `args.timestampUs` {Integer} - Timestamp of the error in microseconds.
138
+ // - `args.handled` {Boolean}
139
+ // - `args.sourceLinesAppFrames` {Integer} - Number of lines of source context
140
+ // to include in stack traces.
141
+ // - `args.sourceLinesLibraryFrames` {Integer} - Number of lines of source
142
+ // context to include in stack traces. This and the previous arg are typically
143
+ // select from the agent `sourceLines{Error,Span}{App,Library}Frames` config
144
+ // vars.
145
+ //
146
+ // Optional args:
147
+ // - `args.callSiteLoc` - A `Error.captureStackTrace`d object with a stack to
148
+ // include as `error.log.stacktrace`.
149
+ // - `args.traceContext` - The current TraceContext, if any.
150
+ // - `args.trans` - The current transaction, if any.
151
+ // - `args.errorContext` - An object to be included as `error.context`.
152
+ // - `args.message` - A message string that will be included as `error.log.message`
153
+ // if `args.exception` is given. Ignored if `args.logMessage` is given.
154
+ // - `args.exceptionType` - A string to use for `error.exception.type`. By
155
+ // default `args.exception.name` is used. This argument is only relevant if
156
+ // `args.exception` was provided.
157
+ //
158
+ // This always calls back with `cb(null, apmError)`, i.e. it doesn't fail.
159
+ function createAPMError(args, cb) {
160
+ let numAsyncStepsRemaining = 0; // finish() will call cb() only when this is 0.
161
+
162
+ const error = {
163
+ id: args.id,
164
+ timestamp: args.timestampUs,
165
+ };
166
+ if (args.traceContext) {
167
+ error.parent_id = args.traceContext.traceparent.id;
168
+ error.trace_id = args.traceContext.traceparent.traceId;
169
+ }
170
+ if (args.trans) {
171
+ error.transaction_id = args.trans.id;
172
+ error.transaction = {
173
+ name: args.trans.name,
174
+ type: args.trans.type,
175
+ sampled: args.trans.sampled,
176
+ };
177
+ }
178
+ if (args.errorContext) {
179
+ error.context = args.errorContext;
180
+ }
181
+
182
+ if (args.exception) {
183
+ // Handle an exception, i.e. `captureError(<an Error instance>, ...)`.
184
+ const err = args.exception;
185
+ const errMsg = String(err.message);
186
+ error.exception = {
187
+ message: errMsg,
188
+ type: args.exceptionType || String(err.name),
189
+ handled: args.handled,
190
+ };
191
+
192
+ if ('code' in err) {
193
+ error.exception.code = String(err.code);
194
+ } else {
195
+ // To provide better grouping of mysql errors that happens after the async
196
+ // boundery, we modify to exception type to include the custom mysql error
197
+ // type (e.g. ER_PARSE_ERROR)
198
+ var match = errMsg.match(MYSQL_ERROR_MSG_RE);
199
+ if (match) {
200
+ error.exception.code = match[1];
201
+ }
202
+ }
203
+
204
+ // Optional add an alternative error message as well as the exception message.
205
+ if (args.message && typeof args.message === 'string') {
206
+ error.log = { message: args.message };
207
+ }
208
+
209
+ if (args.shouldCaptureAttributes) {
210
+ const attrs = attributesFromErr(err);
211
+ if (attrs) {
212
+ error.exception.attributes = attrs;
213
+ }
214
+ }
215
+
216
+ numAsyncStepsRemaining++;
217
+ gatherStackTrace(
218
+ args.log,
219
+ args.exception,
220
+ args.sourceLinesAppFrames,
221
+ args.sourceLinesLibraryFrames,
222
+ null, // filterCallSite
223
+ function (_err, stacktrace) {
224
+ // _err from gatherStackTrace is always null.
225
+
226
+ const culprit = culpritFromStacktrace(stacktrace);
227
+ if (culprit) {
228
+ error.culprit = culprit;
229
+ }
230
+ const moduleName = _moduleNameFromFrames(stacktrace);
231
+ if (moduleName) {
232
+ // TODO: consider if we should include this as it's not originally what module was intended for
233
+ error.exception.module = moduleName;
234
+ }
235
+ error.exception.stacktrace = stacktrace;
236
+ finish();
237
+ },
238
+ );
239
+ } else {
240
+ // Handle a logMessage, i.e. `captureError(<not an Error instance>, ...)`.
241
+ error.log = {};
242
+ const msg = args.logMessage;
243
+ if (typeof msg === 'string') {
244
+ error.log.message = msg;
245
+ } else if (typeof msg === 'object' && msg !== null) {
246
+ if (msg.message) {
247
+ error.log.message = util.format.apply(
248
+ this,
249
+ [msg.message].concat(msg.params),
250
+ );
251
+ error.log.param_message = msg.message;
252
+ } else {
253
+ error.log.message = util.inspect(msg);
254
+ }
255
+ } else {
256
+ error.log.message = String(msg);
257
+ }
258
+ }
259
+
260
+ if (args.callSiteLoc) {
261
+ numAsyncStepsRemaining++;
262
+ gatherStackTrace(
263
+ args.log,
264
+ args.callSiteLoc,
265
+ args.sourceLinesAppFrames,
266
+ args.sourceLinesLibraryFrames,
267
+ null, // filterCallSite
268
+ function (_err, stacktrace) {
269
+ // _err from gatherStackTrace is always null.
270
+
271
+ if (stacktrace) {
272
+ // In case there isn't any log object, we'll make a dummy message
273
+ // as the APM Server requires a message to be present if a
274
+ // stacktrace also present
275
+ if (!error.log) {
276
+ error.log = { message: error.exception.message };
277
+ }
278
+ error.log.stacktrace = stacktrace;
279
+ finish();
280
+ }
281
+ },
282
+ );
283
+ } else {
284
+ numAsyncStepsRemaining++;
285
+ setImmediate(finish);
286
+ }
287
+
288
+ function finish() {
289
+ numAsyncStepsRemaining--;
290
+ if (numAsyncStepsRemaining === 0) {
291
+ cb(null, error);
292
+ }
293
+ }
294
+ }
295
+
296
+ module.exports = {
297
+ generateErrorId,
298
+ createAPMError,
299
+
300
+ // Exported for testing.
301
+ attributesFromErr,
302
+ _moduleNameFromFrames,
303
+ };
@@ -0,0 +1,69 @@
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
+ const querystring = require('querystring');
9
+
10
+ const HEADER_FORM_URLENCODED = 'application/x-www-form-urlencoded';
11
+ const REDACTED = require('../constants').REDACTED;
12
+
13
+ /**
14
+ * Handles req.body as object or string
15
+ *
16
+ * Express provides multiple body parser middlewares with x-www-form-urlencoded
17
+ * handling. See http://expressjs.com/en/resources/middleware/body-parser.html
18
+ *
19
+ * @param {Object | String} body
20
+ * @param {Object} requestHeaders
21
+ * @param {Array<RegExp>} regexes
22
+ * @returns {Object | String} a copy of the body with the redacted fields
23
+ */
24
+ function redactKeysFromPostedFormVariables(body, requestHeaders, regexes) {
25
+ // only redact from application/x-www-form-urlencoded
26
+ if (HEADER_FORM_URLENCODED !== requestHeaders['content-type']) {
27
+ return body;
28
+ }
29
+
30
+ // if body is a plain object, use redactKeysFromObject
31
+ if (body !== null && !Buffer.isBuffer(body) && typeof body === 'object') {
32
+ return redactKeysFromObject(body, regexes);
33
+ }
34
+
35
+ // if body is a string, use querystring to create object,
36
+ // pass to redactKeysFromObject, and reserialize as string
37
+ if (typeof body === 'string') {
38
+ const objBody = redactKeysFromObject(querystring.parse(body), regexes);
39
+ return querystring.stringify(objBody);
40
+ }
41
+
42
+ return body;
43
+ }
44
+
45
+ /**
46
+ * Returns a copy of the provided object. Each entry of the copy will have
47
+ * its value REDACTEd if the key matches any of the regexes
48
+ *
49
+ * @param {Object} obj The source object be copied with redacted fields
50
+ * @param {Array<RegExp>} regexes RegExps to check if the entry value needd to be redacted
51
+ * @param {String} redactedStr The string to use for redacted values. Defaults to '[REDACTED]'.
52
+ * @returns {Object} Copy of the source object with REDACTED entries or the original if falsy or regexes is not an array
53
+ */
54
+ function redactKeysFromObject(obj, regexes, redactedStr = REDACTED) {
55
+ if (!obj || !Array.isArray(regexes)) {
56
+ return obj;
57
+ }
58
+ const result = {};
59
+ for (const key of Object.keys(obj)) {
60
+ const shouldRedact = regexes.some((regex) => regex.test(key));
61
+ result[key] = shouldRedact ? redactedStr : obj[key];
62
+ }
63
+ return result;
64
+ }
65
+
66
+ module.exports = {
67
+ redactKeysFromObject,
68
+ redactKeysFromPostedFormVariables,
69
+ };
@@ -0,0 +1,249 @@
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
+ // A lib that provides a slightly enhanced method for making an HTTP request.
10
+
11
+ const { URL } = require('url');
12
+
13
+ // Getting handles on these `.request()` functions at the top-level ensures
14
+ // that we have *un*instrumented functions for internal HTTP request usage.
15
+ const coreHttpRequest = require('http').request;
16
+ const coreHttpsRequest = require('https').request;
17
+
18
+ // From https://github.com/nodejs/node/blob/v14.15.4/lib/internal/url.js#L1267-L1289
19
+ // for node v8 support.
20
+ //
21
+ // Utility function that converts a URL object into an ordinary
22
+ // options object as expected by the http.request and https.request
23
+ // APIs.
24
+ function urlToOptions(url) {
25
+ const options = {
26
+ protocol: url.protocol,
27
+ hostname:
28
+ typeof url.hostname === 'string' && url.hostname.startsWith('[')
29
+ ? url.hostname.slice(1, -1)
30
+ : url.hostname,
31
+ hash: url.hash,
32
+ search: url.search,
33
+ pathname: url.pathname,
34
+ path: `${url.pathname || ''}${url.search || ''}`,
35
+ href: url.href,
36
+ };
37
+ if (url.port !== '') {
38
+ options.port = Number(url.port);
39
+ }
40
+ if (url.username || url.password) {
41
+ options.auth = `${url.username}:${url.password}`;
42
+ }
43
+ return options;
44
+ }
45
+
46
+ // A wrapper around `{http|https}.request()` that adds support for a connection
47
+ // timeout separate from the existing `options.timeout`.
48
+ //
49
+ // The existing `options.timeout` to `http.request()` sets `socket.setTimeout()`
50
+ // which will emit the 'timeout' event if there is an period of socket idleness
51
+ // that is this long. In practice for short-lived requests, it is a timeout on
52
+ // getting the start of response data back from the server.
53
+ //
54
+ // The new `opts.connectTimeout` is a number of milliseconds count from socket
55
+ // creation to socket 'connect'. If this time is reached a 'connectTimeout'
56
+ // event will be emitted on the request object. As with 'timeout', it is up
57
+ // to the caller to handle destroying the request. See "Usage" below.
58
+ // In pratice this allows for a shorter timeout to see if the remote server
59
+ // is handling connections in a timely manner. To be useful, a `connectTimeout`
60
+ // is typically shorter than a given `timeout`.
61
+ //
62
+ // Usage:
63
+ // const { httpRequest } = require('./http-request')
64
+ //
65
+ // var req = httpRequest(url, {
66
+ // connectTimeout: connectTimeout,
67
+ // // Any {http|https}.request options ...
68
+ // timeout: timeout
69
+ // }, function onRes(res) {
70
+ // // Handling of the response ...
71
+ // })
72
+ //
73
+ // // For both 'timeout' and 'connectTimeout', it is the responsibility
74
+ // // of the caller to abort the request to clean up.
75
+ // //
76
+ // // This `req.destroy()` has the side-effect of self-induced
77
+ // // "socket hang up" error event, so typically an 'error' event handler
78
+ // // is also required. One technique is to pass a specific error to
79
+ // // `req.destroy(...)` that can be inspected in the 'error' event handler.
80
+ // req.on('timeout', function () {
81
+ // // ...
82
+ // req.destroy(new Error('got timeout'))
83
+ // });
84
+ //
85
+ // req.on('connectTimeout', function () {
86
+ // // ...
87
+ // req.destroy(new Error('got connectTimeout'))
88
+ // });
89
+ //
90
+ // req.on('error', function (err) {
91
+ // // ...
92
+ // })
93
+ //
94
+ // req.end()
95
+ //
96
+ function httpRequest(url, opts, cb) {
97
+ // Handle call signature:
98
+ // httpRequest(url: String, opts?: Object, cb?: Function)
99
+ if (typeof url !== 'string') {
100
+ throw new TypeError(
101
+ '"url" argument is not a string, this does not support the "httpRequest(opts, cb)" call signature',
102
+ );
103
+ }
104
+ if (typeof opts === 'function') {
105
+ cb = opts;
106
+ opts = {};
107
+ }
108
+
109
+ // Pull out the 'connectTimeout' option that is handled here.
110
+ const { connectTimeout, ...otherOpts } = opts;
111
+
112
+ // While node v8.x is still supported, we need to merge options from the url
113
+ // and opts to call `http.request(opts, cb)`.
114
+ const u = new URL(url);
115
+ const mergedOpts = Object.assign(urlToOptions(u), otherOpts);
116
+
117
+ // http or https
118
+ let requestFn;
119
+ if (mergedOpts.protocol === 'http:') {
120
+ requestFn = coreHttpRequest;
121
+ } else if (mergedOpts.protocol === 'https:') {
122
+ requestFn = coreHttpsRequest;
123
+ } else {
124
+ throw new Error(`unsupported protocol: "${mergedOpts.protocol}"`);
125
+ }
126
+
127
+ const req = requestFn(mergedOpts, cb);
128
+
129
+ if (connectTimeout) {
130
+ // Handle a connection timeout with a timer starting when the request
131
+ // socket is *created* ("socket" event) and ending when the socket
132
+ // is connected.
133
+ req.on('socket', function (socket) {
134
+ // log.trace({url: url}, 'start connectTimeout')
135
+ var connectTimer = setTimeout(function onConnectTimeout() {
136
+ // log.trace({url: url}, 'connectTimeout')
137
+ req.emit('connectTimeout');
138
+ }, connectTimeout);
139
+
140
+ socket.on('connect', function () {
141
+ // log.trace({url: url}, 'socket connected, clear connectTimeout')
142
+ clearTimeout(connectTimer);
143
+ connectTimer = null;
144
+ });
145
+ socket.on('close', function () {
146
+ if (connectTimer) {
147
+ // log.trace({url: url}, 'socket close with active connectTimer, clear connectTimeout')
148
+ clearTimeout(connectTimer);
149
+ }
150
+ });
151
+ });
152
+ }
153
+
154
+ return req;
155
+ }
156
+
157
+ module.exports = {
158
+ httpRequest,
159
+ };
160
+
161
+ // ---- mainline
162
+
163
+ // This main is only intended to demonstrate usage of this lib; not to be a
164
+ // useful tool.
165
+ //
166
+ // Example:
167
+ // node http-request.js https://www.elastic.co 100 1000 > elastic.html
168
+ //
169
+ // Example: download a ~1GB file, requiring a 30ms connect time, 2s idle timeout
170
+ // node http-request.js http://mirror.uoregon.edu/ubuntu-releases/20.04/ubuntu-20.04.1-live-server-amd64.iso 30 2000 > ubuntu.iso
171
+ //
172
+ // Example: connect timeout because google port 81 drops TCP SYN packets:
173
+ // node http-request.js http://www.google.com:81/foo 1000 10000
174
+ //
175
+ // Example: use `NODE_DEBUG=*` to see internal node debugging details
176
+ // NODE_DEBUG=* node http-request.js $url 30 1000
177
+ //
178
+ // Example: quick connection, slow response
179
+ // % cat server.js
180
+ // var http = require('http');
181
+ // http.createServer(function (req, res) {
182
+ // console.log('SERVER: got request')
183
+ // setTimeout((function() {
184
+ // res.writeHead(200, {'Content-Type': 'text/plain'});
185
+ // res.write('line one\n')
186
+ // setTimeout((function() {
187
+ // res.write('line two\n')
188
+ // setTimeout((function() {
189
+ // res.write('line three\n')
190
+ // res.end()
191
+ // console.log('SERVER: responded')
192
+ // }), 500);
193
+ // }), 500);
194
+ // }), 500);
195
+ // }).listen(8080);
196
+ // % node server.js &
197
+ // % node http-request.js http://127.0.0.1:8080/ 10 1000
198
+ function main(argv) {
199
+ if (argv.length !== 5) {
200
+ process.stderr.write('http-request: error: incorrect number of args\n');
201
+ process.stderr.write(
202
+ 'usage: http-request $url $connectTimeoutMs $timeoutMs\n',
203
+ );
204
+ process.exitCode = 1;
205
+ return;
206
+ }
207
+ const url = argv[2];
208
+ const connectTimeout = Number(argv[3]);
209
+ const timeout = Number(argv[4]);
210
+
211
+ var req = httpRequest(
212
+ url,
213
+ {
214
+ timeout,
215
+ connectTimeout,
216
+ // TODO: log support
217
+ },
218
+ function onRes(res) {
219
+ res.pipe(process.stdout);
220
+ },
221
+ );
222
+
223
+ req.on('timeout', function () {
224
+ console.warn(
225
+ `http-request: response timeout (${timeout}ms): destroying request`,
226
+ );
227
+ req.destroy(new Error('got timeout event'));
228
+ process.exitCode = 28; // using cURL's errno for a timeout
229
+ });
230
+
231
+ req.on('connectTimeout', function () {
232
+ console.warn(
233
+ `http-request: connect timeout (${connectTimeout}ms): destroying request`,
234
+ );
235
+ req.destroy(new Error('got connectTimeout event'));
236
+ process.exitCode = 28; // using cURL's errno for a timeout
237
+ });
238
+
239
+ req.on('error', function (err) {
240
+ console.warn('http-request: request error:', err);
241
+ process.exitCode = 1;
242
+ });
243
+
244
+ req.end();
245
+ }
246
+
247
+ if (require.main === module) {
248
+ main(process.argv);
249
+ }