@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.
- package/LICENSE +26 -0
- package/NOTICE.md +442 -0
- package/README.md +48 -0
- package/changes.json +78 -0
- package/index.d.ts +398 -0
- package/index.js +11 -0
- package/lib/InflightEventSet.js +53 -0
- package/lib/activation-method.js +119 -0
- package/lib/agent.js +941 -0
- package/lib/apm-client/apm-client.js +313 -0
- package/lib/apm-client/http-apm-client/CHANGELOG.md +271 -0
- package/lib/apm-client/http-apm-client/README.md +485 -0
- package/lib/apm-client/http-apm-client/central-config.js +41 -0
- package/lib/apm-client/http-apm-client/container-info.js +111 -0
- package/lib/apm-client/http-apm-client/detect-hostname.js +96 -0
- package/lib/apm-client/http-apm-client/index.js +1975 -0
- package/lib/apm-client/http-apm-client/logging.js +31 -0
- package/lib/apm-client/http-apm-client/ndjson.js +20 -0
- package/lib/apm-client/http-apm-client/truncate.js +434 -0
- package/lib/apm-client/noop-apm-client.js +73 -0
- package/lib/async-hooks-polyfill.js +58 -0
- package/lib/cloud-metadata/aws.js +175 -0
- package/lib/cloud-metadata/azure.js +123 -0
- package/lib/cloud-metadata/callback-coordination.js +159 -0
- package/lib/cloud-metadata/gcp.js +133 -0
- package/lib/cloud-metadata/index.js +175 -0
- package/lib/config/config.js +458 -0
- package/lib/config/normalizers.js +701 -0
- package/lib/config/schema.js +1007 -0
- package/lib/constants.js +35 -0
- package/lib/errors.js +303 -0
- package/lib/filters/sanitize-field-names.js +69 -0
- package/lib/http-request.js +249 -0
- package/lib/instrumentation/azure-functions.js +519 -0
- package/lib/instrumentation/context.js +56 -0
- package/lib/instrumentation/dropped-spans-stats.js +112 -0
- package/lib/instrumentation/elasticsearch-shared.js +63 -0
- package/lib/instrumentation/express-utils.js +91 -0
- package/lib/instrumentation/generic-span.js +322 -0
- package/lib/instrumentation/http-shared.js +424 -0
- package/lib/instrumentation/ids.js +39 -0
- package/lib/instrumentation/index.js +1127 -0
- package/lib/instrumentation/modules/@apollo/server.js +30 -0
- package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
- package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
- package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
- package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
- package/lib/instrumentation/modules/@opentelemetry/api.js +86 -0
- package/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +79 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
- package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
- package/lib/instrumentation/modules/_lambda-handler.js +40 -0
- package/lib/instrumentation/modules/apollo-server-core.js +49 -0
- package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
- package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
- package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
- package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
- package/lib/instrumentation/modules/aws-sdk.js +76 -0
- package/lib/instrumentation/modules/bluebird.js +93 -0
- package/lib/instrumentation/modules/cassandra-driver.js +280 -0
- package/lib/instrumentation/modules/elasticsearch.js +191 -0
- package/lib/instrumentation/modules/express-graphql.js +66 -0
- package/lib/instrumentation/modules/express-queue.js +28 -0
- package/lib/instrumentation/modules/express.js +162 -0
- package/lib/instrumentation/modules/fastify.js +172 -0
- package/lib/instrumentation/modules/finalhandler.js +41 -0
- package/lib/instrumentation/modules/generic-pool.js +85 -0
- package/lib/instrumentation/modules/graphql.js +256 -0
- package/lib/instrumentation/modules/handlebars.js +22 -0
- package/lib/instrumentation/modules/http.js +112 -0
- package/lib/instrumentation/modules/http2.js +320 -0
- package/lib/instrumentation/modules/https.js +68 -0
- package/lib/instrumentation/modules/ioredis.js +94 -0
- package/lib/instrumentation/modules/jade.js +18 -0
- package/lib/instrumentation/modules/kafkajs.js +476 -0
- package/lib/instrumentation/modules/knex.js +91 -0
- package/lib/instrumentation/modules/koa-router.js +74 -0
- package/lib/instrumentation/modules/koa.js +15 -0
- package/lib/instrumentation/modules/memcached.js +99 -0
- package/lib/instrumentation/modules/mimic-response.js +45 -0
- package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
- package/lib/instrumentation/modules/mongodb-core.js +206 -0
- package/lib/instrumentation/modules/mongodb.js +259 -0
- package/lib/instrumentation/modules/mysql.js +200 -0
- package/lib/instrumentation/modules/mysql2.js +140 -0
- package/lib/instrumentation/modules/pg.js +148 -0
- package/lib/instrumentation/modules/pug.js +18 -0
- package/lib/instrumentation/modules/redis.js +176 -0
- package/lib/instrumentation/modules/restify.js +52 -0
- package/lib/instrumentation/modules/tedious.js +159 -0
- package/lib/instrumentation/modules/undici.js +270 -0
- package/lib/instrumentation/modules/ws.js +59 -0
- package/lib/instrumentation/noop-transaction.js +81 -0
- package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
- package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
- package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
- package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
- package/lib/instrumentation/run-context/RunContext.js +151 -0
- package/lib/instrumentation/run-context/index.js +23 -0
- package/lib/instrumentation/shimmer.js +123 -0
- package/lib/instrumentation/span-compression.js +239 -0
- package/lib/instrumentation/span.js +621 -0
- package/lib/instrumentation/template-shared.js +43 -0
- package/lib/instrumentation/timer.js +84 -0
- package/lib/instrumentation/transaction.js +571 -0
- package/lib/lambda.js +992 -0
- package/lib/load-source-map.js +100 -0
- package/lib/logging.js +212 -0
- package/lib/metrics/index.js +92 -0
- package/lib/metrics/platforms/generic/index.js +40 -0
- package/lib/metrics/platforms/generic/process-cpu.js +22 -0
- package/lib/metrics/platforms/generic/process-top.js +157 -0
- package/lib/metrics/platforms/generic/stats.js +34 -0
- package/lib/metrics/platforms/generic/system-cpu.js +51 -0
- package/lib/metrics/platforms/linux/index.js +19 -0
- package/lib/metrics/platforms/linux/stats.js +213 -0
- package/lib/metrics/queue.js +90 -0
- package/lib/metrics/registry.js +52 -0
- package/lib/metrics/reporter.js +119 -0
- package/lib/metrics/runtime.js +77 -0
- package/lib/middleware/connect.js +16 -0
- package/lib/opentelemetry-bridge/OTelBridgeNonRecordingSpan.js +150 -0
- package/lib/opentelemetry-bridge/OTelBridgeRunContext.js +124 -0
- package/lib/opentelemetry-bridge/OTelContextManager.js +82 -0
- package/lib/opentelemetry-bridge/OTelSpan.js +344 -0
- package/lib/opentelemetry-bridge/OTelTracer.js +201 -0
- package/lib/opentelemetry-bridge/OTelTracerProvider.js +25 -0
- package/lib/opentelemetry-bridge/README.md +244 -0
- package/lib/opentelemetry-bridge/index.js +15 -0
- package/lib/opentelemetry-bridge/oblog.js +23 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/README.md +3 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/internal/validators.js +52 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/trace/TraceState.js +109 -0
- package/lib/opentelemetry-bridge/otelutils.js +99 -0
- package/lib/opentelemetry-bridge/setup.js +76 -0
- package/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +285 -0
- package/lib/opentelemetry-metrics/index.js +50 -0
- package/lib/parsers.js +225 -0
- package/lib/propwrap.js +147 -0
- package/lib/stacktraces.js +537 -0
- package/lib/symbols.js +15 -0
- package/lib/tracecontext/index.js +118 -0
- package/lib/tracecontext/traceparent.js +185 -0
- package/lib/tracecontext/tracestate.js +388 -0
- package/lib/wildcard-matcher.js +52 -0
- package/loader.mjs +7 -0
- package/package.json +299 -0
- package/start.d.ts +8 -0
- package/start.js +29 -0
- package/types/aws-lambda.d.ts +98 -0
- 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
|
+
};
|