Skip to content

feat(chore): declarative configuration for trace_provider - #2161

Open
xuan-cao-swi wants to merge 22 commits into
open-telemetry:mainfrom
xuan-cao-swi:otelconfig
Open

feat(chore): declarative configuration for trace_provider#2161
xuan-cao-swi wants to merge 22 commits into
open-telemetry:mainfrom
xuan-cao-swi:otelconfig

Conversation

@xuan-cao-swi

@xuan-cao-swi xuan-cao-swi commented May 25, 2026

Copy link
Copy Markdown
Contributor

Description

This PR introduces an initial declarative configuration implementation for OpenTelemetry Ruby under otelconfig, intentionally scoped to tracing only.

I followed the Go otelconfig convention for config structure and behavior where it maps well to Ruby.

Minimalist approach: no hard dependency on optional components, which optional propagators and resource detectors are resolved only if the corresponding gem has been required by the user. If an optional component is not available, configuration continues safely with warnings instead of failing startup.

Scope in this PR

  • Trace configuration only:
    • tracer provider setup
    • samplers
    • span processors
    • trace exporters
    • span limits
  • Declarative resource and propagation wiring needed for trace setup
  • Instrumentation mapping/installation flow for declarative config
  • Tests and example configuration for the implemented behavior

Try /example

Updated (2026-06-13):

  • The in-memory struct of opentelemetry configuration now is based on https://github.com/open-telemetry/opentelemetry-configuration/tree/v1.0.0-rc.3/schema. bundle exec rake generate:constants will auto-generate the struct file.
  • configuration will return RubySDK struct that contain providers, which user need to set the global provider point to the providers
    sdk = OpenTelemetry::OtelConfig.configure
    OpenTelemetry.tracer_provider = sdk.tracer_provider

@thompson-tomo thompson-tomo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One other thing, how are we ensuring the loading of the the env variables are disabled when covered by declarative config?

Will the current approach support dynamic reloading of the config which is also a part of the spec?

Comment thread otelconfig/Rakefile Outdated
Comment thread config/lib/opentelemetry/components/trace.rb
Comment thread config/lib/opentelemetry/components/trace.rb
Comment thread config/lib/opentelemetry/components/trace.rb
Comment thread otelconfig/lib/opentelemetry/otelconfig/instrumentation.rb Outdated
Comment thread config/lib/opentelemetry/components/trace.rb
Comment thread config/lib/opentelemetry/config/resource.rb
Comment thread otelconfig/opentelemetry-otelconfig.gemspec Outdated
Comment thread otelconfig/opentelemetry-otelconfig.gemspec Outdated
Comment thread otelconfig/opentelemetry-otelconfig.gemspec Outdated
@xuan-cao-swi
xuan-cao-swi marked this pull request as ready for review June 15, 2026 19:36
@xuan-cao-swi
xuan-cao-swi requested review from a team as code owners June 29, 2026 18:07

@mwear mwear left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like the approach overall. I have a few comments based on a first pass through.

Comment thread config/lib/tasks/generate_constants.rake
Comment thread config/opentelemetry-config.gemspec

private

def apply(config)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This only returns an SDK on the happy path. Other paths vary in what they return. We should define what happens on these other paths? Do we want to always return an SDK backed by no-op components or something else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now it will return noop sdk (e.g. noop provider and propagator) if parsing failed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now it will also init the otel sdk for user by replacing the global provider and propagator.

Comment thread config/lib/opentelemetry-config.rb
Comment thread otelconfig/README.md Outdated

@mwear mwear left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry, I have been slow to review the declarative config PRs. I notice we also have #21960 proposing a slightly different shape for declarative config. We should discuss further in #2127 and see if we can align on an approach.

@mwear mwear left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If the comment regarding parse, create, install is addressed, then I think this has the overall shape and behavior that we want and we can consider this as our MVP and handle additional functionality incrementally.

The constructor injection that is proposed in #2196 can be handled as an internal refactor. However, we should not allow the configuration model to leak into the SDK.

There were comments suggesting that we should have support for custom components, which I agree with in spirit. There is likely a path forward using Class#inherited to build a registry of components. However there will be startup, initialization, load-order complications that need to be worked through. The spec currently lists extensions as a SHOULD, so this does not need to be included in the MVP.

Environment variable handling is another thing we're going to have to work on, but it can also be handled separately.


class << self
# Entry point
def configure

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The specification says that there should be parse and create operations. Right now we only expose configure and confgure_from_file. configure has the right behavior, but I would prefer that we build it as suggested in #2127.

sdk = OpenTelemetry::Config.configure
# ^^ Equivalent to vv
sdk = OpenTelemetry::Config.install(
        OpenTelemetry::Config.create(
          OpenTelemetry::Config.parse(ENV['OTEL_CONFIG_FILE'])))
          
# the SDK handle ideally should be usable for shutdown
at_exit { sdk.shutdown }
  • parse should return the model, currently this is parse_config_file
  • create should return a configured RubySDK
  • install should take a RubySDK and assign it the proper globals
  • configure is a convenience method that wraps all three operations, should return a RubySDK

The current apply implementation is create + install fused. It should be split.

Not spec'd but would be nice and mirrors what Go does: the RubySDK should expose a shutdown method that fans out to the configured providers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, configure now is separated to parse, create and install.

require 'opentelemetry/otelconfig/version'

Gem::Specification.new do |spec|
spec.name = 'opentelemetry-otelconfig'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would prefer to see this named: opentelemetry-config or opentelemetry-configuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

change the folder name from otelconfig to config as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

changed folder name to config

@xuan-cao-swi

Copy link
Copy Markdown
Contributor Author

Environment variable handling is another thing we're going to have to work on, but it can also be handled separately.

I think eventually env var will be abandoned if user specifically choose to use declarative config. There are migration from env var (otel-sdk-migration-config.yaml)

There were comments suggesting that we should have support for custom components

The only concern I have is that user have to install and load their custom components first (before declarative config) then the declarative config can recognize them, which means extra steps for user.


propagators = configure_propagation(config.propagator)

configure_instrumentation(config.instrumentation_development)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

create should only construct the SDK components. Instrumentation installation should happen in install instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated: create will only construct SDK conponents and instrumentation installation will only happened in install

# @param ruby_sdk [RubySDK]
# @return [RubySDK] the same SDK handle
def install(ruby_sdk)
OpenTelemetry.tracer_provider = ruby_sdk.tracer_provider if ruby_sdk.tracer_provider

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

install should skip the no-op SDK. As written, it will set the globals to the no-op components since the returned no-op providers are truthy. This is a change in behavior from before the apply split, which left the globals as-is.

The cleanest fix is a frozen NOOP_SDK constant returned from every no-op path, with install short circuiting when ruby_sdk.equal?(NOOP_SDK).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated: every no-op path will return NOOP_SDK constant and will be skipped from install

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants