Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a6d0fca
parse config file
maryliag Aug 22, 2025
f544c93
Merge branch 'main' of github.com:open-telemetry/opentelemetry-js int…
maryliag Aug 22, 2025
fcef569
update changelog
maryliag Aug 22, 2025
0f6a05a
add more tests
maryliag Aug 22, 2025
eeb69f5
Update experimental/packages/opentelemetry-configuration/src/FileConf…
maryliag Sep 16, 2025
cb2cd96
Merge branch 'main' of github.com:open-telemetry/opentelemetry-js int…
maryliag Sep 16, 2025
73fe9af
changes from feedback
maryliag Sep 16, 2025
7a1aec4
create function by paramtre type
maryliag Sep 16, 2025
2297ee9
update package lock
maryliag Sep 16, 2025
97d9013
fix disabled
maryliag Sep 16, 2025
1c3ce03
fix changelog
maryliag Sep 19, 2025
a6833d8
Merge branch 'main' of github.com:open-telemetry/opentelemetry-js int…
maryliag Sep 19, 2025
287c395
Merge branch 'main' into config-yml
maryliag Sep 19, 2025
de08a18
remove import
maryliag Sep 20, 2025
a662970
feat(sampler-composite): add experimental implementation of composite…
maryliag Sep 20, 2025
537e20c
Revert "feat(sampler-composite): add experimental implementation of c…
maryliag Sep 20, 2025
b158b36
Merge branch 'main' of github.com:open-telemetry/opentelemetry-js int…
maryliag Sep 20, 2025
ba49203
use correct type
maryliag Sep 22, 2025
0192f44
Merge branch 'main' of github.com:open-telemetry/opentelemetry-js int…
maryliag Sep 22, 2025
36051e8
Merge branch 'main' of github.com:open-telemetry/opentelemetry-js int…
maryliag Sep 22, 2025
7524d75
update test
maryliag Sep 22, 2025
d695c91
fix lint
maryliag Sep 22, 2025
dd72027
Merge branch 'main' into config-yml
maryliag Sep 23, 2025
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
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
* when using headers, the Browser exporter now prefers `fetch` over `XMLHttpRequest` if present. Sending via `XMLHttpRequest` will be removed in a future release.
* feat(opentelemetry-configuration): creation of basic ConfigProvider [#5809](https://github.com/open-telemetry/opentelemetry-js/pull/5809) @maryliag
* feat(opentelemetry-configuration): creation of basic FileConfigProvider [#5863](https://github.com/open-telemetry/opentelemetry-js/pull/5863) @maryliag
* feat(opentelemetry-configuration): Parse of Configuration File [#5875](https://github.com/open-telemetry/opentelemetry-js/pull/5875) @maryliag
* feat(sdk-node): Add support for multiple metric readers via the new `metricReaders` option in NodeSDK configuration. Users can now register multiple metric readers (e.g., Console, Prometheus) directly through the NodeSDK constructor. The old `metricReader` (singular) option is now deprecated and will show a warning if used, but remains supported for backward compatibility. Comprehensive tests and documentation have been added. [#5760](https://github.com/open-telemetry/opentelemetry-js/issues/5760)
* **Migration:**
- Before:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "2.1.0"
"@opentelemetry/core": "2.1.0",
"yaml": "^1.10.2"
},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
"@types/mocha": "10.0.10",
"@types/node": "18.6.5",
"@types/sinon": "17.0.4",
"eslint-plugin-n": "^17.23.0",
"lerna": "6.6.2",
"mocha": "11.7.2",
"nyc": "17.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* limitations under the License.
*/

import { DiagLogLevel } from '@opentelemetry/api';
import {
ConfigurationModel,
initializeDefaultConfiguration,
Expand All @@ -35,12 +34,11 @@ export class EnvironmentConfigProvider implements ConfigProvider {

constructor() {
this._config = initializeDefaultConfiguration();
this._config.disable = getBooleanFromEnv('OTEL_SDK_DISABLED');
this._config.disabled = getBooleanFromEnv('OTEL_SDK_DISABLED');

const logLevel = getStringFromEnv('OTEL_LOG_LEVEL');
const logLevel = diagLogLevelFromString(getStringFromEnv('OTEL_LOG_LEVEL'));
if (logLevel) {
this._config.log_level =
diagLogLevelFromString(logLevel) ?? DiagLogLevel.INFO;
this._config.log_level = logLevel;
}

const nodeResourceDetectors = getStringListFromEnv(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,26 @@
* limitations under the License.
*/

import { getStringFromEnv } from '@opentelemetry/core';
import { diagLogLevelFromString, getStringFromEnv } from '@opentelemetry/core';
import {
ConfigurationModel,
initializeDefaultConfiguration,
} from './configModel';
import { ConfigProvider } from './IConfigProvider';
import * as fs from 'fs';
import * as yaml from 'yaml';
import {
getBooleanFromConfigFile,
getNumberFromConfigFile,
getStringFromConfigFile,
} from './utils';

export class FileConfigProvider implements ConfigProvider {
private _config: ConfigurationModel;

constructor() {
this._config = initializeDefaultConfiguration();
parseConfigFile(this._config);
}

getInstrumentationConfig(): ConfigurationModel {
Expand All @@ -37,7 +44,10 @@ export class FileConfigProvider implements ConfigProvider {
export function hasValidConfigFile(): boolean {
const configFile = getStringFromEnv('OTEL_EXPERIMENTAL_CONFIG_FILE');
if (configFile) {
if (!configFile.endsWith('.yaml') || !fs.existsSync(configFile)) {
if (
!(configFile.endsWith('.yaml') || configFile.endsWith('.yml')) ||
!fs.existsSync(configFile)
) {
throw new Error(
`Config file ${configFile} set on OTEL_EXPERIMENTAL_CONFIG_FILE is not valid`
);
Expand All @@ -46,3 +56,62 @@ export function hasValidConfigFile(): boolean {
}
return false;
}

function parseConfigFile(config: ConfigurationModel) {
const supportedFileVersions = ['1.0-rc.1'];
const configFile = getStringFromEnv('OTEL_EXPERIMENTAL_CONFIG_FILE') || '';
const file = fs.readFileSync(configFile, 'utf8');
const parsedContent = yaml.parse(file);

if (
parsedContent['file_format'] &&
supportedFileVersions.includes(parsedContent['file_format'])
) {
const disabled = getBooleanFromConfigFile(parsedContent['disabled']);
if (disabled || disabled === false) {
config.disabled = disabled;
}

const logLevel = getNumberFromConfigFile(
diagLogLevelFromString(parsedContent['log_level'])
);
if (logLevel) {
config.log_level = logLevel;
}

const attrList = getStringFromConfigFile(
parsedContent['resource']?.['attributes_list']
);
if (attrList) {
config.resource.attributes_list = attrList;
}

const schemaUrl = getStringFromConfigFile(
parsedContent['resource']?.['schema_url']
);
if (schemaUrl) {
config.resource.schema_url = schemaUrl;
}

setResourceAttributes(config, parsedContent['resource']?.['attributes']);
} else {
throw new Error(
`Unsupported File Format: ${parsedContent['file_format']}. It must be one of the following: ${supportedFileVersions}`
);
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setResourceAttributes(config: ConfigurationModel, attributes: any[]) {
if (attributes) {
config.resource.attributes = [];
for (let i = 0; i < attributes.length; i++) {
const att = attributes[i];
config.resource.attributes.push({
name: att['name'],
value: att['value'],
type: att['type'] ?? 'string',
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface ConfigurationModel {
* Configure if the SDK is disabled or not.
* If omitted or null, false is used.
*/
disable: boolean;
disabled: boolean;

/**
* Configure the log level of the internal logger used by the SDK.
Expand All @@ -44,7 +44,7 @@ export interface ConfigurationModel {

export function initializeDefaultConfiguration(): ConfigurationModel {
const config: ConfigurationModel = {
disable: false,
disabled: false,
log_level: DiagLogLevel.INFO,
node_resource_detectors: ['all'],
resource: {},
Expand All @@ -56,7 +56,7 @@ export function initializeDefaultConfiguration(): ConfigurationModel {
export interface ConfigAttributes {
name: string;
value: string | boolean | number | string[] | boolean[] | number[];
type?:
type:
| 'string'
| 'bool'
| 'int'
Expand Down
79 changes: 79 additions & 0 deletions experimental/packages/opentelemetry-configuration/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright The OpenTelemetry Authors
*
* 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
*
* https://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.
*/
import { diag } from '@opentelemetry/api';
import { inspect } from 'util';

/**
* Retrieves a boolean value from a configuration file parameter.
* - Trims leading and trailing whitespace and ignores casing.
* - Returns `undefined` if the value is empty, unset, or contains only whitespace.
* - Returns `undefined` and a warning for values that cannot be mapped to a boolean.
*
* @param {unknown} value - The value from the config file.
* @returns {boolean} - The boolean value or `false` if the environment variable is unset empty, unset, or contains only whitespace.
*/
export function getBooleanFromConfigFile(value: unknown): boolean | undefined {
const raw = String(value)?.trim().toLowerCase();
if (raw === 'true') {
return true;
} else if (raw === 'false') {
return false;
} else if (raw == null || raw === '') {
return undefined;
} else {
diag.warn(`Unknown value ${inspect(raw)}, expected 'true' or 'false'`);
return undefined;
}
}

/**
* Retrieves a number from a configuration file parameter.
* - Returns `undefined` if the environment variable is empty, unset, or contains only whitespace.
* - Returns `undefined` and a warning if is not a number.
* - Returns a number in all other cases.
*
* @param {unknown} value - The value from the config file.
* @returns {number | undefined} - The number value or `undefined`.
*/
export function getNumberFromConfigFile(value: unknown): number | undefined {
const raw = String(value)?.trim();
if (raw == null || raw.trim() === '') {
return undefined;
}

const n = Number(raw);
if (isNaN(n)) {
diag.warn(`Unknown value ${inspect(raw)}, expected a number`);
return undefined;
}

return n;
}

/**
* Retrieves a string from a configuration file parameter.
* - Returns `undefined` if the environment variable is empty, unset, or contains only whitespace.
*
* @param {unknown} value - The value from the config file.
* @returns {string | undefined} - The string value or `undefined`.
*/
export function getStringFromConfigFile(value: unknown): string | undefined {
const raw = String(value)?.trim();
if (value == null || raw === '') {
return undefined;
}
return raw;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,69 @@ import { DiagLogLevel } from '@opentelemetry/api';
import { createConfigProvider } from '../src/ConfigProvider';

const defaultConfig: Configuration = {
disable: false,
disabled: false,
log_level: DiagLogLevel.INFO,
node_resource_detectors: ['all'],
resource: {},
};

const configFromFile: Configuration = {
disabled: false,
log_level: DiagLogLevel.DEBUG,
node_resource_detectors: ['all'],
resource: {
schema_url: 'https://opentelemetry.io/schemas/1.16.0',
attributes_list: 'service.namespace=my-namespace,service.version=1.0.0',
attributes: [
{
name: 'service.name',
value: 'unknown_service',
type: 'string',
},
{
name: 'string_key',
value: 'value',
type: 'string',
},
{
name: 'bool_key',
value: true,
type: 'bool',
},
{
name: 'int_key',
value: 1,
type: 'int',
},
{
name: 'double_key',
value: 1.1,
type: 'double',
},
{
name: 'string_array_key',
value: ['value1', 'value2'],
type: 'string_array',
},
{
name: 'bool_array_key',
value: [true, false],
type: 'bool_array',
},
{
name: 'int_array_key',
value: [1, 2],
type: 'int_array',
},
{
name: 'double_array_key',
value: [1.1, 2.2],
type: 'double_array',
},
],
},
};

describe('ConfigProvider', function () {
describe('get values from environment variables', function () {
afterEach(function () {
Expand All @@ -47,7 +104,7 @@ describe('ConfigProvider', function () {
process.env.OTEL_SDK_DISABLED = 'true';
const expectedConfig: Configuration = {
...defaultConfig,
disable: true,
disabled: true,
};
const configProvider = createConfigProvider();
assert.deepStrictEqual(
Expand Down Expand Up @@ -103,6 +160,7 @@ describe('ConfigProvider', function () {
describe('get values from config file', function () {
afterEach(function () {
delete process.env.OTEL_EXPERIMENTAL_CONFIG_FILE;
delete process.env.OTEL_NODE_RESOURCE_DETECTORS;
});

it('should initialize config with default values from valid config file', function () {
Expand All @@ -111,7 +169,7 @@ describe('ConfigProvider', function () {
const configProvider = createConfigProvider();
assert.deepStrictEqual(
configProvider.getInstrumentationConfig(),
defaultConfig
configFromFile
);
});

Expand All @@ -122,6 +180,13 @@ describe('ConfigProvider', function () {
});
});

it('should return error from invalid config file format', function () {
process.env.OTEL_EXPERIMENTAL_CONFIG_FILE = 'test/fixtures/invalid.yaml';
assert.throws(() => {
createConfigProvider();
});
});

it('should initialize config with default values with empty string for config file', function () {
process.env.OTEL_EXPERIMENTAL_CONFIG_FILE = '';
const configProvider = createConfigProvider();
Expand All @@ -139,5 +204,15 @@ describe('ConfigProvider', function () {
defaultConfig
);
});

it('should initialize config with default values from valid short config file', function () {
process.env.OTEL_EXPERIMENTAL_CONFIG_FILE =
'test/fixtures/short-config.yml';
const configProvider = createConfigProvider();
assert.deepStrictEqual(
configProvider.getInstrumentationConfig(),
defaultConfig
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
file_format: "invalid"
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ file_format: "1.0-rc.1"
disabled: false
# Configure the log level of the internal logger used by the SDK.
# If omitted, info is used.
log_level: info
log_level: debug
# Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits.
attribute_limits:
# Configure max attribute value size.
Expand Down
Loading
Loading