feat(chore): declarative configuration for trace_provider - #2161
feat(chore): declarative configuration for trace_provider#2161xuan-cao-swi wants to merge 22 commits into
Conversation
thompson-tomo
left a comment
There was a problem hiding this comment.
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?
mwear
left a comment
There was a problem hiding this comment.
I like the approach overall. I have a few comments based on a first pass through.
|
|
||
| private | ||
|
|
||
| def apply(config) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Now it will return noop sdk (e.g. noop provider and propagator) if parsing failed
There was a problem hiding this comment.
Now it will also init the otel sdk for user by replacing the global provider and propagator.
mwear
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 }parseshould return the model, currently this isparse_config_filecreateshould return a configuredRubySDKinstallshould take aRubySDKand assign it the proper globalsconfigureis a convenience method that wraps all three operations, should return aRubySDK
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.
There was a problem hiding this comment.
Thanks, configure now is separated to parse, create and install.
| require 'opentelemetry/otelconfig/version' | ||
|
|
||
| Gem::Specification.new do |spec| | ||
| spec.name = 'opentelemetry-otelconfig' |
There was a problem hiding this comment.
I would prefer to see this named: opentelemetry-config or opentelemetry-configuration.
There was a problem hiding this comment.
change the folder name from otelconfig to config as well?
There was a problem hiding this comment.
changed folder name to config
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)
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) |
There was a problem hiding this comment.
create should only construct the SDK components. Instrumentation installation should happen in install instead.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Updated: every no-op path will return NOOP_SDK constant and will be skipped from install
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
Try
/exampleUpdated (2026-06-13):
bundle exec rake generate:constantswill auto-generate the struct file.