@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,221 @@
|
|
|
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 semver = require('semver');
|
|
10
|
+
|
|
11
|
+
var shimmer = require('../../shimmer');
|
|
12
|
+
|
|
13
|
+
var onPreAuthSym = Symbol('ElasticAPMOnPreAuth');
|
|
14
|
+
|
|
15
|
+
// Collect simple data a Hapi `event.data` object, typically from a Hapi
|
|
16
|
+
// 'log' or 'request' server event (https://hapi.dev/api/#server.events). This
|
|
17
|
+
// limits to including simple property values (bool, string, number, Date) to
|
|
18
|
+
// limit the possibility of accidentally capturing huge data in `captureError`
|
|
19
|
+
// below.
|
|
20
|
+
//
|
|
21
|
+
// This implementation is based on lib/errors.js#attributesFromErr.
|
|
22
|
+
function simpleDataFromEventData(agent, eventData) {
|
|
23
|
+
try {
|
|
24
|
+
let simpleRepr = simpleReprFromVal(eventData);
|
|
25
|
+
if (simpleRepr !== undefined) {
|
|
26
|
+
return simpleRepr;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let n = 0;
|
|
30
|
+
const attrs = {};
|
|
31
|
+
const keys = Object.keys(eventData);
|
|
32
|
+
for (let i = 0; i < keys.length; i++) {
|
|
33
|
+
const key = keys[i];
|
|
34
|
+
let val = eventData[key];
|
|
35
|
+
simpleRepr = simpleReprFromVal(val);
|
|
36
|
+
if (simpleRepr) {
|
|
37
|
+
attrs[key] = simpleRepr;
|
|
38
|
+
n++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return n ? attrs : undefined;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
agent.logger.trace(
|
|
44
|
+
'hapi: could not gather simple attrs from event data: ' + err.message,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If `val` is a "simple" type (bool, string, number, Date), then return a
|
|
50
|
+
// reasonable value to represent it in a JSON serialization. Otherwise, return
|
|
51
|
+
// undefined.
|
|
52
|
+
function simpleReprFromVal(val) {
|
|
53
|
+
switch (typeof val) {
|
|
54
|
+
case 'boolean':
|
|
55
|
+
case 'string':
|
|
56
|
+
case 'number':
|
|
57
|
+
break;
|
|
58
|
+
case 'object':
|
|
59
|
+
// Ignore all objects except Dates.
|
|
60
|
+
if (
|
|
61
|
+
val === null ||
|
|
62
|
+
typeof val.toISOString !== 'function' ||
|
|
63
|
+
typeof val.getTime !== 'function'
|
|
64
|
+
) {
|
|
65
|
+
return;
|
|
66
|
+
} else if (Number.isNaN(val.getTime())) {
|
|
67
|
+
val = 'Invalid Date'; // calling toISOString() on invalid dates throws
|
|
68
|
+
} else {
|
|
69
|
+
val = val.toISOString();
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
return val;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = function (hapi, agent, { version, enabled }) {
|
|
79
|
+
if (!enabled) {
|
|
80
|
+
return hapi;
|
|
81
|
+
}
|
|
82
|
+
if (!semver.satisfies(version, '>=17.9.0 <22.0.0')) {
|
|
83
|
+
agent.logger.debug('@hapi/hapi@%s not supported, skipping', version);
|
|
84
|
+
return hapi;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
agent.setFramework({ name: 'hapi', version, overwrite: false });
|
|
88
|
+
|
|
89
|
+
agent.logger.debug('shimming hapi.Server, hapi.server');
|
|
90
|
+
shimmer.massWrap(hapi, ['Server', 'server'], function (orig) {
|
|
91
|
+
return function (options) {
|
|
92
|
+
var res = orig.apply(this, arguments);
|
|
93
|
+
patchServer(res);
|
|
94
|
+
return res;
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
function patchServer(server) {
|
|
99
|
+
// Hooks that are always allowed
|
|
100
|
+
if (typeof server.on === 'function') {
|
|
101
|
+
attachEvents(server);
|
|
102
|
+
} else if (typeof server.events.on === 'function') {
|
|
103
|
+
attachEvents(server.events);
|
|
104
|
+
} else {
|
|
105
|
+
agent.logger.debug('unable to enable hapi error tracking');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
server.ext('onPreAuth', onPreAuth);
|
|
109
|
+
server.ext('onPreResponse', onPreResponse);
|
|
110
|
+
if (agent._conf.captureBody !== 'off') {
|
|
111
|
+
server.ext('onPostAuth', onPostAuth);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function attachEvents(emitter) {
|
|
116
|
+
emitter.on('log', function (event, tags) {
|
|
117
|
+
captureError('log', null, event, tags);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
emitter.on('request', function (req, event, tags) {
|
|
121
|
+
captureError('request', req, event, tags);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function captureError(type, req, event, tags) {
|
|
126
|
+
if (!event || !tags.error || event.channel === 'internal') {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Hapi 'log' and 'request' events (https://hapi.dev/api/#server.events)
|
|
131
|
+
// have `event.error`, `event.data`, or neither.
|
|
132
|
+
// `agent.captureError` requires an Error instance or string for its first
|
|
133
|
+
// arg: bias to getting that, then any other data add to `opts.custom.data`.
|
|
134
|
+
const info = event.error || event.data;
|
|
135
|
+
let errOrStr, data;
|
|
136
|
+
if (info instanceof Error || typeof info === 'string') {
|
|
137
|
+
errOrStr = info;
|
|
138
|
+
} else if (info) {
|
|
139
|
+
data = simpleDataFromEventData(agent, info);
|
|
140
|
+
}
|
|
141
|
+
if (!errOrStr) {
|
|
142
|
+
errOrStr = 'hapi server emitted a "' + type + '" event tagged "error"';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
agent.captureError(errOrStr, {
|
|
146
|
+
custom: {
|
|
147
|
+
tags: event.tags,
|
|
148
|
+
data,
|
|
149
|
+
},
|
|
150
|
+
request: req && req.raw && req.raw.req,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function onPreAuth(request, reply) {
|
|
155
|
+
agent.logger.debug('received hapi onPreAuth event');
|
|
156
|
+
|
|
157
|
+
// Record the fact that the preAuth extension have been called. This
|
|
158
|
+
// info is useful later to know if this is a CORS preflight request
|
|
159
|
+
// that is automatically handled by hapi (as those will not trigger
|
|
160
|
+
// the onPreAuth extention)
|
|
161
|
+
request[onPreAuthSym] = true;
|
|
162
|
+
|
|
163
|
+
if (request.route) {
|
|
164
|
+
// fingerprint was introduced in hapi 11 and is a little more
|
|
165
|
+
// stable in case the param names change
|
|
166
|
+
// - path example: /foo/{bar*2}
|
|
167
|
+
// - fingerprint example: /foo/?/?
|
|
168
|
+
var fingerprint = request.route.fingerprint || request.route.path;
|
|
169
|
+
|
|
170
|
+
if (fingerprint) {
|
|
171
|
+
var name =
|
|
172
|
+
(request.raw && request.raw.req && request.raw.req.method) ||
|
|
173
|
+
(request.route.method && request.route.method.toUpperCase());
|
|
174
|
+
|
|
175
|
+
if (typeof name === 'string') {
|
|
176
|
+
name = name + ' ' + fingerprint;
|
|
177
|
+
} else {
|
|
178
|
+
name = fingerprint;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
agent._instrumentation.setDefaultTransactionName(name);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return reply.continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function onPostAuth(request, reply) {
|
|
189
|
+
if (request.payload && request.raw && request.raw.req) {
|
|
190
|
+
// Save the parsed req body to be picked up by getContextFromRequest().
|
|
191
|
+
request.raw.req.payload = request.payload;
|
|
192
|
+
}
|
|
193
|
+
return reply.continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function onPreResponse(request, reply) {
|
|
197
|
+
agent.logger.debug('received hapi onPreResponse event');
|
|
198
|
+
|
|
199
|
+
// Detection of CORS preflight requests:
|
|
200
|
+
// There is no easy way in hapi to get the matched route for a
|
|
201
|
+
// CORS preflight request that matches any of the autogenerated
|
|
202
|
+
// routes created by hapi when `cors: true`. The best solution is to
|
|
203
|
+
// detect the request "fingerprint" using the magic if-sentence below
|
|
204
|
+
// and group all those requests into on type of transaction
|
|
205
|
+
if (
|
|
206
|
+
!request[onPreAuthSym] &&
|
|
207
|
+
request.route &&
|
|
208
|
+
request.route.path === '/{p*}' &&
|
|
209
|
+
request.raw &&
|
|
210
|
+
request.raw.req &&
|
|
211
|
+
request.raw.req.method === 'OPTIONS' &&
|
|
212
|
+
request.raw.req.headers['access-control-request-method']
|
|
213
|
+
) {
|
|
214
|
+
agent._instrumentation.setDefaultTransactionName('CORS preflight');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return reply.continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return hapi;
|
|
221
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// Instrument `.metrics.getMeterProvider()` from `@opentelemetry/api` to
|
|
10
|
+
// provide an Elastic APM provider if user code hasn't registered one itself.
|
|
11
|
+
//
|
|
12
|
+
// This covers use case 2 in the OTel metrics spec:
|
|
13
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#exporter-installation
|
|
14
|
+
|
|
15
|
+
const semver = require('semver');
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
isOTelMetricsFeatSupported,
|
|
19
|
+
} = require('../../../opentelemetry-metrics');
|
|
20
|
+
const shimmer = require('../../shimmer');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `otel.metrics.getMeterProvider()` returns a singleton instance of the
|
|
24
|
+
* internal `NoopMeterProvider` class if no global meter provider has been
|
|
25
|
+
* set. There isn't an explicitly API to determine if a provider is a noop
|
|
26
|
+
* one. This function attempts to sniff that out.
|
|
27
|
+
*
|
|
28
|
+
* We cannot rely on comparing to the `NOOP_METER_PROVIDER` exported by
|
|
29
|
+
* "src/metrics/NoopMeterProvider.ts" because there might be multiple
|
|
30
|
+
* "@opentelemetry/api" packages in play.
|
|
31
|
+
*
|
|
32
|
+
* @param {import('@opentelemetry/api').MeterProvider}
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
function isNoopMeterProvider(provider) {
|
|
36
|
+
return !!(
|
|
37
|
+
provider &&
|
|
38
|
+
provider.constructor &&
|
|
39
|
+
provider.constructor.name === 'NoopMeterProvider'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = function (mod, agent, { version, enabled }) {
|
|
44
|
+
const log = agent.logger;
|
|
45
|
+
|
|
46
|
+
if (!enabled) {
|
|
47
|
+
return mod;
|
|
48
|
+
}
|
|
49
|
+
if (!agent._isMetricsEnabled()) {
|
|
50
|
+
log.trace(
|
|
51
|
+
'metrics are not enabled, skipping @opentelemetry/api instrumentation',
|
|
52
|
+
version,
|
|
53
|
+
);
|
|
54
|
+
return mod;
|
|
55
|
+
}
|
|
56
|
+
if (!semver.satisfies(version, '>=1.3.0 <2', { includePrerelease: true })) {
|
|
57
|
+
log.debug(
|
|
58
|
+
'@opentelemetry/api version %s not supported, skipping @opentelemetry/api instrumentation',
|
|
59
|
+
version,
|
|
60
|
+
);
|
|
61
|
+
return mod;
|
|
62
|
+
}
|
|
63
|
+
if (!isOTelMetricsFeatSupported) {
|
|
64
|
+
log.debug(
|
|
65
|
+
'elastic-apm-node OTel Metrics support does not support node %s, skipping @opentelemetry/api instrumentation',
|
|
66
|
+
process.version,
|
|
67
|
+
);
|
|
68
|
+
return mod;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
log.debug('shimming @opentelemetry/api .metrics.getMeterProvider()');
|
|
72
|
+
shimmer.wrap(mod.metrics, 'getMeterProvider', wrapGetMeterProvider);
|
|
73
|
+
|
|
74
|
+
return mod;
|
|
75
|
+
|
|
76
|
+
function wrapGetMeterProvider(orig) {
|
|
77
|
+
return function wrappedGetMeterProvider() {
|
|
78
|
+
const provider = orig.apply(this, arguments);
|
|
79
|
+
if (!isNoopMeterProvider(provider)) {
|
|
80
|
+
return provider;
|
|
81
|
+
}
|
|
82
|
+
const elMeterProvider = agent._getOrCreateOTelMeterProvider();
|
|
83
|
+
return elMeterProvider || provider;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
// This instruments '@opentelemetry/sdk-metrics' to automatically add a metric
|
|
10
|
+
// reader to any `MeterProvider` created by user code. The added metric
|
|
11
|
+
// reader will export metrics to the configured APM server.
|
|
12
|
+
//
|
|
13
|
+
// This covers use case 1 in the OTel metrics spec:
|
|
14
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#exporter-installation
|
|
15
|
+
//
|
|
16
|
+
// Dev Note: This avoids instrumenting the `MeterProvider` used *internally*
|
|
17
|
+
// by the APM agent itself (see "lib/opentelemetry-metrics/index.js") because
|
|
18
|
+
// that file imports `MeterProvider` before the APM agent is started.
|
|
19
|
+
|
|
20
|
+
const semver = require('semver');
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
isOTelMetricsFeatSupported,
|
|
24
|
+
createOTelMetricReader,
|
|
25
|
+
} = require('../../../opentelemetry-metrics');
|
|
26
|
+
|
|
27
|
+
module.exports = function (mod, agent, { version, enabled }) {
|
|
28
|
+
const log = agent.logger;
|
|
29
|
+
|
|
30
|
+
if (!enabled) {
|
|
31
|
+
return mod;
|
|
32
|
+
}
|
|
33
|
+
if (!agent._isMetricsEnabled()) {
|
|
34
|
+
log.trace(
|
|
35
|
+
'metrics are not enabled, skipping @opentelemetry/sdk-metrics instrumentation',
|
|
36
|
+
version,
|
|
37
|
+
);
|
|
38
|
+
return mod;
|
|
39
|
+
}
|
|
40
|
+
// Minimum supported version is 1.11.0 because that version included the fix
|
|
41
|
+
// for side-effects from having two MetricReaders.
|
|
42
|
+
// https://github.com/open-telemetry/opentelemetry-js/issues/3664
|
|
43
|
+
if (!semver.satisfies(version, '>=1.11.0 <2', { includePrerelease: true })) {
|
|
44
|
+
log.debug(
|
|
45
|
+
'@opentelemetry/sdk-metrics@%s is not supported, skipping @opentelemetry/sdk-metrics instrumentation',
|
|
46
|
+
version,
|
|
47
|
+
);
|
|
48
|
+
return mod;
|
|
49
|
+
}
|
|
50
|
+
if (!isOTelMetricsFeatSupported) {
|
|
51
|
+
log.debug(
|
|
52
|
+
'elastic-apm-node OTel Metrics feature does not support node %s, skipping @opentelemetry/sdk-metrics instrumentation',
|
|
53
|
+
process.version,
|
|
54
|
+
);
|
|
55
|
+
return mod;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class ApmMeterProvider extends mod.MeterProvider {
|
|
59
|
+
constructor(...args) {
|
|
60
|
+
super(...args);
|
|
61
|
+
// We create a new metric reader for each new MeterProvider instance,
|
|
62
|
+
// because they shutdown independently -- they cannot be shared between
|
|
63
|
+
// multiple MeterProviders.
|
|
64
|
+
log.trace(
|
|
65
|
+
'@opentelemetry/sdk-metrics ins: create Elastic APM MetricReader',
|
|
66
|
+
);
|
|
67
|
+
this.addMetricReader(createOTelMetricReader(agent));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
Object.defineProperty(mod, 'MeterProvider', {
|
|
71
|
+
configurable: true,
|
|
72
|
+
enumerable: true,
|
|
73
|
+
get: function () {
|
|
74
|
+
return ApmMeterProvider;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return mod;
|
|
79
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
const semver = require('semver');
|
|
8
|
+
const { getDBDestination } = require('../../../../../../context');
|
|
9
|
+
const { redisClientOptions } = require('../../../../../../../../lib/symbols');
|
|
10
|
+
const destinationContext = Symbol('destinationContext');
|
|
11
|
+
|
|
12
|
+
const TYPE = 'db';
|
|
13
|
+
const SUBTYPE = 'redis';
|
|
14
|
+
const ACTION = 'query';
|
|
15
|
+
|
|
16
|
+
// From https://redis.io/commands/ this is the set of redis commands that have
|
|
17
|
+
// *two* tokens for the full command name.
|
|
18
|
+
const IS_COMMAND_PREFIX = new Set([
|
|
19
|
+
'ACL',
|
|
20
|
+
'CLIENT',
|
|
21
|
+
'CLUSTER',
|
|
22
|
+
'COMMAND',
|
|
23
|
+
'CONFIG',
|
|
24
|
+
'FUNCTION', // e.g. FUNCTION LOAD
|
|
25
|
+
'LATENCY',
|
|
26
|
+
'MEMORY',
|
|
27
|
+
'MODULE',
|
|
28
|
+
'OBJECT',
|
|
29
|
+
'PUBSUB',
|
|
30
|
+
'SCRIPT',
|
|
31
|
+
'SLOWLOG',
|
|
32
|
+
'XGROUP',
|
|
33
|
+
'XINFO',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Return the redis command name from the given `RedisCommandArguments`.
|
|
38
|
+
*
|
|
39
|
+
* export type RedisCommandArgument = string | Buffer;
|
|
40
|
+
* export type RedisCommandArguments = Array<RedisCommandArgument> & { preserve?: unknown };
|
|
41
|
+
*
|
|
42
|
+
* Examples:
|
|
43
|
+
* > commandNameFromArgs(['PING'])
|
|
44
|
+
* 'PING'
|
|
45
|
+
* > commandNameFromArgs(['SET', 'foo', 'bar'])
|
|
46
|
+
* 'SET'
|
|
47
|
+
* > commandNameFromArgs(['ACL', 'SETUSER', 'karin', 'on', '+@all', '-@dangerous'])
|
|
48
|
+
* 'ACL SETUSER'
|
|
49
|
+
*/
|
|
50
|
+
function commandNameFromArgs(args) {
|
|
51
|
+
if (IS_COMMAND_PREFIX.has(args[0]) && args.length >= 2) {
|
|
52
|
+
return args.slice(0, 2).join(' ');
|
|
53
|
+
} else {
|
|
54
|
+
return args[0];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = function (mod, agent, { version, enabled }) {
|
|
59
|
+
if (!enabled) {
|
|
60
|
+
return mod;
|
|
61
|
+
}
|
|
62
|
+
if (!semver.satisfies(version, '>=1 <2')) {
|
|
63
|
+
agent.logger.debug(
|
|
64
|
+
'@redis/client/dist/lib/client version %s not supported - aborting...',
|
|
65
|
+
version,
|
|
66
|
+
);
|
|
67
|
+
return mod;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const RedisCommandsQueue = mod.default;
|
|
71
|
+
if (
|
|
72
|
+
!(
|
|
73
|
+
RedisCommandsQueue &&
|
|
74
|
+
RedisCommandsQueue.name === 'RedisCommandsQueue' &&
|
|
75
|
+
RedisCommandsQueue.prototype &&
|
|
76
|
+
typeof RedisCommandsQueue.prototype.addCommand === 'function'
|
|
77
|
+
)
|
|
78
|
+
) {
|
|
79
|
+
agent.logger.debug('cannot instrument @redis/client');
|
|
80
|
+
return mod;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ins = agent._instrumentation;
|
|
84
|
+
|
|
85
|
+
agent.logger.debug(
|
|
86
|
+
'shimming @redis/client RedisCommandsQueue.prototype.addCommand',
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
class RedisCommandsQueueTraced extends RedisCommandsQueue {
|
|
90
|
+
constructor() {
|
|
91
|
+
super(arguments);
|
|
92
|
+
|
|
93
|
+
// Determine destination context to use for Redis spans, using the
|
|
94
|
+
// RedisClient `options` passed down from RedisClient.create instrumentation.
|
|
95
|
+
if (ins[redisClientOptions]) {
|
|
96
|
+
// https://github.com/redis/node-redis/blob/master/docs/client-configuration.md
|
|
97
|
+
// Reproducing determination of 'address' and 'port' per
|
|
98
|
+
// `RedisClient.#initializeOptions()`
|
|
99
|
+
let address = 'localhost';
|
|
100
|
+
let port = 6379;
|
|
101
|
+
if (ins[redisClientOptions].url) {
|
|
102
|
+
const parsed = new URL(ins[redisClientOptions].url);
|
|
103
|
+
address = parsed.hostname;
|
|
104
|
+
if (parsed.port) {
|
|
105
|
+
port = parsed.port;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (ins[redisClientOptions].socket) {
|
|
109
|
+
if (ins[redisClientOptions].socket.host) {
|
|
110
|
+
address = ins[redisClientOptions].socket.host;
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
ins[redisClientOptions].socket.port &&
|
|
114
|
+
!isNaN(Number(ins[redisClientOptions].socket.port))
|
|
115
|
+
) {
|
|
116
|
+
port = Number(ins[redisClientOptions].socket.port);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this[destinationContext] = getDBDestination(address, port);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
addCommand(args) {
|
|
125
|
+
const commandName = commandNameFromArgs(args);
|
|
126
|
+
agent.logger.debug(
|
|
127
|
+
{ commandName },
|
|
128
|
+
'intercepted call to @redis/client RedisCommandsQueue.prototype.addCommand',
|
|
129
|
+
);
|
|
130
|
+
const span = ins.createSpan(
|
|
131
|
+
commandName.toUpperCase(),
|
|
132
|
+
TYPE,
|
|
133
|
+
SUBTYPE,
|
|
134
|
+
ACTION,
|
|
135
|
+
{ exitSpan: true },
|
|
136
|
+
);
|
|
137
|
+
if (!span) {
|
|
138
|
+
return super.addCommand.apply(this, arguments);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
span.setDbContext({ type: 'redis' });
|
|
142
|
+
|
|
143
|
+
if (this[destinationContext]) {
|
|
144
|
+
span._setDestinationContext(this[destinationContext]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const parentRunContext = ins.currRunContext();
|
|
148
|
+
const spanRunContext = parentRunContext.enterSpan(span);
|
|
149
|
+
const finish = ins.bindFunctionToRunContext(spanRunContext, (err) => {
|
|
150
|
+
if (err) {
|
|
151
|
+
agent.captureError(err);
|
|
152
|
+
}
|
|
153
|
+
span.end();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const promise = super.addCommand.apply(this, arguments);
|
|
157
|
+
if (promise.then) {
|
|
158
|
+
promise.then(
|
|
159
|
+
function onResolve(_result) {
|
|
160
|
+
finish(null);
|
|
161
|
+
},
|
|
162
|
+
function onReject(err) {
|
|
163
|
+
finish(err);
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
} else {
|
|
167
|
+
// didn't get back the expected promose -- just end the
|
|
168
|
+
// span now and accept that those spans will be bogus
|
|
169
|
+
// (sync, zero-ish elapsed time).
|
|
170
|
+
span.end();
|
|
171
|
+
}
|
|
172
|
+
return promise;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
mod.default = RedisCommandsQueueTraced;
|
|
176
|
+
|
|
177
|
+
return mod;
|
|
178
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Shim @redis/client's `RedisClient.create` to pass client options down to
|
|
8
|
+
// the `RedisCommandsQueue` instrumentation in ./client/commands-queue.js.
|
|
9
|
+
const semver = require('semver');
|
|
10
|
+
const shimmer = require('../../../../../../shimmer');
|
|
11
|
+
const { redisClientOptions } = require('../../../../../../../../lib/symbols');
|
|
12
|
+
|
|
13
|
+
module.exports = function (mod, agent, { version, enabled }) {
|
|
14
|
+
if (!enabled) {
|
|
15
|
+
return mod;
|
|
16
|
+
}
|
|
17
|
+
if (!semver.satisfies(version, '>=1 <2')) {
|
|
18
|
+
agent.logger.debug(
|
|
19
|
+
'@redis/client/dist/lib/client version %s not supported - aborting...',
|
|
20
|
+
version,
|
|
21
|
+
);
|
|
22
|
+
return mod;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const RedisClient = mod.default;
|
|
26
|
+
if (!(RedisClient && typeof RedisClient.create === 'function')) {
|
|
27
|
+
agent.logger.debug('cannot instrument @redis/client RedisClient');
|
|
28
|
+
return mod;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ins = agent._instrumentation;
|
|
32
|
+
|
|
33
|
+
agent.logger.debug('shimming @redis/client RedisClient.create');
|
|
34
|
+
shimmer.wrap(RedisClient, 'create', function wrap(origCreate) {
|
|
35
|
+
return function wrappedCreate(options) {
|
|
36
|
+
// Capture the RedisClient options for just the synchronous run of the
|
|
37
|
+
// create function. We are relying on the RedisClient constructor
|
|
38
|
+
// synchronously creating its RedisCommandsQueue, which we've wrapped
|
|
39
|
+
// to pick up these options. This allows the queue instance to determine
|
|
40
|
+
// the appropriate destination context for spans it creates.
|
|
41
|
+
ins[redisClientOptions] = options;
|
|
42
|
+
const rv = origCreate.apply(this, arguments);
|
|
43
|
+
delete ins[redisClientOptions];
|
|
44
|
+
return rv;
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return mod;
|
|
49
|
+
};
|