Skip to content

feat: add attributes support to instrumentation scope for tracers#2094

Open
robertlaurin wants to merge 2 commits intoopen-telemetry:mainfrom
robertlaurin:scope-attributes
Open

feat: add attributes support to instrumentation scope for tracers#2094
robertlaurin wants to merge 2 commits intoopen-telemetry:mainfrom
robertlaurin:scope-attributes

Conversation

@robertlaurin
Copy link
Copy Markdown
Contributor

The OpenTelemetry spec (stable as of v1.24.0) defines an attributes parameter on TracerProvider#tracer as part of the instrumentation scope. This enables instrumentations to attach metadata to the scope that produced their spans.

The tracer method now accepts both legacy positional arguments and a new keyword-based interface. When both are provided for the same parameter, keyword arguments take precedence.

# Legacy positional (unchanged, fully supported)
tracer('name', '1.0')

# New keyword interface
tracer(name: 'name', version: '1.0', attributes: { 'key' => 'value' })

Backwards compatibility across independent gem releases is preserved:

  • New API + Old SDK: ProxyTracerProvider introspects the delegate's method signature at assignment time and omits the attributes keyword when delegating to providers that do not support it.
  • Old API + New SDK: The SDK's tracer method accepts positional arguments via splat, so old callers continue to work unchanged.

API changes:

  • TracerProvider#tracer signature: (*args, name:, version:, attributes:)
  • ProxyTracerProvider stores attributes in its registry key and delegates them when the underlying provider supports it

SDK changes:

  • TracerProvider#tracer passes attributes through to Tracer
  • Tracer#initialize accepts attributes and attaches them to InstrumentationLibrary
  • InstrumentationLibrary struct gains an attributes field
  • Attributes are normalized (nil -> frozen empty hash) and frozen to prevent mutation of registry keys

@robertlaurin robertlaurin force-pushed the scope-attributes branch 2 times, most recently from 17bac28 to 6aceef6 Compare April 16, 2026 21:47
@robertlaurin robertlaurin changed the title Add attributes support to instrumentation scope for tracers feat: add attributes support to instrumentation scope for tracers Apr 16, 2026
@robertlaurin robertlaurin marked this pull request as ready for review April 16, 2026 21:58
Comment thread api/lib/opentelemetry/internal/proxy_tracer_provider.rb Outdated
Comment thread api/lib/opentelemetry/trace/tracer_provider.rb Outdated
@robertlaurin robertlaurin force-pushed the scope-attributes branch 2 times, most recently from 89a3d55 to b042e7b Compare April 17, 2026 17:20
The OpenTelemetry spec (stable as of v1.24.0) defines an `attributes`
parameter on `TracerProvider#tracer` as part of the instrumentation
scope. This enables instrumentations to attach metadata to the scope
that produced their spans.

The `tracer` method now accepts both legacy positional arguments and a
new keyword-based interface. When both are provided for the same
parameter, keyword arguments take precedence.

    # Legacy positional (unchanged, fully supported)
    tracer('name', '1.0')

    # New keyword interface
    tracer(name: 'name', version: '1.0', attributes: { 'key' => 'value' })

Backwards compatibility across independent gem releases is preserved:

- New API + Old SDK: ProxyTracerProvider introspects the delegate's
  method signature at assignment time and omits the attributes keyword
  when delegating to providers that do not support it.
- Old API + New SDK: The SDK's tracer method accepts positional
  arguments via splat, so old callers continue to work unchanged.

API changes:
- TracerProvider#tracer signature: (*args, name:, version:, attributes:)
- ProxyTracerProvider stores attributes in its registry key and
  delegates them when the underlying provider supports it

SDK changes:
- TracerProvider#tracer passes attributes through to Tracer
- Tracer#initialize accepts attributes and attaches them to
  InstrumentationScope
- InstrumentationScope struct gains an attributes field
- Attributes are normalized (nil -> frozen empty hash) and frozen
  to prevent mutation of registry keys
@robbkidd
Copy link
Copy Markdown
Member

This is on my list to review today/this week. I had just started planning a proposal to implement schema_url in API and SDK for all the signals. I think that will overlap with this PR in that both will update the method signature for TracerProvider.tracer(). (🤔 … though, in the shift to using keyword arguments, we don't really need to decide on order in signature now.)

Is "scope-level attributes" and "scope schema_url" separate enough concerns to implement in separate PRs or should we combine forces?


it 'prefers keyword arguments over positional arguments' do
tracer1 = tracer_provider.tracer('positional', '1.0')
tracer2 = tracer_provider.tracer('keyword', '2.0', name: 'keyword', version: '2.0')
Copy link
Copy Markdown
Member

@robbkidd robbkidd Apr 20, 2026

Choose a reason for hiding this comment

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

🤔

Suggested change
tracer2 = tracer_provider.tracer('keyword', '2.0', name: 'keyword', version: '2.0')
tracer2 = tracer_provider.tracer('positional', '1.0', name: 'keyword', version: '2.0')

I think these suggested tracer1 positional params given to tracer2 will properly exhibit the kw arg precedence. When all arguments are entirely different, tracer2 will always be different than tracer1.

def tracer(deprecated_name = nil, deprecated_version = nil, name: nil, version: nil, attributes: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
name ||= deprecated_name || ''
version ||= deprecated_version || ''
attributes = attributes&.dup&.freeze || EMPTY_ATTRIBUTES
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.

Hmm. We should probably do attribute validation here like we do on spans.

@robbkidd
Copy link
Copy Markdown
Member

I'm a fan of all of this. Scope attributes!

Are these intentionally out-of-scope for this PR?

  • updating exporters to use the scope-level attributes when serializing for send
  • adding scope-level attributes to meter and logger

@@ -37,21 +37,50 @@ def delegate=(provider)

@mutex.synchronize do
@delegate = provider
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.

Rather than have conditional code executed on every provider.tracer(...) call, could we conditionally wrap the provider before the mutex with a legacy provider that just drops the attributes: arg? I.e. (roughly)

class LegacyProviderWrapper
  def initialize(legacy_provider)
    @legacy_provider = legacy_provider
  end

  def tracer(name, version, attributes:)
    @legacy_provider.tracer(name, version)
  end
end

# then above

provider = LegacyProviderWrapper.new(provider) unless provider.respond_to?(:tracer) && provider.method(:tracer).parameters.any? { |_, n| n == :attributes }

This would avoid penalizing providers implementing the new signature (which should be the common case, assuming most people use the SDK provider) and avoids the conditional dispatch in the legacy case.

Conditionally wrap the delegate in a LegacyProviderWrapper that drops
the attributes: kwarg, rather than branching on every tracer(...) call.
This avoids penalizing providers implementing the new signature.
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