UNPKG

@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
"use strict"; /* * 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