Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/opentelemetry-node/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @elastic/opentelemetry-node Changelog

## Unreleased

- Upgrade upstream OTel dependencies to SDK 2.0. This should be non-breaking
for users of `node --import @elastic/opentelemetry-node my-app.js` to start
EDOT Node.js for their application.
(https://github.com/elastic/elastic-otel-node/pull/663)

## v0.7.0

- BREAKING CHANGE: Bump min-supported node to `^18.19.0 || >=20.6.0`.
Expand Down
59 changes: 29 additions & 30 deletions packages/opentelemetry-node/lib/detectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@
*/

/**
* NOTE: when `Detector` is finally removed import only `DetectorSync` and
* get rid of the aliasing
* @typedef {import('@opentelemetry/resources').Detector} DetectorOrig
* @typedef {import('@opentelemetry/resources').DetectorSync} DetectorSyncOrig
* @typedef {DetectorOrig | DetectorSyncOrig} DetectorSync
* @typedef {import('@opentelemetry/resources').ResourceDetector} ResourceDetector
*/

const {getStringListFromEnv} = require('@opentelemetry/core');
const {
alibabaCloudEcsDetector,
} = require('@opentelemetry/resource-detector-alibaba-cloud');
Expand All @@ -31,41 +28,41 @@ const {
} = require('@opentelemetry/resource-detector-container');
const {gcpDetector} = require('@opentelemetry/resource-detector-gcp');
const {
envDetectorSync,
hostDetectorSync,
osDetectorSync,
processDetectorSync,
serviceInstanceIdDetectorSync,
Resource,
envDetector,
hostDetector,
osDetector,
processDetector,
serviceInstanceIdDetector,
} = require('@opentelemetry/resources');

const {getEnvVar} = require('./environment');
const {log} = require('./logging');

// @ts-ignore - compiler options do not allow lookp outside `lib` folder
const ELASTIC_SDK_VERSION = require('../package.json').version;

// Elastic's own detector to add distro related metadata
/** @type {DetectorSync} */
const distroDetectorSync = {
/** @type {ResourceDetector} */
const distroDetector = {
detect() {
// TODO: change to semconv resource attribs when
// `@opentelemetry/semantic-conventions` gets updated with the attribs used
// https://github.com/open-telemetry/opentelemetry-js/issues/4235
return new Resource({
'telemetry.distro.name': 'elastic',
'telemetry.distro.version': ELASTIC_SDK_VERSION,
});
return {
attributes: {
'telemetry.distro.name': 'elastic',
'telemetry.distro.version': ELASTIC_SDK_VERSION,
},
};
},
};

/** @type {Record<string, DetectorSync | Array<DetectorSync>>} */
/** @type {Record<string, ResourceDetector | Array<ResourceDetector>>} */
const defaultDetectors = {
env: envDetectorSync,
process: processDetectorSync,
serviceinstance: serviceInstanceIdDetectorSync,
os: osDetectorSync,
host: hostDetectorSync,
env: envDetector,
process: processDetector,
serviceinstance: serviceInstanceIdDetector,
os: osDetector,
host: hostDetector,
container: containerDetector,
alibaba: alibabaCloudEcsDetector,
aws: [
Expand All @@ -80,24 +77,26 @@ const defaultDetectors = {
};

/**
* @param {Array<DetectorSync>} [detectors]
* @returns {Array<DetectorSync>}
* @param {Array<ResourceDetector>} [detectors]
* @returns {Array<ResourceDetector>}
*/
function resolveDetectors(detectors) {
if (detectors) {
detectors.push(distroDetectorSync);
detectors.push(distroDetector);
return detectors;
}

let detectorKeys = getEnvVar('OTEL_NODE_RESOURCE_DETECTORS');
let detectorKeys = getStringListFromEnv('OTEL_NODE_RESOURCE_DETECTORS') || [
'all',
];
if (detectorKeys.some((k) => k === 'all')) {
detectorKeys = Object.keys(defaultDetectors);
} else if (detectorKeys.some((k) => k === 'none')) {
return [];
}

/** @type {Array<DetectorSync | DetectorSync[]>} */
const resolvedDetectors = [distroDetectorSync];
/** @type {Array<ResourceDetector | ResourceDetector[]>} */
const resolvedDetectors = [distroDetector];
for (const key of detectorKeys) {
if (defaultDetectors[key]) {
resolvedDetectors.push(defaultDetectors[key]);
Expand Down
34 changes: 21 additions & 13 deletions packages/opentelemetry-node/lib/elastic-node-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@

const os = require('os');

const {
getBooleanFromEnv,
getStringFromEnv,
getNumberFromEnv,
} = require('@opentelemetry/core');
const {metrics, NodeSDK, api} = require('@opentelemetry/sdk-node');
const {BatchLogRecordProcessor} = require('@opentelemetry/sdk-logs');

const {log, registerOTelDiagLogger} = require('./logging');
const {resolveDetectors} = require('./detectors');
const {
setupEnvironment,
restoreEnvironment,
getEnvVar,
} = require('./environment');
const {setupEnvironment, restoreEnvironment} = require('./environment');
const {getInstrumentations} = require('./instrumentations');
const {enableHostMetrics, HOST_METRICS_VIEWS} = require('./metrics/host');
// @ts-ignore - compiler options do not allow lookp outside `lib` folder
Expand All @@ -32,9 +33,6 @@ class ElasticNodeSDK extends NodeSDK {
log.trace('ElasticNodeSDK opts:', opts);
registerOTelDiagLogger(api);

// Setup & fix some env
setupEnvironment();

// - NodeSDK defaults to `TracerProviderWithEnvExporters` if neither
// `spanProcessor` nor `traceExporter` are passed in.
/** @type {Partial<NodeSDKConfiguration>} */
Expand All @@ -53,7 +51,8 @@ class ElasticNodeSDK extends NodeSDK {
// Get logs exporter protocol based on environment.
const logsExportProtocol =
process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL ||
getEnvVar('OTEL_EXPORTER_OTLP_PROTOCOL');
getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL') ||
'http/protobuf';
let logExporterType = exporterPkgNameFromEnvVar[logsExportProtocol];
if (!logExporterType) {
log.warn(
Expand All @@ -77,12 +76,14 @@ class ElasticNodeSDK extends NodeSDK {
// TODO what `temporalityPreference`?

// Disable metrics by config
const metricsDisabled = getEnvVar('ELASTIC_OTEL_METRICS_DISABLED');
const metricsDisabled =
getBooleanFromEnv('ELASTIC_OTEL_METRICS_DISABLED') ?? false;
if (!metricsDisabled) {
// Get metrics exporter protocol based on environment.
const metricsExportProtocol =
process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL ||
getEnvVar('OTEL_EXPORTER_OTLP_PROTOCOL');
getStringFromEnv('OTEL_EXPORTER_OTLP_PROTOCOL') ||
'http/protobuf';
let metricExporterType =
exporterPkgNameFromEnvVar[metricsExportProtocol];
if (!metricExporterType) {
Expand All @@ -98,8 +99,12 @@ class ElasticNodeSDK extends NodeSDK {
`@opentelemetry/exporter-metrics-otlp-${metricExporterType}`
);

const metricsInterval = getEnvVar('OTEL_METRIC_EXPORT_INTERVAL');
const metricsTimeout = getEnvVar('OTEL_METRIC_EXPORT_TIMEOUT');
// Note: Default values has been taken from the specs
// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#periodic-exporting-metricreader
const metricsInterval =
getNumberFromEnv('OTEL_METRIC_EXPORT_INTERVAL') ?? 60000;
const metricsTimeout =
getNumberFromEnv('OTEL_METRIC_EXPORT_TIMEOUT') ?? 30000;
defaultConfig.metricReader =
new metrics.PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
Expand All @@ -112,6 +117,9 @@ class ElasticNodeSDK extends NodeSDK {
];
}

// Setup & fix some env
setupEnvironment();

const configuration = Object.assign(defaultConfig, opts);
super(configuration);

Expand Down
112 changes: 1 addition & 111 deletions packages/opentelemetry-node/lib/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

// NOTE: this API may be removed in future
// ref: https://github.com/open-telemetry/opentelemetry-js/issues/5172
const {getEnv} = require('@opentelemetry/core');

/** @type {NodeJS.ProcessEnv} */
const envToRestore = {};

/**
* Returns an array of strings from the given input. If undefined returns the fallback
* value.
* @param {string | undefined} str
* @param {string[]} [fallback=[]]
* @returns {string[]}
*/
function parseStringList(str, fallback = []) {
if (!str) {
return fallback;
}
return str.split(',').map((s) => s.trim());
}

/**
* Returns a boolean from the given input
* @param {string | undefined} str
* @param {boolean} fallback
* @returns {boolean}
*/
function parseBoolean(str, fallback) {
if (!str) {
return fallback;
}
return str.toLowerCase() === 'true';
}

/**
* Returns a boolean from te given input
* @param {string | undefined} str
* @param {number} fallback
* @returns {number}
*/
function parseNumber(str, fallback) {
if (!str) {
return fallback;
}

const num = Number(str);
return isNaN(num) ? fallback : num;
}

/**
* This funtion makes necessari changes to the environment so:
* - Avoid OTEL's NodeSDK known warnings (eg. OTEL_TRACES_EXPORTER not set)
Expand Down Expand Up @@ -86,75 +40,11 @@ function setupEnvironment() {
function restoreEnvironment() {
Object.keys(envToRestore).forEach((k) => {
process.env[k] = envToRestore[k];
delete envToRestore[k];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note for reviewer: IMO the getters defined below should take values directly from prcess.env once the environment vars are restored.

});
}

/**
* @typedef {Object} EdotEnv
* @property {string[]} OTEL_NODE_RESOURCE_DETECTORS
* @property {number} OTEL_METRIC_EXPORT_INTERVAL
* @property {number} OTEL_METRIC_EXPORT_TIMEOUT
* @property {boolean} ELASTIC_OTEL_METRICS_DISABLED
*/
/**
* @typedef {keyof EdotEnv} EdotEnvKey
*/
/** @type {EdotEnv} */
const edotEnv = {
// Missing OTEL_ vars from global spec and nodejs specific spec
OTEL_NODE_RESOURCE_DETECTORS: parseStringList(
process.env.OTEL_NODE_RESOURCE_DETECTORS,
['all']
),
// Note: Default values has been taken from the specs
// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#periodic-exporting-metricreader
OTEL_METRIC_EXPORT_INTERVAL: parseNumber(
process.env.OTEL_METRIC_EXPORT_INTERVAL,
60000
),
OTEL_METRIC_EXPORT_TIMEOUT: parseNumber(
process.env.OTEL_METRIC_EXPORT_TIMEOUT,
30000
),
// ELASTIC_OTEL_ vars
ELASTIC_OTEL_METRICS_DISABLED: parseBoolean(
process.env.ELASTIC_OTEL_METRICS_DISABLED,
false
),
};

/**
* @typedef {import('@opentelemetry/core').ENVIRONMENT} OtelEnv
*/
/**
* @typedef {keyof OtelEnv} OtelEnvKey
*/
const otelEnv = getEnv();

/**
* @template T
* @typedef {T extends OtelEnvKey ? OtelEnv[T] : T extends EdotEnvKey ? EdotEnv[T] : never} EnvValue<T>
*/
/**
* @template {OtelEnvKey | EdotEnvKey} T
* Returns the value of the env var already parsed to the proper type. If
* the variable is not defined it will return the default value based on
* the environmment variables spec https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
* @param {T} name
* @returns {EnvValue<T>}
*/
function getEnvVar(name) {
if (name in otelEnv) {
// @ts-ignore -- T is {keyof OtelEnv} but not sure how to make TS infer that
return otelEnv[name];
}

// @ts-ignore -- T is {keyof EdotEnv} but not sure how to make TS infer that
return edotEnv[name];
}

module.exports = {
setupEnvironment,
restoreEnvironment,
getEnvVar,
};
18 changes: 11 additions & 7 deletions packages/opentelemetry-node/lib/instrumentations.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
* SPDX-License-Identifier: Apache-2.0
*/

const {
getStringListFromEnv,
getBooleanFromEnv,
} = require('@opentelemetry/core');
const {log} = require('./logging');

/**
* @typedef {import('@opentelemetry/instrumentation').Instrumentation} Instrumentation
*
Expand Down Expand Up @@ -95,9 +101,6 @@ const {TediousInstrumentation} = require('@opentelemetry/instrumentation-tedious
const {UndiciInstrumentation} = require('@opentelemetry/instrumentation-undici');
const {WinstonInstrumentation} = require('@opentelemetry/instrumentation-winston');

const {log} = require('./logging');
const {getEnvVar} = require('./environment');

// Instrumentations attach their Hook (for require-in-the-middle or import-in-the-middle)
// when the `enable` method is called and this happens inside their constructor
// https://github.com/open-telemetry/opentelemetry-js/blob/1b4999f386e0240b7f65350e8360ccc2930b0fe6/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts#L71
Expand All @@ -112,7 +115,7 @@ const instrumentationsMap = {
'@opentelemetry/instrumentation-aws-sdk': (cfg) => new AwsInstrumentation(cfg),
'@opentelemetry/instrumentation-bunyan': (cfg) => new BunyanInstrumentation(cfg),
'@opentelemetry/instrumentation-connect': (cfg) => new ConnectInstrumentation(cfg),
'@opentelemetry/instrumentation-cassandra-driver': (cfg) => new CassandraDriverInstrumentation(cfg),
'@opentelemetry/instrumentation-cassandra-driver': (cfg) => new CassandraDriverInstrumentation(cfg),
'@opentelemetry/instrumentation-cucumber': (cfg) => new CucumberInstrumentation(cfg),
'@opentelemetry/instrumentation-dataloader': (cfg) => new DataloaderInstrumentation(cfg),
'@opentelemetry/instrumentation-dns': (cfg) => new DnsInstrumentation(cfg),
Expand Down Expand Up @@ -179,9 +182,9 @@ for (const name of Object.keys(instrumentationsMap)) {
* @returns {Array<string> | undefined}
*/
function getInstrumentationsFromEnv(envvar) {
if (process.env[envvar]) {
const names = getStringListFromEnv(envvar);
if (names) {
const instrumentations = [];
const names = process.env[envvar].split(',').map((s) => s.trim());

for (const name of names) {
if (otelInstrShortNames.has(name)) {
Expand Down Expand Up @@ -267,7 +270,8 @@ function getInstrumentations(opts = {}) {
}

// Skip if metrics are disabled by env var
const isMetricsDisabled = getEnvVar('ELASTIC_OTEL_METRICS_DISABLED');
const isMetricsDisabled =
getBooleanFromEnv('ELASTIC_OTEL_METRICS_DISABLED') ?? false;
if (
isMetricsDisabled &&
name === '@opentelemetry/instrumentation-runtime-node'
Expand Down
Loading
Loading