@splunk/otel
Version:
The Splunk distribution of OpenTelemetry Node Instrumentation provides a Node agent that automatically instruments your Node application to capture and report distributed traces to Splunk APM.
292 lines • 11.6 kB
JavaScript
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.allowedMetricsOptions = void 0;
exports.createOtlpExporter = createOtlpExporter;
exports.defaultMetricReaderFactory = defaultMetricReaderFactory;
exports.startMetrics = startMetrics;
exports._setDefaultOptions = _setDefaultOptions;
const api_1 = require("@opentelemetry/api");
const resources_1 = require("@opentelemetry/resources");
const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
const exporter_metrics_otlp_proto_1 = require("@opentelemetry/exporter-metrics-otlp-proto");
const utils_1 = require("../utils");
const debug_metrics_1 = require("./debug_metrics");
const util = require("util");
const resource_1 = require("../resource");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const ConsoleMetricExporter_1 = require("./ConsoleMetricExporter");
const typedKeys = (obj) => Object.keys(obj);
function _loadExtension() {
let extension;
try {
extension = require('../native_ext');
}
catch (e) {
api_1.diag.error('Unable to load native metrics extension. Event loop and GC metrics will not be reported', e);
}
return extension === null || extension === void 0 ? void 0 : extension.metrics;
}
function recordGcSumMetric(counter, counters, field) {
for (const type of typedKeys(counters.gc)) {
counter.add(counters.gc[type][field].sum, {
'gc.type': type,
});
}
}
function recordGcCountMetric(counter, counters) {
for (const type of typedKeys(counters.gc)) {
counter.add(counters.gc[type].collected.count, {
'gc.type': type,
});
}
}
const SUPPORTED_EXPORTER_TYPES = ['console', 'otlp', 'none'];
function areValidExporterTypes(types) {
return types.every((t) => SUPPORTED_EXPORTER_TYPES.includes(t));
}
function createOtlpExporter(options) {
let protocol = (0, utils_1.getEnvValueByPrecedence)([
'OTEL_EXPORTER_OTLP_METRICS_PROTOCOL',
'OTEL_EXPORTER_OTLP_PROTOCOL',
]);
let endpoint = options.endpoint;
if (options.realm) {
if (protocol !== undefined && protocol !== 'http/protobuf') {
api_1.diag.warn(`OTLP metric exporter: defaulting protocol to 'http/protobuf' instead of '${protocol}' due to realm being defined.`);
}
const envEndpoint = (0, utils_1.getEnvValueByPrecedence)([
'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
'OTEL_EXPORTER_OTLP_ENDPOINT',
]);
if (endpoint === undefined && envEndpoint === undefined) {
endpoint = `https://ingest.${options.realm}.signalfx.com/v2/datapoint/otlp`;
protocol = 'http/protobuf';
}
else {
api_1.diag.warn('OTLP metric exporter factory: Realm value ignored (full endpoint URL has been specified).');
}
}
protocol = protocol !== null && protocol !== void 0 ? protocol : 'http/protobuf';
switch (protocol) {
case 'grpc': {
const grpcModule = require('@grpc/grpc-js');
const otlpGrpc = require('@opentelemetry/exporter-metrics-otlp-grpc');
const metadata = new grpcModule.Metadata();
if (options.accessToken) {
metadata.set('X-SF-TOKEN', options.accessToken);
}
return new otlpGrpc.OTLPMetricExporter({
url: endpoint,
metadata,
});
}
case 'http/protobuf': {
const headers = options.accessToken
? {
'X-SF-TOKEN': options.accessToken,
}
: undefined;
const url = (0, utils_1.ensureResourcePath)(endpoint, '/v1/metrics');
return new exporter_metrics_otlp_proto_1.OTLPMetricExporter({
url,
headers,
});
}
default:
throw new Error(`Metrics: expected OTLP protocol to be either grpc or http/protobuf, got ${protocol}.`);
}
}
function createExporters(options) {
const metricExporters = (0, utils_1.getEnvArray)('OTEL_METRICS_EXPORTER', [
'otlp',
]);
if (!areValidExporterTypes(metricExporters)) {
throw new Error(`Invalid value for OTEL_METRICS_EXPORTER env variable: ${util.inspect((0, utils_1.getNonEmptyEnvVar)('OTEL_METRICS_EXPORTER'))}. Choose from ${util.inspect(SUPPORTED_EXPORTER_TYPES, {
compact: true,
})} or leave undefined.`);
}
return metricExporters.flatMap((type) => {
switch (type) {
case 'otlp':
return createOtlpExporter(options);
case 'console':
return new ConsoleMetricExporter_1.ConsoleMetricExporter();
default:
return [];
}
});
}
function defaultMetricReaderFactory(options) {
return createExporters(options).map((exporter) => {
return new sdk_metrics_1.PeriodicExportingMetricReader({
exportIntervalMillis: options.exportIntervalMillis,
exporter,
});
});
}
exports.allowedMetricsOptions = [
'accessToken',
'realm',
'endpoint',
'exportIntervalMillis',
'metricReaderFactory',
'views',
'resourceFactory',
'runtimeMetricsEnabled',
'runtimeMetricsCollectionIntervalMillis',
'serviceName',
'debugMetricsEnabled',
];
function startMetrics(options) {
const debugMetricsViews = options.debugMetricsEnabled
? (0, debug_metrics_1.getDebugMetricsViews)()
: [];
const metricReaders = options.metricReaderFactory(options);
const provider = new sdk_metrics_1.MeterProvider({
resource: options.resource,
views: [...(options.views || []), ...debugMetricsViews],
readers: metricReaders,
});
api_1.metrics.setGlobalMeterProvider(provider);
async function stopGlobalMetrics() {
api_1.metrics.disable();
await provider.forceFlush();
await provider.shutdown();
}
if (options.debugMetricsEnabled) {
(0, debug_metrics_1.enableDebugMetrics)();
}
if (!options.runtimeMetricsEnabled) {
return {
stop: stopGlobalMetrics,
};
}
const meter = api_1.metrics.getMeter('splunk-otel-js-runtime-metrics');
meter
.createObservableGauge('process.runtime.nodejs.memory.heap.total', {
unit: 'By',
valueType: api_1.ValueType.INT,
})
.addCallback((result) => {
result.observe(process.memoryUsage().heapTotal);
});
meter
.createObservableGauge('process.runtime.nodejs.memory.heap.used', {
unit: 'By',
valueType: api_1.ValueType.INT,
})
.addCallback((result) => {
result.observe(process.memoryUsage().heapUsed);
});
meter
.createObservableGauge('process.runtime.nodejs.memory.rss', {
unit: 'By',
valueType: api_1.ValueType.INT,
})
.addCallback((result) => {
result.observe(process.memoryUsage().rss);
});
const extension = _loadExtension();
if (extension === undefined) {
return {
stop: stopGlobalMetrics,
};
}
extension.start();
let runtimeCounters = extension.collect();
meter
.createObservableGauge('process.runtime.nodejs.event_loop.lag.max', {
unit: 'ns',
valueType: api_1.ValueType.INT,
})
.addCallback((result) => {
result.observe(runtimeCounters.eventLoopLag.max);
});
meter
.createObservableGauge('process.runtime.nodejs.event_loop.lag.min', {
unit: 'ns',
valueType: api_1.ValueType.INT,
})
.addCallback((result) => {
result.observe(runtimeCounters.eventLoopLag.min);
});
const gcSizeCounter = meter.createCounter('process.runtime.nodejs.memory.gc.size', {
unit: 'By',
valueType: api_1.ValueType.INT,
});
const gcPauseCounter = meter.createCounter('process.runtime.nodejs.memory.gc.pause', {
unit: 'By',
valueType: api_1.ValueType.INT,
});
const gcCountCounter = meter.createCounter('process.runtime.nodejs.memory.gc.count', {
unit: '1',
valueType: api_1.ValueType.INT,
});
const interval = setInterval(() => {
runtimeCounters = extension.collect();
extension.reset();
recordGcSumMetric(gcSizeCounter, runtimeCounters, 'collected');
recordGcSumMetric(gcPauseCounter, runtimeCounters, 'duration');
recordGcCountMetric(gcCountCounter, runtimeCounters);
}, options.runtimeMetricsCollectionIntervalMillis);
interval.unref();
return {
stop: async () => {
clearInterval(interval);
await stopGlobalMetrics();
},
};
}
function _setDefaultOptions(options = {}) {
var _a, _b, _c, _d;
const accessToken = options.accessToken || (0, utils_1.getNonEmptyEnvVar)('SPLUNK_ACCESS_TOKEN') || '';
const realm = options.realm || (0, utils_1.getNonEmptyEnvVar)('SPLUNK_REALM') || '';
if (realm) {
if (!accessToken) {
throw new Error('Splunk realm is set, but access token is unset. To send metrics to the Observability Cloud, both need to be set');
}
if (options.metricReaderFactory) {
api_1.diag.warn('Splunk realm is set with a custom metric reader. Make sure to use OTLP metrics proto HTTP exporter.');
}
}
const envResource = (0, resource_1.getDetectedResource)();
const serviceName = String(options.serviceName ||
((_a = envResource.attributes) === null || _a === void 0 ? void 0 : _a[semantic_conventions_1.ATTR_SERVICE_NAME]) ||
(0, utils_1.defaultServiceName)());
const resourceFactory = options.resourceFactory || ((resource) => resource);
let resource = resourceFactory((0, resources_1.resourceFromAttributes)(envResource.attributes || {}));
resource = resource.merge((0, resources_1.resourceFromAttributes)({
[semantic_conventions_1.ATTR_SERVICE_NAME]: serviceName,
}));
return {
serviceName,
accessToken,
realm,
resource,
endpoint: options.endpoint,
views: options.views,
metricReaderFactory: (_b = options.metricReaderFactory) !== null && _b !== void 0 ? _b : defaultMetricReaderFactory,
exportIntervalMillis: options.exportIntervalMillis ||
(0, utils_1.getEnvNumber)('OTEL_METRIC_EXPORT_INTERVAL', 30000),
debugMetricsEnabled: (_c = options.debugMetricsEnabled) !== null && _c !== void 0 ? _c : (0, utils_1.getEnvBoolean)('SPLUNK_DEBUG_METRICS_ENABLED', false),
runtimeMetricsEnabled: (_d = options.runtimeMetricsEnabled) !== null && _d !== void 0 ? _d : (0, utils_1.getEnvBoolean)('SPLUNK_RUNTIME_METRICS_ENABLED', true),
runtimeMetricsCollectionIntervalMillis: options.runtimeMetricsCollectionIntervalMillis ||
(0, utils_1.getEnvNumber)('SPLUNK_RUNTIME_METRICS_COLLECTION_INTERVAL', 5000),
};
}
//# sourceMappingURL=index.js.map
;