@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,1127 @@
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 fs = require('fs');
10
+ var path = require('path');
11
+
12
+ const { Hook: RitmHook } = require('require-in-the-middle');
13
+ const IitmHook = require('import-in-the-middle');
14
+ const semver = require('semver');
15
+
16
+ const {
17
+ CONTEXT_MANAGER_ASYNCHOOKS,
18
+ CONTEXT_MANAGER_ASYNCLOCALSTORAGE,
19
+ } = require('../constants');
20
+ var { Ids } = require('./ids');
21
+ var Transaction = require('./transaction');
22
+ var { NoopTransaction } = require('./noop-transaction');
23
+ const {
24
+ AsyncHooksRunContextManager,
25
+ AsyncLocalStorageRunContextManager,
26
+ } = require('./run-context');
27
+ const { getLambdaHandlerInfo } = require('../lambda');
28
+ const undiciInstr = require('./modules/undici');
29
+ const azureFunctionsInstr = require('./azure-functions');
30
+
31
+ const nodeSupportsAsyncLocalStorage = semver.satisfies(
32
+ process.versions.node,
33
+ '>=14.5 || ^12.19.0',
34
+ );
35
+ // Node v16.5.0 added fetch support (behind `--experimental-fetch` until
36
+ // v18.0.0) based on undici@5.0.0. We can instrument undici >=v4.7.1.
37
+ const nodeHasInstrumentableFetch = typeof global.fetch === 'function';
38
+
39
+ var MODULE_PATCHERS = [
40
+ { modPath: '@apollo/server' },
41
+ { modPath: '@smithy/smithy-client' }, // Instrument the base client which all AWS-SDK v3 clients extend.
42
+ {
43
+ modPath: '@aws-sdk/smithy-client',
44
+ patcher: './modules/@smithy/smithy-client.js',
45
+ },
46
+ { modPath: '@elastic/elasticsearch' },
47
+ {
48
+ modPath: '@elastic/elasticsearch-canary',
49
+ patcher: './modules/@elastic/elasticsearch.js',
50
+ },
51
+ { modPath: '@opentelemetry/api' },
52
+ { modPath: '@opentelemetry/sdk-metrics' },
53
+ { modPath: '@redis/client/dist/lib/client/index.js', diKey: 'redis' },
54
+ {
55
+ modPath: '@redis/client/dist/lib/client/commands-queue.js',
56
+ diKey: 'redis',
57
+ },
58
+ {
59
+ modPath: '@node-redis/client/dist/lib/client/index.js',
60
+ patcher: './modules/@redis/client/dist/lib/client/index.js',
61
+ diKey: 'redis',
62
+ },
63
+ {
64
+ modPath: '@node-redis/client/dist/lib/client/commands-queue.js',
65
+ patcher: './modules/@redis/client/dist/lib/client/commands-queue.js',
66
+ diKey: 'redis',
67
+ },
68
+ { modPath: 'apollo-server-core' },
69
+ { modPath: 'aws-sdk' },
70
+ { modPath: 'bluebird' },
71
+ { modPath: 'cassandra-driver' },
72
+ { modPath: 'elasticsearch' },
73
+ { modPath: 'express' },
74
+ { modPath: 'express-graphql' },
75
+ { modPath: 'express-queue' },
76
+ { modPath: 'fastify' },
77
+ { modPath: 'finalhandler' },
78
+ { modPath: 'generic-pool' },
79
+ { modPath: 'graphql' },
80
+ { modPath: 'handlebars' },
81
+ { modPath: '@hapi/hapi' },
82
+ { modPath: 'http' },
83
+ { modPath: 'https' },
84
+ { modPath: 'http2' },
85
+ { modPath: 'ioredis' },
86
+ { modPath: 'jade' },
87
+ { modPath: 'kafkajs' },
88
+ { modPath: 'knex' },
89
+ { modPath: 'koa' },
90
+ { modPath: 'koa-router' },
91
+ { modPath: '@koa/router', patcher: './modules/koa-router.js' },
92
+ { modPath: 'memcached' },
93
+ { modPath: 'mimic-response' },
94
+ { modPath: 'mongodb-core' },
95
+ { modPath: 'mongodb' },
96
+ {
97
+ modPath: 'mongodb/lib/cmap/connection_pool.js',
98
+ patcher: './modules/mongodb/lib/cmap/connection_pool.js',
99
+ },
100
+ { modPath: 'mysql' },
101
+ { modPath: 'mysql2' },
102
+ { modPath: 'pg' },
103
+ { modPath: 'pug' },
104
+ { modPath: 'redis' },
105
+ { modPath: 'restify' },
106
+ { modPath: 'tedious' },
107
+ { modPath: 'undici' },
108
+ { modPath: 'ws' },
109
+ ];
110
+
111
+ /**
112
+ * This is a subset of `MODULES` until ESM support for all is tested.
113
+ *
114
+ * @typedef {Object} IitmModuleInfo
115
+ * @property {boolean} [instrumentImportMod] If false, this indicates that
116
+ * the instrumentation for this module should be passed the
117
+ * `modExports.default` property instead of the `modExports`. For
118
+ * instrumentation of CommonJS modules that do not modify top-level
119
+ * exports, this generally means the instrumentation can remain unchanged.
120
+ * See the handling of the `default` property at
121
+ * https://nodejs.org/api/esm.html#commonjs-namespaces
122
+ *
123
+ * @type {Map<string, IitmModuleInfo>}
124
+ */
125
+ const IITM_MODULES = {
126
+ // This smithy-client entry isn't used for `@aws-sdk/client-*` ESM support
127
+ // because the smithy-client is transitively `require`d by CommonJS aws-sdk
128
+ // code. If a future aws-sdk v3 version switches to ESM imports internally,
129
+ // then this will be relevant.
130
+ // '@aws-sdk/smithy-client': { instrumentImportMod: false },
131
+ 'cassandra-driver': { instrumentImportMod: false },
132
+ express: { instrumentImportMod: false },
133
+ fastify: { instrumentImportMod: true },
134
+ http: { instrumentImportMod: true },
135
+ https: { instrumentImportMod: true },
136
+ ioredis: { instrumentImportMod: false },
137
+ knex: { instrumentImportMod: false },
138
+ pg: { instrumentImportMod: false },
139
+ };
140
+
141
+ /**
142
+ * modPath modName
143
+ * ------- ---------
144
+ * mongodb mongodb
145
+ * mongodb/lib/foo.js mongodb
146
+ * @elastic/elasticsearch @elastic/elasticsearch
147
+ * @redis/client/dist/lib/client.js @redis/client
148
+ * /var/task/index.js /var/task/index.js
149
+ */
150
+ function modNameFromModPath(modPath) {
151
+ if (modPath.startsWith('/')) {
152
+ return modPath;
153
+ } else if (modPath.startsWith('@')) {
154
+ return modPath.split('/', 2).join('/');
155
+ } else {
156
+ return modPath.split('/', 1)[0];
157
+ }
158
+ }
159
+
160
+ function normPathSeps(s) {
161
+ return path.sep !== '/' ? s.split(path.sep).join('/') : s;
162
+ }
163
+
164
+ /**
165
+ * Holds the registered set of "patchers" (functions that monkey patch imported
166
+ * modules) for a module path (`modPath`).
167
+ */
168
+ class PatcherRegistry {
169
+ constructor() {
170
+ this.reset();
171
+ }
172
+
173
+ reset() {
174
+ this._infoFromModPath = {};
175
+ }
176
+
177
+ /**
178
+ * Add a patcher for the given module path.
179
+ *
180
+ * @param {string} modPath - Identifies a module that RITM can hook: a
181
+ * module name (http, @smithy/client), a module-relative path
182
+ * (mongodb/lib/cmap/connection_pool.js), an absolute path
183
+ * (/var/task/index.js; Windows paths are not supported), a sub-module
184
+ * (react-dom/server).
185
+ * @param {import('../..').PatchHandler | string} patcher - A patcher function
186
+ * or a path to a CommonJS module that exports one as the default export.
187
+ * @param {string} [diKey] - An optional key in the `disableInstrumentations`
188
+ * config var that is used to determine if this patcher is
189
+ * disabled. All patchers for the same modPath must share the same `diKey`.
190
+ * This throws if a conflicting `diKey` is given.
191
+ * It defaults to the `modName` (derived from the `modPath`).
192
+ */
193
+ add(modPath, patcher, diKey = null) {
194
+ if (!(modPath in this._infoFromModPath)) {
195
+ this._infoFromModPath[modPath] = {
196
+ patchers: [patcher],
197
+ diKey: diKey || modNameFromModPath(modPath),
198
+ };
199
+ } else {
200
+ const entry = this._infoFromModPath[modPath];
201
+ // The `diKey`, if provided, must be the same for all patchers for a modPath.
202
+ if (diKey && diKey !== entry.diKey) {
203
+ throw new Error(
204
+ `invalid "diKey", ${diKey}, for module "${modPath}" patcher: it conflicts with existing diKey=${entry.diKey}`,
205
+ );
206
+ }
207
+ entry.patchers.push(patcher);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Remove the given patcher for the given module path.
213
+ */
214
+ remove(modPath, patcher) {
215
+ const entry = this._infoFromModPath[modPath];
216
+ if (!entry) {
217
+ return;
218
+ }
219
+ const idx = entry.patchers.indexOf(patcher);
220
+ if (idx !== -1) {
221
+ entry.patchers.splice(idx, 1);
222
+ }
223
+ if (entry.patchers.length === 0) {
224
+ delete this._infoFromModPath[modPath];
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Remove all patchers for the given module path.
230
+ */
231
+ clear(modPath) {
232
+ delete this._infoFromModPath[modPath];
233
+ }
234
+
235
+ has(modPath) {
236
+ return modPath in this._infoFromModPath;
237
+ }
238
+
239
+ getPatchers(modPath) {
240
+ return this._infoFromModPath[modPath]?.patchers;
241
+ }
242
+
243
+ /**
244
+ * Returns the appropriate RITM `modules` argument so that all registered
245
+ * `modPath`s will be hooked. This assumes `{internals: true}` RITM options
246
+ * are used.
247
+ *
248
+ * @returns {Array<string>}
249
+ */
250
+ ritmModulesArg() {
251
+ // RITM hooks:
252
+ // 1. `require('mongodb')` if 'mongodb' is in the modules arg;
253
+ // 2. `require('mongodb/lib/foo.js')`, a module-relative path, if 'mongodb'
254
+ // is in the modules arg and `{internals: true}` option is given;
255
+ // 3. `require('/var/task/index.js')` if the exact resolved absolute path
256
+ // is in the modules arg; and
257
+ // 4. `require('react-dom/server')`, a "sub-module", if 'react-dom/server'
258
+ // is in the modules arg.
259
+ //
260
+ // The wrinkle is that the modPath "mongodb/lib/foo.js" need not be in the
261
+ // `modules` argument to RITM, but the similar-looking "react-dom/server"
262
+ // must be.
263
+ const modules = new Set();
264
+ const hasModExt = /\.(js|cjs|mjs|json)$/;
265
+ Object.keys(this._infoFromModPath).forEach((modPath) => {
266
+ const modName = modNameFromModPath(modPath);
267
+ if (modPath === modName) {
268
+ modules.add(modPath);
269
+ } else {
270
+ if (hasModExt.test(modPath)) {
271
+ modules.add(modName); // case 2
272
+ } else {
273
+ // Beware the RITM bug: passing both 'foo' and 'foo/subpath' results
274
+ // in 'foo/subpath' not being hooked.
275
+ // TODO: link to issue for this
276
+ modules.add(modPath); // case 4
277
+ }
278
+ }
279
+ });
280
+
281
+ return Array.from(modules);
282
+ }
283
+
284
+ /**
285
+ * Get the string on the `disableInstrumentations` config var that indicates
286
+ * if this module path should be disabled.
287
+ *
288
+ * Typically this is the module name -- e.g. "@redis/client" -- but might be
289
+ * a custom value -- e.g. "lambda" for a Lambda handler path.
290
+ *
291
+ * @returns {string | undefined}
292
+ */
293
+ diKey(modPath) {
294
+ return this._infoFromModPath[modPath]?.diKey;
295
+ }
296
+ }
297
+
298
+ function Instrumentation(agent) {
299
+ this._agent = agent;
300
+ this._disableInstrumentationsSet = null;
301
+ this._ritmHook = null;
302
+ this._iitmHook = null;
303
+ this._started = false;
304
+ this._runCtxMgr = null;
305
+ this._log = agent.logger;
306
+ this._patcherReg = new PatcherRegistry();
307
+ this._cachedVerFromModBaseDir = new Map();
308
+ }
309
+
310
+ Instrumentation.prototype.currTransaction = function () {
311
+ if (!this._started) {
312
+ return null;
313
+ }
314
+ return this._runCtxMgr.active().currTransaction();
315
+ };
316
+
317
+ Instrumentation.prototype.currSpan = function () {
318
+ if (!this._started) {
319
+ return null;
320
+ }
321
+ return this._runCtxMgr.active().currSpan();
322
+ };
323
+
324
+ Instrumentation.prototype.ids = function () {
325
+ if (!this._started) {
326
+ return new Ids();
327
+ }
328
+ const runContext = this._runCtxMgr.active();
329
+ const currSpanOrTrans = runContext.currSpan() || runContext.currTransaction();
330
+ if (currSpanOrTrans) {
331
+ return currSpanOrTrans.ids;
332
+ }
333
+ return new Ids();
334
+ };
335
+
336
+ Instrumentation.prototype.addPatch = function (modules, handler) {
337
+ if (!Array.isArray(modules)) {
338
+ modules = [modules];
339
+ }
340
+ for (const modPath of modules) {
341
+ const type = typeof handler;
342
+ if (type !== 'function' && type !== 'string') {
343
+ this._agent.logger.error('Invalid patch handler type: %s', type);
344
+ return;
345
+ }
346
+ this._patcherReg.add(modPath, handler);
347
+ }
348
+ this._restartHooks();
349
+ };
350
+
351
+ Instrumentation.prototype.removePatch = function (modules, handler) {
352
+ if (!Array.isArray(modules)) modules = [modules];
353
+
354
+ for (const modPath of modules) {
355
+ this._patcherReg.remove(modPath, handler);
356
+ }
357
+
358
+ this._restartHooks();
359
+ };
360
+
361
+ Instrumentation.prototype.clearPatches = function (modules) {
362
+ if (!Array.isArray(modules)) modules = [modules];
363
+
364
+ for (const modPath of modules) {
365
+ this._patcherReg.clear(modPath);
366
+ }
367
+
368
+ this._restartHooks();
369
+ };
370
+
371
+ // If in a Lambda environment, find its handler and add a patcher for it.
372
+ Instrumentation.prototype._maybeLoadLambdaPatcher = function () {
373
+ let lambdaHandlerInfo = getLambdaHandlerInfo(process.env, this._log);
374
+
375
+ if (lambdaHandlerInfo && this._patcherReg.has(lambdaHandlerInfo.modName)) {
376
+ this._log.warn(
377
+ 'Unable to instrument Lambda handler "%s" due to name conflict with "%s", please choose a different Lambda handler name',
378
+ process.env._HANDLER,
379
+ lambdaHandlerInfo.modName,
380
+ );
381
+ lambdaHandlerInfo = null;
382
+ }
383
+
384
+ if (lambdaHandlerInfo) {
385
+ const { createLambdaPatcher } = require('./modules/_lambda-handler');
386
+ this._lambdaHandlerInfo = lambdaHandlerInfo;
387
+ this._patcherReg.add(
388
+ this._lambdaHandlerInfo.filePath,
389
+ createLambdaPatcher(lambdaHandlerInfo.propPath),
390
+ 'lambda', // diKey
391
+ );
392
+ }
393
+ };
394
+
395
+ // Start the instrumentation system.
396
+ //
397
+ // @param {RunContext} [runContextClass] - A class to use for the core object
398
+ // that is used to track run context. It defaults to `RunContext`. If given,
399
+ // it must be `RunContext` (the typical case) or a subclass of it. The OTel
400
+ // Bridge uses this to provide a subclass that bridges to OpenTelemetry
401
+ // `Context` usage.
402
+ Instrumentation.prototype.start = function (runContextClass) {
403
+ if (this._started) return;
404
+ this._started = true;
405
+
406
+ // Could have changed in Agent.start().
407
+ this._log = this._agent.logger;
408
+
409
+ // Select the appropriate run-context manager.
410
+ const confContextManager = this._agent._conf.contextManager;
411
+ if (confContextManager === CONTEXT_MANAGER_ASYNCHOOKS) {
412
+ this._runCtxMgr = new AsyncHooksRunContextManager(
413
+ this._log,
414
+ runContextClass,
415
+ );
416
+ } else if (nodeSupportsAsyncLocalStorage) {
417
+ this._runCtxMgr = new AsyncLocalStorageRunContextManager(
418
+ this._log,
419
+ runContextClass,
420
+ );
421
+ } else {
422
+ if (confContextManager === CONTEXT_MANAGER_ASYNCLOCALSTORAGE) {
423
+ this._log.warn(
424
+ `config includes 'contextManager="${confContextManager}"', but node ${process.version} does not support AsyncLocalStorage for run-context management: falling back to using async_hooks`,
425
+ );
426
+ }
427
+ this._runCtxMgr = new AsyncHooksRunContextManager(
428
+ this._log,
429
+ runContextClass,
430
+ );
431
+ }
432
+
433
+ // Load module patchers: from MODULE_PATCHERS, for Lambda, and from
434
+ // config.addPatch.
435
+ for (let info of MODULE_PATCHERS) {
436
+ let patcher;
437
+ if (info.patcher) {
438
+ patcher = path.resolve(__dirname, info.patcher);
439
+ } else {
440
+ // Typically the patcher module for the APM agent's included
441
+ // instrumentations is "./modules/${modPath}[.js]".
442
+ patcher = path.resolve(
443
+ __dirname,
444
+ 'modules',
445
+ info.modPath + (info.modPath.endsWith('.js') ? '' : '.js'),
446
+ );
447
+ }
448
+
449
+ this._patcherReg.add(info.modPath, patcher, info.diKey);
450
+ }
451
+
452
+ this._maybeLoadLambdaPatcher();
453
+
454
+ const patches = this._agent._conf.addPatch;
455
+ if (Array.isArray(patches)) {
456
+ for (const [modPath, patcher] of patches) {
457
+ this._patcherReg.add(modPath, patcher);
458
+ }
459
+ }
460
+
461
+ this._runCtxMgr.enable();
462
+ this._restartHooks();
463
+
464
+ if (nodeHasInstrumentableFetch && this._isModuleEnabled('undici')) {
465
+ this._log.debug('instrumenting fetch');
466
+ undiciInstr.instrumentUndici(this._agent);
467
+ }
468
+
469
+ if (azureFunctionsInstr.isAzureFunctionsEnvironment) {
470
+ this._log.debug('instrumenting azure-functions');
471
+ azureFunctionsInstr.instrument(this._agent);
472
+ }
473
+ };
474
+
475
+ // Stop active instrumentation and reset global state *as much as possible*.
476
+ //
477
+ // Limitations: Removing and re-applying 'require-in-the-middle'-based patches
478
+ // has no way to update existing references to patched or unpatched exports from
479
+ // those modules.
480
+ Instrumentation.prototype.stop = function () {
481
+ this._started = false;
482
+
483
+ // Reset run context tracking.
484
+ if (this._runCtxMgr) {
485
+ this._runCtxMgr.disable();
486
+ this._runCtxMgr = null;
487
+ }
488
+
489
+ // Reset patching.
490
+ if (this._ritmHook) {
491
+ this._ritmHook.unhook();
492
+ this._ritmHook = null;
493
+ }
494
+ if (this._iitmHook) {
495
+ this._iitmHook.unhook();
496
+ this._iitmHook = null;
497
+ }
498
+ this._patcherReg.reset();
499
+ this._lambdaHandlerInfo = null;
500
+ if (nodeHasInstrumentableFetch) {
501
+ undiciInstr.uninstrumentUndici();
502
+ }
503
+ if (azureFunctionsInstr.isAzureFunctionsEnvironment) {
504
+ azureFunctionsInstr.uninstrument();
505
+ }
506
+ };
507
+
508
+ // Reset internal state for (relatively) clean re-use of this Instrumentation.
509
+ // Used for testing, while `resetAgent()` + "test/_agent.js" usage still exists.
510
+ //
511
+ // This does *not* include redoing monkey patching. It resets context tracking,
512
+ // so a subsequent test case can re-use the Instrumentation in the same process.
513
+ Instrumentation.prototype.testReset = function () {
514
+ if (this._runCtxMgr) {
515
+ this._runCtxMgr.testReset();
516
+ }
517
+ };
518
+
519
+ Instrumentation.prototype._isModuleEnabled = function (modName) {
520
+ if (!this._disableInstrumentationsSet) {
521
+ this._disableInstrumentationsSet = new Set(
522
+ this._agent._conf.disableInstrumentations,
523
+ );
524
+ }
525
+ return (
526
+ this._agent._conf.instrument &&
527
+ !this._disableInstrumentationsSet.has(modName)
528
+ );
529
+ };
530
+
531
+ Instrumentation.prototype._restartHooks = function () {
532
+ if (!this._started) {
533
+ return;
534
+ }
535
+ if (this._ritmHook || this._iitmHook) {
536
+ this._agent.logger.debug('removing hooks to Node.js module loader');
537
+ if (this._ritmHook) {
538
+ this._ritmHook.unhook();
539
+ }
540
+ if (this._iitmHook) {
541
+ this._iitmHook.unhook();
542
+ }
543
+ }
544
+
545
+ var self = this;
546
+
547
+ this._log.debug('adding Node.js module loader hooks');
548
+
549
+ this._ritmHook = new RitmHook(
550
+ this._patcherReg.ritmModulesArg(),
551
+ { internals: true },
552
+ function (exports, modPath, basedir) {
553
+ let version = undefined;
554
+
555
+ // An *absolute path* given to RITM results in the file *basename* being
556
+ // used as `modPath` in this callback. We need the absolute path back to
557
+ // look up the patcher in our registry. We know the only absolute path
558
+ // we use is for our Lambda handler.
559
+ if (self._lambdaHandlerInfo?.modName === modPath) {
560
+ modPath = self._lambdaHandlerInfo.filePath;
561
+ version = process.env.AWS_LAMBDA_FUNCTION_VERSION || '';
562
+ } else {
563
+ // RITM returns `modPath` using native path separators. However,
564
+ // _patcherReg is keyed with '/' separators, so we need to normalize.
565
+ modPath = normPathSeps(modPath);
566
+ }
567
+
568
+ if (!self._patcherReg.has(modPath)) {
569
+ // Skip out if there are no patchers for this hooked module name.
570
+ return exports;
571
+ }
572
+
573
+ // Find an appropriate version for this modPath.
574
+ if (version !== undefined) {
575
+ // Lambda version already handled above.
576
+ } else if (!basedir) {
577
+ // This is a core module.
578
+ version = process.versions.node;
579
+ } else {
580
+ // This is a module (e.g. 'mongodb') or a module internal path
581
+ // ('mongodb/lib/cmap/connection_pool.js').
582
+ version = self._getPackageVersion(modPath, basedir);
583
+ if (version === undefined) {
584
+ self._log.debug('could not patch %s module', modPath);
585
+ return exports;
586
+ }
587
+ }
588
+
589
+ const diKey = self._patcherReg.diKey(modPath);
590
+ const enabled = self._isModuleEnabled(diKey);
591
+ return self._patchModule(exports, modPath, version, enabled, false);
592
+ },
593
+ );
594
+
595
+ this._iitmHook = IitmHook(
596
+ // TODO: Eventually derive this from `_patcherRegistry`.
597
+ Object.keys(IITM_MODULES),
598
+ function (modExports, modName, modBaseDir) {
599
+ const enabled = self._isModuleEnabled(modName);
600
+ const version = modBaseDir
601
+ ? self._getPackageVersion(modName, modBaseDir)
602
+ : process.versions.node;
603
+ if (IITM_MODULES[modName].instrumentImportMod) {
604
+ return self._patchModule(modExports, modName, version, enabled, true);
605
+ } else {
606
+ modExports.default = self._patchModule(
607
+ modExports.default,
608
+ modName,
609
+ version,
610
+ enabled,
611
+ false,
612
+ );
613
+ return modExports;
614
+ }
615
+ },
616
+ );
617
+ };
618
+
619
+ Instrumentation.prototype._getPackageVersion = function (modName, modBaseDir) {
620
+ if (this._cachedVerFromModBaseDir.has(modBaseDir)) {
621
+ return this._cachedVerFromModBaseDir.get(modBaseDir);
622
+ }
623
+
624
+ let ver = undefined;
625
+ try {
626
+ const version = JSON.parse(
627
+ fs.readFileSync(path.join(modBaseDir, 'package.json')),
628
+ ).version;
629
+ if (typeof version === 'string') {
630
+ ver = version;
631
+ }
632
+ } catch (err) {
633
+ this._agent.logger.debug(
634
+ { modName, modBaseDir, err },
635
+ 'could not load package version',
636
+ );
637
+ }
638
+
639
+ this._cachedVerFromModBaseDir.set(modBaseDir, ver);
640
+ return ver;
641
+ };
642
+
643
+ /**
644
+ * Patch/instrument the given module.
645
+ *
646
+ * @param {Module | any} modExports The object made available by the RITM or
647
+ * IITM hook. For a `require` this is the `module.exports` value, which can
648
+ * by any type. For an `import` this is a `Module` object if
649
+ * `isImportMod=true`, or the default export (the equivalent of
650
+ * `module.exports`) if `isImportMod=false`.
651
+ * @param {string} modPath
652
+ * @param {string} version
653
+ * @param {boolean} enabled Whether instrumentation is enabled for this module
654
+ * depending on the `disableInstrumentations` config value. (Currently the
655
+ * http, https, and http2 instrumentations, at least, do *some* work even if
656
+ * enabled=false.)
657
+ * @param {boolean} isImportMod When false, the `modExports` param is the
658
+ * `module.exports` object (typically from a `require`). When true,
659
+ * `modExports` is the `Module` instance from an `import`. This depends on
660
+ * the `instrumentImportMod` flag that is set per module.
661
+ */
662
+ Instrumentation.prototype._patchModule = function (
663
+ modExports,
664
+ modPath,
665
+ version,
666
+ enabled,
667
+ isImportMod,
668
+ ) {
669
+ this._log.debug(
670
+ 'instrumenting %s@%s module (enabled=%s, isImportMod=%s)',
671
+ modPath,
672
+ version,
673
+ enabled,
674
+ isImportMod,
675
+ );
676
+ const patchers = this._patcherReg.getPatchers(modPath);
677
+ if (patchers) {
678
+ for (let patcher of patchers) {
679
+ if (typeof patcher === 'string') {
680
+ if (patcher[0] === '.') {
681
+ patcher = path.resolve(process.cwd(), patcher);
682
+ }
683
+ patcher = require(patcher);
684
+ }
685
+
686
+ const type = typeof patcher;
687
+ if (type !== 'function') {
688
+ this._agent.logger.error(
689
+ 'Invalid patch handler type "%s" for module "%s"',
690
+ type,
691
+ modPath,
692
+ );
693
+ continue;
694
+ }
695
+
696
+ modExports = patcher(modExports, this._agent, {
697
+ name: modPath,
698
+ version,
699
+ enabled,
700
+ isImportMod,
701
+ });
702
+ }
703
+ }
704
+ return modExports;
705
+ };
706
+
707
+ Instrumentation.prototype.addEndedTransaction = function (transaction) {
708
+ var agent = this._agent;
709
+
710
+ if (!this._started) {
711
+ agent.logger.debug('ignoring transaction %o', {
712
+ trans: transaction.id,
713
+ trace: transaction.traceId,
714
+ });
715
+ return;
716
+ }
717
+
718
+ const rc = this._runCtxMgr.active();
719
+ if (rc.currTransaction() === transaction) {
720
+ // Replace the active run context with an empty one. I.e. there is now
721
+ // no active transaction or span (at least in this async task).
722
+ this._runCtxMgr.supersedeRunContext(this._runCtxMgr.root());
723
+ this._log.debug(
724
+ { ctxmgr: this._runCtxMgr.toString() },
725
+ 'addEndedTransaction(%s)',
726
+ transaction.name,
727
+ );
728
+ }
729
+
730
+ // Avoid transaction filtering time if only propagating trace-context.
731
+ if (agent._conf.contextPropagationOnly) {
732
+ // This one log.trace related to contextPropagationOnly is included as a
733
+ // possible log hint to future debugging for why events are not being sent
734
+ // to APM server.
735
+ agent.logger.trace('contextPropagationOnly: skip sendTransaction');
736
+ return;
737
+ }
738
+
739
+ // https://github.com/elastic/apm/blob/main/specs/agents/tracing-sampling.md#non-sampled-transactions
740
+ if (
741
+ !transaction.sampled &&
742
+ !agent._apmClient.supportsKeepingUnsampledTransaction()
743
+ ) {
744
+ agent.logger.debug(
745
+ { trans: transaction.id, trace: transaction.traceId },
746
+ 'dropping unsampled transaction',
747
+ );
748
+ return;
749
+ }
750
+
751
+ // if I have ended and I have something buffered, send that buffered thing
752
+ if (transaction.getBufferedSpan()) {
753
+ this._encodeAndSendSpan(transaction.getBufferedSpan());
754
+ }
755
+
756
+ var payload = agent._transactionFilters.process(transaction._encode());
757
+ if (!payload) {
758
+ agent.logger.debug('transaction ignored by filter %o', {
759
+ trans: transaction.id,
760
+ trace: transaction.traceId,
761
+ });
762
+ return;
763
+ }
764
+
765
+ agent.logger.debug('sending transaction %o', {
766
+ trans: transaction.id,
767
+ trace: transaction.traceId,
768
+ });
769
+ agent._apmClient.sendTransaction(payload);
770
+ };
771
+
772
+ Instrumentation.prototype.addEndedSpan = function (span) {
773
+ var agent = this._agent;
774
+
775
+ if (!this._started) {
776
+ agent.logger.debug('ignoring span %o', {
777
+ span: span.id,
778
+ parent: span.parentId,
779
+ trace: span.traceId,
780
+ name: span.name,
781
+ type: span.type,
782
+ });
783
+ return;
784
+ }
785
+
786
+ // Replace the active run context with this span removed. Typically this
787
+ // span is the top of stack (i.e. is the current span). However, it is
788
+ // possible to have out-of-order span.end(), in which case the ended span
789
+ // might not.
790
+ const newRc = this._runCtxMgr.active().leaveSpan(span);
791
+ if (newRc) {
792
+ this._runCtxMgr.supersedeRunContext(newRc);
793
+ }
794
+ this._log.debug(
795
+ { ctxmgr: this._runCtxMgr.toString() },
796
+ 'addEndedSpan(%s)',
797
+ span.name,
798
+ );
799
+
800
+ // Avoid span encoding time if only propagating trace-context.
801
+ if (agent._conf.contextPropagationOnly) {
802
+ return;
803
+ }
804
+
805
+ if (!span.isRecorded()) {
806
+ span.transaction.captureDroppedSpan(span);
807
+ return;
808
+ }
809
+
810
+ if (!this._agent._conf.spanCompressionEnabled) {
811
+ this._encodeAndSendSpan(span);
812
+ } else {
813
+ // if I have ended and I have something buffered, send that buffered thing
814
+ if (span.getBufferedSpan()) {
815
+ this._encodeAndSendSpan(span.getBufferedSpan());
816
+ span.setBufferedSpan(null);
817
+ }
818
+
819
+ const parentSpan = span.getParentSpan();
820
+ if ((parentSpan && parentSpan.ended) || !span.isCompressionEligible()) {
821
+ const buffered = parentSpan && parentSpan.getBufferedSpan();
822
+ if (buffered) {
823
+ this._encodeAndSendSpan(buffered);
824
+ parentSpan.setBufferedSpan(null);
825
+ }
826
+ this._encodeAndSendSpan(span);
827
+ } else if (!parentSpan.getBufferedSpan()) {
828
+ // span is compressible and there's nothing buffered
829
+ // add to buffer, move on
830
+ parentSpan.setBufferedSpan(span);
831
+ } else if (!parentSpan.getBufferedSpan().tryToCompress(span)) {
832
+ // we could not compress span so SEND bufferend span
833
+ // and buffer the span we could not compress
834
+ this._encodeAndSendSpan(parentSpan.getBufferedSpan());
835
+ parentSpan.setBufferedSpan(span);
836
+ }
837
+ }
838
+ };
839
+
840
+ Instrumentation.prototype._encodeAndSendSpan = function (span) {
841
+ const duration = span.isComposite()
842
+ ? span.getCompositeSum()
843
+ : span.duration();
844
+ if (
845
+ span.discardable &&
846
+ duration / 1000 < this._agent._conf.exitSpanMinDuration
847
+ ) {
848
+ span.transaction.captureDroppedSpan(span);
849
+ return;
850
+ }
851
+
852
+ const agent = this._agent;
853
+ // Note this error as an "inflight" event. See Agent#flush().
854
+ const inflightEvents = agent._inflightEvents;
855
+ inflightEvents.add(span.id);
856
+
857
+ agent.logger.debug('encoding span %o', {
858
+ span: span.id,
859
+ parent: span.parentId,
860
+ trace: span.traceId,
861
+ name: span.name,
862
+ type: span.type,
863
+ });
864
+ span._encode(function (err, payload) {
865
+ if (err) {
866
+ agent.logger.error('error encoding span %o', {
867
+ span: span.id,
868
+ parent: span.parentId,
869
+ trace: span.traceId,
870
+ name: span.name,
871
+ type: span.type,
872
+ error: err.message,
873
+ });
874
+ } else {
875
+ payload = agent._spanFilters.process(payload);
876
+ if (!payload) {
877
+ agent.logger.debug('span ignored by filter %o', {
878
+ span: span.id,
879
+ parent: span.parentId,
880
+ trace: span.traceId,
881
+ name: span.name,
882
+ type: span.type,
883
+ });
884
+ } else {
885
+ agent.logger.debug('sending span %o', {
886
+ span: span.id,
887
+ parent: span.parentId,
888
+ trace: span.traceId,
889
+ name: span.name,
890
+ type: span.type,
891
+ });
892
+ if (agent._apmClient) {
893
+ agent._apmClient.sendSpan(payload);
894
+ }
895
+ }
896
+ }
897
+ inflightEvents.delete(span.id);
898
+ });
899
+ };
900
+
901
+ // Replace the current run context with one where the given transaction is
902
+ // current.
903
+ Instrumentation.prototype.supersedeWithTransRunContext = function (trans) {
904
+ if (this._started) {
905
+ const rc = this._runCtxMgr.root().enterTrans(trans);
906
+ this._runCtxMgr.supersedeRunContext(rc);
907
+ this._log.debug(
908
+ { ctxmgr: this._runCtxMgr.toString() },
909
+ 'supersedeWithTransRunContext(<Trans %s>)',
910
+ trans.id,
911
+ );
912
+ }
913
+ };
914
+
915
+ // Replace the current run context with one where the given span is current.
916
+ Instrumentation.prototype.supersedeWithSpanRunContext = function (span) {
917
+ if (this._started) {
918
+ const rc = this._runCtxMgr.active().enterSpan(span);
919
+ this._runCtxMgr.supersedeRunContext(rc);
920
+ this._log.debug(
921
+ { ctxmgr: this._runCtxMgr.toString() },
922
+ 'supersedeWithSpanRunContext(<Span %s>)',
923
+ span.id,
924
+ );
925
+ }
926
+ };
927
+
928
+ // Set the current run context to have *no* transaction. No spans will be
929
+ // created in this run context until a subsequent `startTransaction()`.
930
+ Instrumentation.prototype.supersedeWithEmptyRunContext = function () {
931
+ if (this._started) {
932
+ this._runCtxMgr.supersedeRunContext(this._runCtxMgr.root());
933
+ this._log.debug(
934
+ { ctxmgr: this._runCtxMgr.toString() },
935
+ 'supersedeWithEmptyRunContext()',
936
+ );
937
+ }
938
+ };
939
+
940
+ // Create a new transaction, but do *not* replace the current run context to
941
+ // make this the "current" transaction. Compare to `startTransaction`.
942
+ Instrumentation.prototype.createTransaction = function (name, ...args) {
943
+ return new Transaction(this._agent, name, ...args);
944
+ };
945
+
946
+ Instrumentation.prototype.startTransaction = function (name, ...args) {
947
+ if (!this._agent.isStarted()) {
948
+ return new NoopTransaction();
949
+ }
950
+ const trans = new Transaction(this._agent, name, ...args);
951
+ this.supersedeWithTransRunContext(trans);
952
+ return trans;
953
+ };
954
+
955
+ Instrumentation.prototype.endTransaction = function (result, endTime) {
956
+ const trans = this.currTransaction();
957
+ if (!trans) {
958
+ this._agent.logger.debug(
959
+ 'cannot end transaction - no active transaction found',
960
+ );
961
+ return;
962
+ }
963
+ trans.end(result, endTime);
964
+ };
965
+
966
+ Instrumentation.prototype.setDefaultTransactionName = function (name) {
967
+ const trans = this.currTransaction();
968
+ if (!trans) {
969
+ this._agent.logger.debug(
970
+ 'no active transaction found - cannot set default transaction name',
971
+ );
972
+ return;
973
+ }
974
+ trans.setDefaultName(name);
975
+ };
976
+
977
+ Instrumentation.prototype.setTransactionName = function (name) {
978
+ const trans = this.currTransaction();
979
+ if (!trans) {
980
+ this._agent.logger.debug(
981
+ 'no active transaction found - cannot set transaction name',
982
+ );
983
+ return;
984
+ }
985
+ trans.name = name;
986
+ };
987
+
988
+ Instrumentation.prototype.setTransactionOutcome = function (outcome) {
989
+ const trans = this.currTransaction();
990
+ if (!trans) {
991
+ this._agent.logger.debug(
992
+ 'no active transaction found - cannot set transaction outcome',
993
+ );
994
+ return;
995
+ }
996
+ trans.setOutcome(outcome);
997
+ };
998
+
999
+ // Create a new span in the current transaction, if any, and make it the
1000
+ // current span. The started span is returned. This will return null if a span
1001
+ // could not be created -- which could happen for a number of reasons.
1002
+ Instrumentation.prototype.startSpan = function (
1003
+ name,
1004
+ type,
1005
+ subtype,
1006
+ action,
1007
+ opts,
1008
+ ) {
1009
+ const trans = this.currTransaction();
1010
+ if (!trans) {
1011
+ this._agent.logger.debug(
1012
+ 'no active transaction found - cannot build new span',
1013
+ );
1014
+ return null;
1015
+ }
1016
+ return trans.startSpan.apply(trans, arguments);
1017
+ };
1018
+
1019
+ // Create a new span in the current transaction, if any. The created span is
1020
+ // returned, or null if the span could not be created.
1021
+ //
1022
+ // This does *not* replace the current run context to make this span the
1023
+ // "current" one. This allows instrumentations to avoid impacting the run
1024
+ // context of the calling code. Compare to `startSpan`.
1025
+ Instrumentation.prototype.createSpan = function (
1026
+ name,
1027
+ type,
1028
+ subtype,
1029
+ action,
1030
+ opts,
1031
+ ) {
1032
+ const trans = this.currTransaction();
1033
+ if (!trans) {
1034
+ this._agent.logger.debug(
1035
+ 'no active transaction found - cannot build new span',
1036
+ );
1037
+ return null;
1038
+ }
1039
+ return trans.createSpan.apply(trans, arguments);
1040
+ };
1041
+
1042
+ Instrumentation.prototype.setSpanOutcome = function (outcome) {
1043
+ const span = this.currSpan();
1044
+ if (!span) {
1045
+ this._agent.logger.debug('no active span found - cannot set span outcome');
1046
+ return null;
1047
+ }
1048
+ span.setOutcome(outcome);
1049
+ };
1050
+
1051
+ Instrumentation.prototype.currRunContext = function () {
1052
+ if (!this._started) {
1053
+ return null;
1054
+ }
1055
+ return this._runCtxMgr.active();
1056
+ };
1057
+
1058
+ // Bind the given function to the current run context.
1059
+ Instrumentation.prototype.bindFunction = function (fn) {
1060
+ if (!this._started) {
1061
+ return fn;
1062
+ }
1063
+ return this._runCtxMgr.bindFn(this._runCtxMgr.active(), fn);
1064
+ };
1065
+
1066
+ // Bind the given function to a given run context.
1067
+ Instrumentation.prototype.bindFunctionToRunContext = function (runContext, fn) {
1068
+ if (!this._started) {
1069
+ return fn;
1070
+ }
1071
+ return this._runCtxMgr.bindFn(runContext, fn);
1072
+ };
1073
+
1074
+ // Bind the given function to an *empty* run context.
1075
+ // This can be used to ensure `fn` does *not* run in the context of the current
1076
+ // transaction or span.
1077
+ Instrumentation.prototype.bindFunctionToEmptyRunContext = function (fn) {
1078
+ if (!this._started) {
1079
+ return fn;
1080
+ }
1081
+ return this._runCtxMgr.bindFn(this._runCtxMgr.root(), fn);
1082
+ };
1083
+
1084
+ // Bind the given EventEmitter to the current run context.
1085
+ //
1086
+ // This wraps the emitter so that any added event handler function is bound
1087
+ // as if `bindFunction` had been called on it. Note that `ee` need not
1088
+ // inherit from EventEmitter -- it uses duck typing.
1089
+ Instrumentation.prototype.bindEmitter = function (ee) {
1090
+ if (!this._started) {
1091
+ return ee;
1092
+ }
1093
+ return this._runCtxMgr.bindEE(this._runCtxMgr.active(), ee);
1094
+ };
1095
+
1096
+ // Bind the given EventEmitter to a given run context.
1097
+ Instrumentation.prototype.bindEmitterToRunContext = function (runContext, ee) {
1098
+ if (!this._started) {
1099
+ return ee;
1100
+ }
1101
+ return this._runCtxMgr.bindEE(runContext, ee);
1102
+ };
1103
+
1104
+ // Return true iff the given EventEmitter is bound to a run context.
1105
+ Instrumentation.prototype.isEventEmitterBound = function (ee) {
1106
+ if (!this._started) {
1107
+ return false;
1108
+ }
1109
+ return this._runCtxMgr.isEEBound(ee);
1110
+ };
1111
+
1112
+ // Invoke the given function in the context of `runContext`.
1113
+ Instrumentation.prototype.withRunContext = function (
1114
+ runContext,
1115
+ fn,
1116
+ thisArg,
1117
+ ...args
1118
+ ) {
1119
+ if (!this._started) {
1120
+ return fn.call(thisArg, ...args);
1121
+ }
1122
+ return this._runCtxMgr.with(runContext, fn, thisArg, ...args);
1123
+ };
1124
+
1125
+ module.exports = {
1126
+ Instrumentation,
1127
+ };