From 7032479fa7b62558e822adba471c2fb8a7d9ad70 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Thu, 4 Jun 2026 12:02:38 -0700 Subject: [PATCH 1/8] fix(sdk-node): fail-fast on LoggerProvider creation from config This PR includes a few changes to declarative config "create()" handling in startNodeSDK(). I've started with just the config handling of `logger_provider` to have a smaller start and smaller diff for review. First, this *starts* a move to "fail fast" semantics: If there is a problem creating SDK components from the parsed config, then `create()` throws, and startNodeSDK() warns and returns a no-op SDK. This matches OTel Java behaviour and the spec. From https://opentelemetry.io/docs/specs/otel/configuration/sdk/#create > This SHOULD return an error if it encounters an error in configuration (i.e. fail fast) in accordance with initialization error handling principles. Second, I'm proposing a naming pattern: createFromConfig() # e.g., createLogRecordExporterFromConfig for the suite of functions that create an SDK thing from configuration data. "create" because these are all used by the SDK configuration "create()" step (https://opentelemetry.io/docs/specs/otel/configuration/sdk/#create). "FromConfig" because it is shorter than "FromConfiguration", and these functions don't always take a full `ConfigurationModel`. Currently we have these patterns: get # getLogRecordExporter getFromConfiguration # getSpanProcessorsFromConfiguration buildFromConfig # buildSamplerFromConfig Third, this fixes missing handling of `logger_provider.limits`, which were previously not being passed to the new LoggerProvider(...). Last, I've added a `checkConfigUse(...)` internal function that can help warn about unhandled config properties in `createFromConfig()` functions. It is pretty low tech. With this config: ```yaml logger_provider: logger_configurator/development: default_config: enabled: true processors: - simple: exporter: console: ``` Before: this would silently ignore "logger_configurator/development". After: it diag.warn's ``` Config warning: some specified LoggerProvider configuration properties were not handled by SDK setup: ["logger_configurator/development"] ``` --- .../packages/configuration/src/index.ts | 9 + .../opentelemetry-sdk-node/src/start.ts | 135 ++++++---- .../opentelemetry-sdk-node/src/types.ts | 2 +- .../opentelemetry-sdk-node/src/utils.ts | 254 +++++++++++++----- .../opentelemetry-sdk-node/test/start.test.ts | 10 +- 5 files changed, 282 insertions(+), 128 deletions(-) diff --git a/experimental/packages/configuration/src/index.ts b/experimental/packages/configuration/src/index.ts index 203f21192b1..0241d5d805b 100644 --- a/experimental/packages/configuration/src/index.ts +++ b/experimental/packages/configuration/src/index.ts @@ -18,5 +18,14 @@ export type { HttpTls as HttpTlsConfigModel, GrpcTls as GrpcTlsConfigModel, SeverityNumber as SeverityNumberConfigModel, + + LoggerProvider as LoggerProviderConfigModel, + AttributeLimits as AttributeLimitsConfigModel, + LogRecordProcessor as LogRecordProcessorConfigModel, + BatchLogRecordProcessor as BatchLogRecordProcessorConfigModel, + SimpleLogRecordProcessor as SimpleLogRecordProcessorConfigModel, + OtlpHttpExporter as OtlpHttpExporterConfigModel, + OtlpGrpcExporter as OtlpGrpcExporterConfigModel, + LogRecordLimits as LogRecordLimitsConfigModel, } from './generated/types'; export { createConfigFactory } from './ConfigFactory'; diff --git a/experimental/packages/opentelemetry-sdk-node/src/start.ts b/experimental/packages/opentelemetry-sdk-node/src/start.ts index deedd6f845e..aa98b896d2a 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/start.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/start.ts @@ -17,7 +17,7 @@ import { } from '@opentelemetry/api'; import { getInstanceID, - getLogRecordProcessorsFromConfiguration, + createLoggerProviderFromConfig, getMeterReadersFromConfiguration, getMeterViewsFromConfiguration, getPropagatorFromConfiguration, @@ -28,7 +28,6 @@ import { } from './utils'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import type { SDKComponents, SDKOptions } from './types'; -import { LoggerProvider } from '@opentelemetry/sdk-logs'; import { MeterProvider } from '@opentelemetry/sdk-metrics'; import { logs } from '@opentelemetry/api-logs'; import type { @@ -83,8 +82,16 @@ export function startNodeSDK(sdkOptions?: SDKOptions): { instrumentations: sdkOptions?.instrumentations?.flat() ?? [], }); - const components = create(config, sdkOptions); - context.setGlobalContextManager(components.contextManager); + let components: SDKComponents; + try { + components = create(config, sdkOptions); + } catch (createErr) { + diag.error(`Could not create OpenTelemetry SDK: ${createErr.message}`); + return NOOP_SDK; + } + if (components.contextManager) { + context.setGlobalContextManager(components.contextManager); + } if (components.loggerProvider) { logs.setGlobalLoggerProvider(components.loggerProvider); } @@ -121,63 +128,77 @@ function create( config: ConfigurationModel, sdkOptions?: SDKOptions ): SDKComponents { - const defaultContextManager = new AsyncLocalStorageContextManager(); - defaultContextManager.enable(); - const components: SDKComponents = { - contextManager: defaultContextManager, - }; - const resource = setupResource(config, sdkOptions); - - const propagator = - sdkOptions?.textMapPropagator === null - ? null - : (sdkOptions?.textMapPropagator ?? - getPropagatorFromConfiguration(config)); - if (propagator) { - components.propagator = propagator; - } + const components: SDKComponents = {}; - const logProcessors = getLogRecordProcessorsFromConfiguration(config); - if (logProcessors) { - const loggerProvider = new LoggerProvider({ - resource: resource, - processors: logProcessors, - }); - components.loggerProvider = loggerProvider; - } + try { + components.contextManager = new AsyncLocalStorageContextManager(); + components.contextManager.enable(); + + const resource = setupResource(config, sdkOptions); + + const propagator = + sdkOptions?.textMapPropagator === null + ? null + : (sdkOptions?.textMapPropagator ?? + getPropagatorFromConfiguration(config)); + if (propagator) { + components.propagator = propagator; + } - const meterReaders = getMeterReadersFromConfiguration(config); - if (meterReaders) { - const meterViews = getMeterViewsFromConfiguration(config); - const meterProvider = new MeterProvider({ - resource: resource, - readers: meterReaders, - views: meterViews ?? [], - }); - components.meterProvider = meterProvider; - } + if (config.logger_provider) { + components.loggerProvider = createLoggerProviderFromConfig( + resource, + config.logger_provider, + config.attribute_limits + ); + } - const spanProcessors = getSpanProcessorsFromConfiguration(config); - if (spanProcessors) { - const spanLimits = getSpanLimitsFromConfiguration(config); - // TODO (6506): support sampler configuration from config - const tracerProvider = new BasicTracerProvider({ - resource, - spanProcessors, - spanLimits, - generalLimits: { - attributeValueLengthLimit: - config.attribute_limits?.attribute_value_length_limit ?? undefined, - attributeCountLimit: - config.attribute_limits?.attribute_count_limit ?? undefined, - }, - // TODO (6616): support idGenerator configuration from config - // TODO (6624): support for `meterProvider: components.meterProvider` - }); - components.tracerProvider = tracerProvider; - } + const meterReaders = getMeterReadersFromConfiguration(config); + if (meterReaders) { + const meterViews = getMeterViewsFromConfiguration(config); + const meterProvider = new MeterProvider({ + resource: resource, + readers: meterReaders, + views: meterViews ?? [], + }); + components.meterProvider = meterProvider; + } + + const spanProcessors = getSpanProcessorsFromConfiguration(config); + if (spanProcessors) { + const spanLimits = getSpanLimitsFromConfiguration(config); + // TODO (6506): support sampler configuration from config + const tracerProvider = new BasicTracerProvider({ + resource, + spanProcessors, + spanLimits, + generalLimits: { + attributeValueLengthLimit: + config.attribute_limits?.attribute_value_length_limit ?? undefined, + attributeCountLimit: + config.attribute_limits?.attribute_count_limit ?? undefined, + }, + // TODO (6616): support idGenerator configuration from config + // TODO (6624): support for `meterProvider: components.meterProvider` + }); + components.tracerProvider = tracerProvider; + } - return components; + return components; + } catch (createErr) { + // Clean up any SDK components that were created before the error. + if (components.loggerProvider) { + void components.loggerProvider.shutdown(); + } + if (components.meterProvider) { + void components.meterProvider.shutdown(); + } + if (components.tracerProvider) { + void components.tracerProvider.shutdown(); + } + + throw createErr; + } } export function setupResource( diff --git a/experimental/packages/opentelemetry-sdk-node/src/types.ts b/experimental/packages/opentelemetry-sdk-node/src/types.ts index 41a3ce9fe9e..5d25964764e 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/types.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/types.ts @@ -55,7 +55,7 @@ export interface SDKOptions { } export interface SDKComponents { - contextManager: ContextManager; + contextManager?: ContextManager; loggerProvider?: LoggerProvider; meterProvider?: MeterProvider; tracerProvider?: BasicTracerProvider; diff --git a/experimental/packages/opentelemetry-sdk-node/src/utils.ts b/experimental/packages/opentelemetry-sdk-node/src/utils.ts index 7b4e9ee86aa..5be500f59cc 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/utils.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/utils.ts @@ -68,6 +68,14 @@ import type { NameStringValuePairConfigModel, HttpTlsConfigModel, GrpcTlsConfigModel, + AttributeLimitsConfigModel, + BatchLogRecordProcessorConfigModel, + LoggerProviderConfigModel, + LogRecordProcessorConfigModel, + OtlpGrpcExporterConfigModel, + OtlpHttpExporterConfigModel, + SimpleLogRecordProcessorConfigModel, + LogRecordLimitsConfigModel, } from '@opentelemetry/configuration'; import type { AggregationOption, @@ -92,11 +100,13 @@ import type { LogRecordExporter, LoggerProviderOptions, LogRecordProcessor, + LogRecordLimits, } from '@opentelemetry/sdk-logs'; import { BatchLogRecordProcessor, ConsoleLogRecordExporter, SimpleLogRecordProcessor, + LoggerProvider, } from '@opentelemetry/sdk-logs'; import * as fs from 'fs'; @@ -615,81 +625,127 @@ export function getBatchLogRecordProcessorFromEnv( ); } -export function getLogRecordExporter( +function createLogRecordLimitsFromConfig( + limits?: LogRecordLimitsConfigModel, + attribute_limits?: AttributeLimitsConfigModel +): LogRecordLimits { + return { + attributeValueLengthLimit: + limits?.attribute_value_length_limit ?? + attribute_limits?.attribute_value_length_limit ?? + undefined, + attributeCountLimit: + limits?.attribute_count_limit ?? + attribute_limits?.attribute_count_limit ?? + undefined, + }; +} + +export function createLoggerProviderFromConfig( + resource: Resource, + logger_provider: LoggerProviderConfigModel, + attribute_limits?: AttributeLimitsConfigModel +): LoggerProvider { + const processors = logger_provider.processors.map(p => + createLogRecordProcessorFromConfig(p) + ); + const logRecordLimits = createLogRecordLimitsFromConfig( + logger_provider.limits, + attribute_limits + ); + checkConfigUse('LoggerProvider', logger_provider, ['processors', 'limits']); + + return new LoggerProvider({ + resource, + processors, + logRecordLimits, + // TODO: loggerConfigurator + // TODO: meterProvider + // Note: forceFlushTimeoutMillis not configurable via decl conf. + }); +} + +export function createLogRecordExporterFromConfig( exporter: LogRecordExporterConfigModel -): LogRecordExporter | undefined { - if (exporter.otlp_http !== undefined) { - const cfg = exporter.otlp_http; - const commonOpts = { - compression: - cfg?.compression === 'gzip' - ? CompressionAlgorithm.GZIP - : CompressionAlgorithm.NONE, - url: cfg?.endpoint ?? undefined, - headers: getHeadersFromConfiguration(cfg?.headers), - timeoutMillis: validateExporterTimeout(cfg?.timeout), - httpAgentOptions: getHttpAgentOptionsFromTls(cfg?.tls), - }; - const encoding = cfg?.encoding ?? 'protobuf'; - if (encoding === 'json') { - return new OTLPHttpLogExporter(commonOpts); +): LogRecordExporter { + const [name, properties] = mustSingleEntry(exporter, 'LogRecordExporter'); + + switch (name) { + case 'otlp_http': { + const props = properties as OtlpHttpExporterConfigModel; + const commonOpts = { + compression: + props?.compression === 'gzip' + ? CompressionAlgorithm.GZIP + : CompressionAlgorithm.NONE, + url: props?.endpoint ?? undefined, + headers: getHeadersFromConfiguration(props?.headers), + timeoutMillis: validateExporterTimeout(props?.timeout), + httpAgentOptions: getHttpAgentOptionsFromTls(props?.tls), + }; + const encoding = props?.encoding ?? 'protobuf'; + switch (encoding) { + case 'json': + return new OTLPHttpLogExporter(commonOpts); + case 'protobuf': + return new OTLPProtoLogExporter(commonOpts); + default: + throw new Error( + `unknown OtlpHttpExporter encoding in configuration: "${encoding}"` + ); + } } - if (encoding === 'protobuf') { - return new OTLPProtoLogExporter(commonOpts); + + case 'otlp_grpc': { + const props = properties as OtlpGrpcExporterConfigModel; + return new OTLPGrpcLogExporter({ + compression: + props?.compression === 'gzip' + ? CompressionAlgorithm.GZIP + : CompressionAlgorithm.NONE, + url: props?.endpoint ?? undefined, + timeoutMillis: validateExporterTimeout(props?.timeout), + credentials: getGrpcCredentialsFromTls(props?.tls), + metadata: getGrpcMetadataFromHeaders(props?.headers), + }); } - diag.warn( - `Unsupported OTLP logs encoding: ${encoding}. Using http/protobuf.` - ); - return new OTLPProtoLogExporter(commonOpts); - } else if (exporter.otlp_grpc !== undefined) { - const cfg = exporter.otlp_grpc; - return new OTLPGrpcLogExporter({ - compression: - cfg?.compression === 'gzip' - ? CompressionAlgorithm.GZIP - : CompressionAlgorithm.NONE, - url: cfg?.endpoint ?? undefined, - timeoutMillis: validateExporterTimeout(cfg?.timeout), - credentials: getGrpcCredentialsFromTls(cfg?.tls), - metadata: getGrpcMetadataFromHeaders(cfg?.headers), - }); - } else if (exporter.console !== undefined) { - return new ConsoleLogRecordExporter(); + + case 'console': + return new ConsoleLogRecordExporter(); + + default: + throw new Error( + `unknown LogRecordExporter name in configuration: "${name}"` + ); } - diag.warn('Unsupported Exporter value. No Log Record Exporter registered'); - return undefined; } -export function getLogRecordProcessorsFromConfiguration( - config: ConfigurationModel -): LogRecordProcessor[] | undefined { - const logRecordProcessors: LogRecordProcessor[] = []; - config.logger_provider?.processors?.forEach(processor => { - if (processor.batch) { - const exporter = getLogRecordExporter(processor.batch.exporter); - if (exporter) { - logRecordProcessors.push( - new BatchLogRecordProcessor(exporter, { - maxQueueSize: processor.batch.max_queue_size ?? undefined, - maxExportBatchSize: - processor.batch.max_export_batch_size ?? undefined, - scheduledDelayMillis: processor.batch.schedule_delay ?? undefined, - exportTimeoutMillis: processor.batch.export_timeout ?? undefined, - }) - ); - } +export function createLogRecordProcessorFromConfig( + processor: LogRecordProcessorConfigModel +): LogRecordProcessor { + const [name, properties] = mustSingleEntry(processor, 'LogRecordProcessor'); + + switch (name) { + case 'batch': { + const props = properties as BatchLogRecordProcessorConfigModel; + const exporter = createLogRecordExporterFromConfig(props.exporter); + return new BatchLogRecordProcessor(exporter, { + maxQueueSize: props.max_queue_size ?? undefined, + maxExportBatchSize: props.max_export_batch_size ?? undefined, + scheduledDelayMillis: props.schedule_delay ?? undefined, + exportTimeoutMillis: props.export_timeout ?? undefined, + }); } - if (processor.simple) { - const exporter = getLogRecordExporter(processor.simple.exporter); - if (exporter) { - logRecordProcessors.push(new SimpleLogRecordProcessor(exporter)); - } + + case 'simple': { + const props = properties as SimpleLogRecordProcessorConfigModel; + const exporter = createLogRecordExporterFromConfig(props.exporter); + return new SimpleLogRecordProcessor(exporter); } - }); - if (logRecordProcessors.length > 0) { - return logRecordProcessors; + + default: + throw new Error(`unknown LogRecordProcessor name: "${name}"`); } - return undefined; } export function getHeadersFromConfiguration( @@ -1120,3 +1176,69 @@ export function buildSamplerFromConfig( diag.error('Unknown sampler config, defaulting to ParentBased(AlwaysOn).'); return new ParentBasedSampler({ root: new AlwaysOnSampler() }); } + +/** + * Warn if some props from a declarative config object have not been handled. + * + * This is intended to be used by `create*FromConfig()` functions. It is a low + * tech mechanism to add awareness when a given valid config is not being + * completely handled. This could help when properties are added to the + * configuration schema. (A higher tech mechanism that wraps the parsed + * configuration during `create()` and watches for untouched properties + * might be nice.) + */ +function checkConfigUse( + name: string, + props: object | undefined, + handledProps: string[] +) { + if (!props) return; + // Dev note: I'd use Set#difference, but that requires Node.js v22. + const unhandledProps = Object.keys(props).filter( + k => !handledProps.includes(k) + ); + + if (unhandledProps.length > 0) { + diag.warn( + `Config warning: some specified ${name} configuration properties were not handled by SDK setup: ${JSON.stringify(unhandledProps)}` + ); + } +} + +/** + * Return the single non-undefined entry in the given config object, or throw. + * + * It is common for Declarative Configuration to have config objects with + * a single entry, e.g. + * + * "LogRecordProcessor": { + * "type": "object", + * "additionalProperties": { + * "type": [ + * "object", + * "null" + * ] + * }, + * "minProperties": 1, + * "maxProperties": 1, + * + * The TypeScript types cannot express the minProperties/maxProperties from the + * JSON schema. We guard against that here. + */ +function mustSingleEntry( + configObj: Record, + configTypeName: string +): [string, unknown] { + const entries = Object.entries(configObj).filter( + ([_name, properties]) => properties !== undefined + ); + + if (entries.length !== 1) { + const entryNames = entries.map(e => e[0]); + throw Error( + `invalid ${configTypeName} in configuration: must have exactly one entry: entries=${JSON.stringify(entryNames)}` + ); + } + + return entries[0]; +} diff --git a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts index d23729a6a78..d7728d1407e 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts @@ -56,7 +56,7 @@ import { } from '../src/semconv'; import { ATTR_OS_TYPE } from '@opentelemetry/resources/src/semconv'; import { - getLogRecordExporter, + createLogRecordExporterFromConfig, getSpanExporter, setupContextManager, } from '../src/utils'; @@ -958,9 +958,11 @@ describe('startNodeSDK', function () { }); describe('tests to increase code coverage', function () { - it('should return undefined for invalid log record exporter model', async () => { - const exporter: LogRecordExporterConfigModel = {}; - assert.equal(getLogRecordExporter(exporter), undefined); + it('should throw for invalid log record exporter model', async () => { + assert.throws(() => { + const exporter: LogRecordExporterConfigModel = {}; + createLogRecordExporterFromConfig(exporter); + }, 'XXX'); }); it('should warn when exporter timeout is 0', async () => { From 3c3626806063c0db0f06c35920716ff83da60ad3 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Thu, 4 Jun 2026 12:35:53 -0700 Subject: [PATCH 2/8] add changelog entry --- experimental/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index f2720659b51..84fd7287474 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -28,6 +28,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2 * fix(browser-detector): user agent resource attribute always [#6754](https://github.com/open-telemetry/opentelemetry-js/pull/6754) @david-luna * fix(opentelemetry-exporter-prometheus): handle additional edge cases in metric name conversion [#6727](https://github.com/open-telemetry/opentelemetry-js/pull/6727) @cjihrig * fix(sdk-logs): avoid null dereference in `BatchLogRecordProcessor._flushAll` when an in-flight export completes between awaits [#6763](https://github.com/open-telemetry/opentelemetry-js/pull/6763) @Janealter +* fix(sdk-node): fail-fast on LoggerProvider creation with startNodeSDK() and declarative config [#6785](https://github.com/open-telemetry/opentelemetry-js/pull/6785) @trentm ### :books: Documentation From aadf86daf3b30c9718f0145133a5f1378ae2c500 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Thu, 4 Jun 2026 12:37:11 -0700 Subject: [PATCH 3/8] lint:fix --- experimental/packages/configuration/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/experimental/packages/configuration/src/index.ts b/experimental/packages/configuration/src/index.ts index 0241d5d805b..f34bb61effe 100644 --- a/experimental/packages/configuration/src/index.ts +++ b/experimental/packages/configuration/src/index.ts @@ -18,7 +18,6 @@ export type { HttpTls as HttpTlsConfigModel, GrpcTls as GrpcTlsConfigModel, SeverityNumber as SeverityNumberConfigModel, - LoggerProvider as LoggerProviderConfigModel, AttributeLimits as AttributeLimitsConfigModel, LogRecordProcessor as LogRecordProcessorConfigModel, From 68bfbd09049260d1a2901cd413fd5c35505e78d3 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Thu, 4 Jun 2026 13:56:56 -0700 Subject: [PATCH 4/8] drop debugging comment/message --- experimental/packages/opentelemetry-sdk-node/test/start.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts index d7728d1407e..8cb9152269c 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts @@ -962,7 +962,7 @@ describe('startNodeSDK', function () { assert.throws(() => { const exporter: LogRecordExporterConfigModel = {}; createLogRecordExporterFromConfig(exporter); - }, 'XXX'); + }); }); it('should warn when exporter timeout is 0', async () => { From eff328bdd0a46635232d89a281081cca21a441df Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Thu, 4 Jun 2026 14:16:09 -0700 Subject: [PATCH 5/8] add a test case for the create() throwing --- .../fixtures/unknown-log-record-processor.yaml | 4 ++++ .../opentelemetry-sdk-node/test/start.test.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 experimental/packages/opentelemetry-sdk-node/test/fixtures/unknown-log-record-processor.yaml diff --git a/experimental/packages/opentelemetry-sdk-node/test/fixtures/unknown-log-record-processor.yaml b/experimental/packages/opentelemetry-sdk-node/test/fixtures/unknown-log-record-processor.yaml new file mode 100644 index 00000000000..45969da6d7c --- /dev/null +++ b/experimental/packages/opentelemetry-sdk-node/test/fixtures/unknown-log-record-processor.yaml @@ -0,0 +1,4 @@ +file_format: "1.0" +logger_provider: + processors: + - my_custom_processor: diff --git a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts index 8cb9152269c..6147974cbec 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts @@ -254,6 +254,21 @@ describe('startNodeSDK', function () { await sdk.shutdown(); }); + it('should diag.error and return NOOP_SDK when components in OTEL_CONFIG_FILE cannot be created', async () => { + const diagError = Sinon.spy(diag, 'error'); + process.env.OTEL_CONFIG_FILE = 'test/fixtures/unknown-log-record-processor.yaml'; + const sdk = startNodeSDK({}); + + assert.strictEqual(sdk, NOOP_SDK); + assert.strictEqual(diagError.callCount, 1); + assert.strictEqual( + diagError.args[0][0], + 'Could not create OpenTelemetry SDK: unknown LogRecordProcessor name: "my_custom_processor"' + ); + + await sdk.shutdown(); + }); + it('should register a logger provider if multiple log record processors are provided', async () => { process.env.OTEL_CONFIG_FILE = 'test/fixtures/logger.yaml'; const sdk = startNodeSDK({}); From fee5125573b402e7ee0920730451bf076f6ac50c Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Fri, 5 Jun 2026 10:03:45 -0700 Subject: [PATCH 6/8] lint:fix (prettier) --- .../packages/opentelemetry-sdk-node/test/start.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts index 6147974cbec..fe7c955987c 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/start.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/start.test.ts @@ -256,7 +256,8 @@ describe('startNodeSDK', function () { it('should diag.error and return NOOP_SDK when components in OTEL_CONFIG_FILE cannot be created', async () => { const diagError = Sinon.spy(diag, 'error'); - process.env.OTEL_CONFIG_FILE = 'test/fixtures/unknown-log-record-processor.yaml'; + process.env.OTEL_CONFIG_FILE = + 'test/fixtures/unknown-log-record-processor.yaml'; const sdk = startNodeSDK({}); assert.strictEqual(sdk, NOOP_SDK); From b2f95ae8118c94e1cf6b4d884eeaa064efaa0e66 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 15 Jun 2026 13:52:14 -0700 Subject: [PATCH 7/8] move CHANGELOG entry up, since there has been a release --- experimental/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 94427a123a7..ba2bcc4b58b 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -15,6 +15,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2 * fix(configuration): percent-decode keys and values in `resource.attributes_list` per spec [#6787](https://github.com/open-telemetry/opentelemetry-js/pull/6787) @MikeGoldsmith * fix(sdk-node): apply spec-defined `schedule_delay: 1000` default for BatchLogRecordProcessor from declarative config (SDK defaults to 5000) [#6788](https://github.com/open-telemetry/opentelemetry-js/pull/6788) @MikeGoldsmith * fix(configuration): default `log_level` to `info` in env-based config initialization for consistency with file-based config [#6788](https://github.com/open-telemetry/opentelemetry-js/pull/6788) @MikeGoldsmith +* fix(sdk-node): fail-fast on LoggerProvider creation with startNodeSDK() and declarative config [#6785](https://github.com/open-telemetry/opentelemetry-js/pull/6785) @trentm ### :books: Documentation @@ -47,7 +48,6 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2 * fix(opentelemetry-exporter-prometheus): handle additional edge cases in metric name conversion [#6727](https://github.com/open-telemetry/opentelemetry-js/pull/6727) @cjihrig * fix(sdk-logs): avoid null dereference in `BatchLogRecordProcessor._flushAll` when an in-flight export completes between awaits [#6763](https://github.com/open-telemetry/opentelemetry-js/pull/6763) @Janealter * fix(configuration): improve environment variable substitution to handle all the cases shown in the spec [#6757](https://github.com/open-telemetry/opentelemetry-js/pull/6757) @trentm -* fix(sdk-node): fail-fast on LoggerProvider creation with startNodeSDK() and declarative config [#6785](https://github.com/open-telemetry/opentelemetry-js/pull/6785) @trentm ### :books: Documentation From 74585201b59959a380b7dfb160462b6ddc40d3f6 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Wed, 17 Jun 2026 11:21:19 -0700 Subject: [PATCH 8/8] fix a 'new BatchLogRecordProcessor' usage from this PR to the new call signature --- experimental/packages/opentelemetry-sdk-node/src/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/experimental/packages/opentelemetry-sdk-node/src/utils.ts b/experimental/packages/opentelemetry-sdk-node/src/utils.ts index e2f655672dd..c4f3e24b620 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/utils.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/utils.ts @@ -767,7 +767,8 @@ export function createLogRecordProcessorFromConfig( case 'batch': { const props = properties as BatchLogRecordProcessorConfigModel; const exporter = createLogRecordExporterFromConfig(props.exporter); - return new BatchLogRecordProcessor(exporter, { + return new BatchLogRecordProcessor({ + exporter, maxQueueSize: props.max_queue_size ?? undefined, maxExportBatchSize: props.max_export_batch_size ?? undefined, scheduledDelayMillis: props.schedule_delay ?? undefined,