@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,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const otel = require('@opentelemetry/api');
|
|
10
|
+
const semver = require('semver');
|
|
11
|
+
const timeOrigin = require('perf_hooks').performance.timeOrigin;
|
|
12
|
+
|
|
13
|
+
// Note: This is *OTel's* TraceState class, which differs from our TraceState
|
|
14
|
+
// class in "lib/tracecontext/...".
|
|
15
|
+
const { TraceState } = require('./opentelemetry-core-mini/trace/TraceState');
|
|
16
|
+
|
|
17
|
+
const haveUsablePerformanceNow = semver.satisfies(process.version, '>=8.12.0');
|
|
18
|
+
|
|
19
|
+
function isTimeInputHrTime(value) {
|
|
20
|
+
return (
|
|
21
|
+
Array.isArray(value) &&
|
|
22
|
+
value.length === 2 &&
|
|
23
|
+
typeof value[0] === 'number' &&
|
|
24
|
+
typeof value[1] === 'number'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Convert an OTel `TimeInput` to a number of milliseconds since the Unix epoch.
|
|
29
|
+
//
|
|
30
|
+
// The latter is the time format used for `SpanOptions.startTime` and the
|
|
31
|
+
// `endTime` arg to `Span.end()` and `Transaction.end()`.
|
|
32
|
+
//
|
|
33
|
+
// `TimeInput` is defined as:
|
|
34
|
+
// // hrtime, epoch milliseconds, performance.now() or Date
|
|
35
|
+
// export type TimeInput = HrTime | number | Date;
|
|
36
|
+
//
|
|
37
|
+
// This implementation is adapted from of TimeInput parsing in
|
|
38
|
+
// "@opentelemetry/core/src/common/time.ts". Some notes:
|
|
39
|
+
// - @opentelemetry/core base supported node is 8.12.0. The APM agent's is
|
|
40
|
+
// currently 8.6.0. Before 8.12.0, node's `performance.now()` did *not*
|
|
41
|
+
// return a value relative to `performance.timeOrigin()`. Therefore it was
|
|
42
|
+
// not useful to specify an absolute time value.
|
|
43
|
+
// - Allowing both epoch milliseconds and performance.now() in the same arg
|
|
44
|
+
// is inherently ambiguous. To disambiguate, the following code relies on an
|
|
45
|
+
// *assumption* (the same made by @opentelemetry/core) that one never wants to
|
|
46
|
+
// provide a TimeInput value that is before the process started. Woe be the
|
|
47
|
+
// code that attempts to set a retroactive span startTime for, say, an
|
|
48
|
+
// incoming message from an out of process queue.
|
|
49
|
+
function epochMsFromOTelTimeInput(otelTimeInput) {
|
|
50
|
+
if (isTimeInputHrTime(otelTimeInput)) {
|
|
51
|
+
// OTel's HrTime is `[<seconds since unix epoch>, <nanoseconds>]`
|
|
52
|
+
return otelTimeInput[0] * 1e3 + otelTimeInput[1] / 1e6;
|
|
53
|
+
} else if (typeof otelTimeInput === 'number') {
|
|
54
|
+
// Assume a performance.now() if it's smaller than process start time.
|
|
55
|
+
if (haveUsablePerformanceNow && otelTimeInput < timeOrigin) {
|
|
56
|
+
return timeOrigin + otelTimeInput;
|
|
57
|
+
} else {
|
|
58
|
+
return otelTimeInput;
|
|
59
|
+
}
|
|
60
|
+
} else if (otelTimeInput instanceof Date) {
|
|
61
|
+
return otelTimeInput.getTime();
|
|
62
|
+
} else {
|
|
63
|
+
throw TypeError(`Invalid OTel TimeInput: ${otelTimeInput}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Convert an OTel SpanContext to a traceparent string.
|
|
68
|
+
//
|
|
69
|
+
// Adapted from W3CTraceContextPropagator in @opentelemetry/core.
|
|
70
|
+
// https://github.com/open-telemetry/opentelemetry-js/blob/83355af4999c2d1ca660ce2499017d19642742bc/packages/opentelemetry-core/src/trace/W3CTraceContextPropagator.ts#L83-L85
|
|
71
|
+
function traceparentStrFromOTelSpanContext(spanContext) {
|
|
72
|
+
return `00-${spanContext.traceId}-${spanContext.spanId}-0${Number(
|
|
73
|
+
spanContext.traceFlags || otel.TraceFlags.NONE,
|
|
74
|
+
).toString(16)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Convert an Elastic TraceContext instance to an OTel SpanContext.
|
|
78
|
+
// These are the Elastic and OTel classes for storing W3C trace-context data.
|
|
79
|
+
function otelSpanContextFromTraceContext(traceContext) {
|
|
80
|
+
const traceparent = traceContext.traceparent;
|
|
81
|
+
const otelSpanContext = {
|
|
82
|
+
traceId: traceparent.traceId,
|
|
83
|
+
spanId: traceparent.id,
|
|
84
|
+
// `traceparent.flags` is a two-char hex string. `traceFlags` is a number.
|
|
85
|
+
// This conversion assumes `traceparent.flags` are valid.
|
|
86
|
+
traceFlags: parseInt(traceparent.flags, 16),
|
|
87
|
+
};
|
|
88
|
+
const traceStateStr = traceContext.toTraceStateString();
|
|
89
|
+
if (traceStateStr) {
|
|
90
|
+
otelSpanContext.traceState = new TraceState(traceStateStr);
|
|
91
|
+
}
|
|
92
|
+
return otelSpanContext;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
epochMsFromOTelTimeInput,
|
|
97
|
+
otelSpanContextFromTraceContext,
|
|
98
|
+
traceparentStrFromOTelSpanContext,
|
|
99
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const util = require('util');
|
|
10
|
+
const otel = require('@opentelemetry/api');
|
|
11
|
+
|
|
12
|
+
const logging = require('../logging');
|
|
13
|
+
const { fetchSpanKey } = require('./OTelBridgeRunContext');
|
|
14
|
+
const oblog = require('./oblog');
|
|
15
|
+
const { OTelContextManager } = require('./OTelContextManager');
|
|
16
|
+
const { OTelTracerProvider } = require('./OTelTracerProvider');
|
|
17
|
+
const { OTelTracer } = require('./OTelTracer');
|
|
18
|
+
|
|
19
|
+
function setupOTelBridge(agent) {
|
|
20
|
+
let success;
|
|
21
|
+
|
|
22
|
+
const log = logging.isLoggerCustom(agent.logger)
|
|
23
|
+
? agent.logger
|
|
24
|
+
: agent.logger.child({ 'event.module': 'otelbridge' });
|
|
25
|
+
|
|
26
|
+
// `otel.diag` is "the OpenTelemetry internal diagnostic API". If *trace*
|
|
27
|
+
// level logging is enabled, then hook into diag.
|
|
28
|
+
if (log.isLevelEnabled('trace')) {
|
|
29
|
+
success = otel.diag.setLogger(
|
|
30
|
+
{
|
|
31
|
+
verbose: log.trace.bind(log),
|
|
32
|
+
debug: log.debug.bind(log),
|
|
33
|
+
info: log.info.bind(log),
|
|
34
|
+
warn: log.warn.bind(log),
|
|
35
|
+
error: log.error.bind(log),
|
|
36
|
+
},
|
|
37
|
+
otel.DiagLogLevel.ALL,
|
|
38
|
+
);
|
|
39
|
+
if (!success) {
|
|
40
|
+
log.error('could not register OpenTelemetry bridge diag logger');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// For development/debugging-only, set `LOG_OTEL_API_CALLS = true` to get log
|
|
46
|
+
// (almost) every call into the OpenTelemetry API. See docs in "oblog.js".
|
|
47
|
+
const LOG_OTEL_API_CALLS = false;
|
|
48
|
+
if (LOG_OTEL_API_CALLS) {
|
|
49
|
+
// oblog.setApiCallLogFn(log.debug.bind(log)) // Alternative, to use our ecs logger.
|
|
50
|
+
oblog.setApiCallLogFn((...args) => {
|
|
51
|
+
const s = util.format(...args);
|
|
52
|
+
console.log('\x1b[90motelapi:\x1b[39m \x1b[32m' + s + '\x1b[39m');
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
success = otel.trace.setGlobalTracerProvider(
|
|
57
|
+
new OTelTracerProvider(new OTelTracer(agent)),
|
|
58
|
+
);
|
|
59
|
+
if (!success) {
|
|
60
|
+
log.error('could not register OpenTelemetry bridge TracerProvider');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The OTelBridgeRunContext class needs to get the SPAN_KEY before it can
|
|
65
|
+
// be used.
|
|
66
|
+
fetchSpanKey();
|
|
67
|
+
|
|
68
|
+
success = otel.context.setGlobalContextManager(new OTelContextManager(agent));
|
|
69
|
+
if (!success) {
|
|
70
|
+
log.error('could not register OpenTelemetry bridge ContextManager');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
setupOTelBridge,
|
|
76
|
+
};
|
|
@@ -0,0 +1,285 @@
|
|
|
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 { ExportResultCode } = require('@opentelemetry/core');
|
|
8
|
+
const {
|
|
9
|
+
AggregationTemporality,
|
|
10
|
+
InstrumentType,
|
|
11
|
+
ExplicitBucketHistogramAggregation,
|
|
12
|
+
SumAggregation,
|
|
13
|
+
LastValueAggregation,
|
|
14
|
+
DropAggregation,
|
|
15
|
+
DataPointType,
|
|
16
|
+
} = require('@opentelemetry/sdk-metrics');
|
|
17
|
+
const { LRUCache } = require('lru-cache');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The `timestamp` in a metricset for APM Server intake is "UTC based and
|
|
21
|
+
* formatted as microseconds since Unix epoch".
|
|
22
|
+
*
|
|
23
|
+
* Dev note: We need to round because APM server intake requires an integer.
|
|
24
|
+
* This means a loss of sub-ms precision, which for this use case is fine.
|
|
25
|
+
*/
|
|
26
|
+
function metricTimestampFromOTelHrTime(otelHrTime) {
|
|
27
|
+
// OTel's HrTime is `[<seconds since unix epoch>, <nanoseconds>]`
|
|
28
|
+
return Math.round(otelHrTime[0] * 1e6 + otelHrTime[1] / 1e3);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// From oteljs/packages/sdk-metrics/src/utils.ts#hashAttributes
|
|
32
|
+
function hashAttributes(attributes) {
|
|
33
|
+
let keys = Object.keys(attributes);
|
|
34
|
+
if (keys.length === 0) return '';
|
|
35
|
+
|
|
36
|
+
// Return a string that is stable on key orders.
|
|
37
|
+
keys = keys.sort();
|
|
38
|
+
return JSON.stringify(keys.map((key) => [key, attributes[key]]));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fill in an Intake V2 API "sample" object for a histogram from an OTel Metrics
|
|
43
|
+
* histogram DataPoint.
|
|
44
|
+
*
|
|
45
|
+
* Algorithm is from the spec (`convertBucketBoundaries`)
|
|
46
|
+
*/
|
|
47
|
+
function fillIntakeHistogramSample(sample, otelDataPoint) {
|
|
48
|
+
const otelCounts = otelDataPoint.value.buckets.counts;
|
|
49
|
+
const otelBoundaries = otelDataPoint.value.buckets.boundaries;
|
|
50
|
+
|
|
51
|
+
const bucketCount = otelCounts.length;
|
|
52
|
+
if (bucketCount === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const intakeCounts = (sample.counts = []);
|
|
57
|
+
const intakeValues = (sample.values = []);
|
|
58
|
+
sample.type = 'histogram';
|
|
59
|
+
|
|
60
|
+
// otelBoundaries has a size of bucketCount-1
|
|
61
|
+
// the first bucket has the boundaries ( -inf, otelBoundaries[0] ]
|
|
62
|
+
// the second bucket has the boundaries ( otelBoundaries[0], otelBoundaries[1] ]
|
|
63
|
+
// ..
|
|
64
|
+
// the last bucket has the boundaries (otelBoundaries[bucketCount-2], inf)
|
|
65
|
+
for (let i = 0; i < bucketCount; i++) {
|
|
66
|
+
if (otelCounts[i] !== 0) {
|
|
67
|
+
// ignore empty buckets
|
|
68
|
+
intakeCounts.push(otelCounts[i]);
|
|
69
|
+
if (i === 0) {
|
|
70
|
+
// first bucket
|
|
71
|
+
let bound = otelBoundaries[i];
|
|
72
|
+
if (bound > 0) {
|
|
73
|
+
bound /= 2;
|
|
74
|
+
}
|
|
75
|
+
intakeValues.push(bound);
|
|
76
|
+
} else if (i === bucketCount - 1) {
|
|
77
|
+
// last bucket
|
|
78
|
+
intakeValues.push(otelBoundaries[bucketCount - 2]);
|
|
79
|
+
} else {
|
|
80
|
+
// in between
|
|
81
|
+
const lower = otelBoundaries[i - 1];
|
|
82
|
+
const upper = otelBoundaries[i];
|
|
83
|
+
intakeValues.push(lower + (upper - lower) / 2);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A PushMetricExporter that exports to an Elastic APM server. It is meant to be
|
|
91
|
+
* used with a PeriodicExportingMetricReader -- which defers to
|
|
92
|
+
* `selectAggregation` and `selectAggregationTemporality` on this class.
|
|
93
|
+
*
|
|
94
|
+
* @implements {import('@opentelemetry/sdk-metrics').PushMetricExporter}
|
|
95
|
+
*/
|
|
96
|
+
class ElasticApmMetricExporter {
|
|
97
|
+
constructor(agent) {
|
|
98
|
+
this._agent = agent;
|
|
99
|
+
this._histogramAggregation = new ExplicitBucketHistogramAggregation(
|
|
100
|
+
this._agent._conf.customMetricsHistogramBoundaries,
|
|
101
|
+
);
|
|
102
|
+
this._sumAggregation = new SumAggregation();
|
|
103
|
+
this._lastValueAggregation = new LastValueAggregation();
|
|
104
|
+
this._dropAggregation = new DropAggregation();
|
|
105
|
+
this._attrDropWarnCache = new LRUCache({ max: 1000 });
|
|
106
|
+
this._dataPointTypeDropWarnCache = new LRUCache({ max: 1000 });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Spec: https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#aggregation
|
|
111
|
+
*
|
|
112
|
+
* @param {import('@opentelemetry/sdk-metrics').InstrumentType} instrumentType
|
|
113
|
+
* @returns {import('@opentelemetry/sdk-metrics').Aggregation}
|
|
114
|
+
*/
|
|
115
|
+
selectAggregation(instrumentType) {
|
|
116
|
+
// The same behaviour as OTel's `DefaultAggregation`, except for changes
|
|
117
|
+
// to the default Histogram bucket sizes and support for the
|
|
118
|
+
// `custom_metrics_histogram_boundaries` config var.
|
|
119
|
+
switch (instrumentType) {
|
|
120
|
+
case InstrumentType.COUNTER:
|
|
121
|
+
case InstrumentType.UP_DOWN_COUNTER:
|
|
122
|
+
case InstrumentType.OBSERVABLE_COUNTER:
|
|
123
|
+
case InstrumentType.OBSERVABLE_UP_DOWN_COUNTER:
|
|
124
|
+
return this._sumAggregation;
|
|
125
|
+
case InstrumentType.OBSERVABLE_GAUGE:
|
|
126
|
+
return this._lastValueAggregation;
|
|
127
|
+
case InstrumentType.HISTOGRAM:
|
|
128
|
+
return this._histogramAggregation;
|
|
129
|
+
default:
|
|
130
|
+
this._agent.logger.warn(
|
|
131
|
+
`cannot selectAggregation: unknown OTel Metric instrumentType: ${instrumentType}`,
|
|
132
|
+
);
|
|
133
|
+
return this._dropAggregation;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Spec: https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#aggregation-temporality
|
|
138
|
+
//
|
|
139
|
+
// Note: This differs from the OTel SDK default.
|
|
140
|
+
// `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=Cumulative`
|
|
141
|
+
// https://opentelemetry.io/docs/reference/specification/metrics/sdk_exporters/otlp/
|
|
142
|
+
selectAggregationTemporality(instrumentType) {
|
|
143
|
+
switch (instrumentType) {
|
|
144
|
+
case InstrumentType.COUNTER:
|
|
145
|
+
case InstrumentType.OBSERVABLE_COUNTER:
|
|
146
|
+
case InstrumentType.HISTOGRAM:
|
|
147
|
+
case InstrumentType.OBSERVABLE_GAUGE:
|
|
148
|
+
return AggregationTemporality.DELTA;
|
|
149
|
+
case InstrumentType.UP_DOWN_COUNTER:
|
|
150
|
+
case InstrumentType.OBSERVABLE_UP_DOWN_COUNTER:
|
|
151
|
+
return AggregationTemporality.CUMULATIVE;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async forceFlush() {
|
|
156
|
+
return this._agent.flush();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async shutdown() {
|
|
160
|
+
return this._agent.flush();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Export an OTel `ResourceMetrics` to Elastic APM intake `metricset`s.
|
|
165
|
+
*
|
|
166
|
+
* Dev notes:
|
|
167
|
+
* - Explicitly *not* including `metricData.descriptor.unit` because the APM
|
|
168
|
+
* spec doesn't include it. It isn't clear there is value.
|
|
169
|
+
*/
|
|
170
|
+
export(resourceMetrics, resultCallback) {
|
|
171
|
+
// console.log('resourceMetrics:'); console.dir(resourceMetrics, { depth: 10 })
|
|
172
|
+
for (const scopeMetrics of resourceMetrics.scopeMetrics) {
|
|
173
|
+
// Metrics from separate instrumentation scopes must be in separate
|
|
174
|
+
// `metricset` objects. In the future, the APM spec may dictate that we
|
|
175
|
+
// add labels for the instrumentation scope -- perhaps `otel.scope.*`.
|
|
176
|
+
// Discussion: https://github.com/elastic/apm/pull/742#discussion_r1061444699
|
|
177
|
+
const metricsetFromAttrHash = {};
|
|
178
|
+
|
|
179
|
+
for (const metricData of scopeMetrics.metrics) {
|
|
180
|
+
const metricName = metricData.descriptor.name;
|
|
181
|
+
if (this._agent._isMetricNameDisabled(metricName)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (
|
|
185
|
+
!(
|
|
186
|
+
metricData.dataPointType === DataPointType.GAUGE ||
|
|
187
|
+
metricData.dataPointType === DataPointType.SUM ||
|
|
188
|
+
metricData.dataPointType === DataPointType.HISTOGRAM
|
|
189
|
+
)
|
|
190
|
+
) {
|
|
191
|
+
if (!this._dataPointTypeDropWarnCache.has(metricName)) {
|
|
192
|
+
this._agent.logger.warn(
|
|
193
|
+
`dropping metric "${metricName}": cannot export metrics with dataPointType=${metricData.dataPointType}`,
|
|
194
|
+
);
|
|
195
|
+
this._dataPointTypeDropWarnCache.set(metricName, true);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const dataPoint of metricData.dataPoints) {
|
|
200
|
+
const labels = this._labelsFromOTelMetricAttributes(
|
|
201
|
+
dataPoint.attributes,
|
|
202
|
+
metricData.descriptor.name,
|
|
203
|
+
);
|
|
204
|
+
const attrHash = hashAttributes(labels);
|
|
205
|
+
let metricset = metricsetFromAttrHash[attrHash];
|
|
206
|
+
if (!metricset) {
|
|
207
|
+
metricset = {
|
|
208
|
+
samples: {},
|
|
209
|
+
// Assumption: `endTime` is the same for all `dataPoint`s in
|
|
210
|
+
// this `metricData`.
|
|
211
|
+
timestamp: metricTimestampFromOTelHrTime(dataPoint.endTime),
|
|
212
|
+
tags: labels,
|
|
213
|
+
};
|
|
214
|
+
metricsetFromAttrHash[attrHash] = metricset;
|
|
215
|
+
}
|
|
216
|
+
const sample = {};
|
|
217
|
+
switch (metricData.dataPointType) {
|
|
218
|
+
case DataPointType.GAUGE:
|
|
219
|
+
sample.type = 'gauge';
|
|
220
|
+
sample.value = dataPoint.value;
|
|
221
|
+
break;
|
|
222
|
+
case DataPointType.SUM:
|
|
223
|
+
if (metricData.isMonotonic) {
|
|
224
|
+
sample.type = 'counter';
|
|
225
|
+
} else {
|
|
226
|
+
sample.type = 'gauge';
|
|
227
|
+
}
|
|
228
|
+
sample.value = dataPoint.value;
|
|
229
|
+
break;
|
|
230
|
+
case DataPointType.HISTOGRAM:
|
|
231
|
+
fillIntakeHistogramSample(sample, dataPoint);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
if (sample.type) {
|
|
235
|
+
metricset.samples[metricData.descriptor.name] = sample;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Importantly, if a metric has no `dataPoints` then we send nothing. This
|
|
241
|
+
// satisfies the following from the APM agents spec:
|
|
242
|
+
//
|
|
243
|
+
// > For all instrument types with delta temporality, agents MUST filter out
|
|
244
|
+
// > zero values before exporting.
|
|
245
|
+
Object.values(metricsetFromAttrHash).forEach((metricset) => {
|
|
246
|
+
this._agent._apmClient.sendMetricSet(metricset);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Convert from `dataPoint.attributes` to a set of labels (a.k.a. tags) for
|
|
255
|
+
* Elastic APM intake. Attributes with an *array* value are not supported --
|
|
256
|
+
* they are dropped with a log.warn that mentions the metric and attribute
|
|
257
|
+
* names.
|
|
258
|
+
*
|
|
259
|
+
* This makes *in-place* changes to the given `attrs` argument. It returns
|
|
260
|
+
* the same object.
|
|
261
|
+
*
|
|
262
|
+
* https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#labels
|
|
263
|
+
*/
|
|
264
|
+
_labelsFromOTelMetricAttributes(attrs, metricName) {
|
|
265
|
+
const keys = Object.keys(attrs);
|
|
266
|
+
for (var i = 0; i < keys.length; i++) {
|
|
267
|
+
const k = keys[i];
|
|
268
|
+
const v = attrs[k];
|
|
269
|
+
if (Array.isArray(v)) {
|
|
270
|
+
delete attrs[k];
|
|
271
|
+
const cacheKey = metricName + '/' + k;
|
|
272
|
+
if (!this._attrDropWarnCache.has(cacheKey)) {
|
|
273
|
+
this._agent.logger.warn(
|
|
274
|
+
{ metricName, attrName: k },
|
|
275
|
+
'dropping array-valued metric attribute: array attribute values are not supported',
|
|
276
|
+
);
|
|
277
|
+
this._attrDropWarnCache.set(cacheKey, true);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return attrs;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = ElasticApmMetricExporter;
|
|
@@ -0,0 +1,50 @@
|
|
|
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 assert = require('assert');
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
MeterProvider,
|
|
11
|
+
PeriodicExportingMetricReader,
|
|
12
|
+
} = require('@opentelemetry/sdk-metrics');
|
|
13
|
+
const semver = require('semver');
|
|
14
|
+
|
|
15
|
+
const ElasticApmMetricExporter = require('./ElasticApmMetricExporter');
|
|
16
|
+
|
|
17
|
+
// `isOTelMetricsFeatSupported` is true if the agent's included OTel Metrics
|
|
18
|
+
// feature is supported. Currently this depends on the Node.js version supported
|
|
19
|
+
// by the Metrics SDK package.
|
|
20
|
+
const _supportRange = require('@opentelemetry/sdk-metrics/package.json').engines
|
|
21
|
+
.node;
|
|
22
|
+
const isOTelMetricsFeatSupported = semver.satisfies(
|
|
23
|
+
process.version,
|
|
24
|
+
_supportRange,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
function createOTelMetricReader(agent) {
|
|
28
|
+
const metricsInterval = agent._conf.metricsInterval;
|
|
29
|
+
assert(
|
|
30
|
+
metricsInterval > 0,
|
|
31
|
+
'createOTelMeterProvider() should not be called if metricsInterval <= 0',
|
|
32
|
+
);
|
|
33
|
+
return new PeriodicExportingMetricReader({
|
|
34
|
+
exporter: new ElasticApmMetricExporter(agent),
|
|
35
|
+
exportIntervalMillis: metricsInterval * 1000,
|
|
36
|
+
exportTimeoutMillis: (metricsInterval / 2) * 1000,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createOTelMeterProvider(agent) {
|
|
41
|
+
const meterProvider = new MeterProvider();
|
|
42
|
+
meterProvider.addMetricReader(createOTelMetricReader(agent));
|
|
43
|
+
return meterProvider;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
isOTelMetricsFeatSupported,
|
|
48
|
+
createOTelMetricReader,
|
|
49
|
+
createOTelMeterProvider,
|
|
50
|
+
};
|