opentelemetry-metrics-sdk 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58da416a1cf5a0613371e4b5541a87c5464ff0aa912cd4dc3705ca727a40bef0
4
- data.tar.gz: 2e72844bdc836231c7e90c44b0cea11f99b135436deae9d36061285f07e42064
3
+ metadata.gz: aaa97b8e63eb4737a973f36e8861a9884e7eccba6b3df0355b9074769a5d89c3
4
+ data.tar.gz: 70fb46dfb922370d54a647e37b80608f6f2d237979cd9a7fe4d0c40cf7132aea
5
5
  SHA512:
6
- metadata.gz: 1e37720e573b016a2a325857a2bf4f809e5bdf1491e80ca635286b93da4b48983db55d2d7433f30be811b3ea469ca556741a6c50e21a262a2a73e0840bf200ee
7
- data.tar.gz: 9a254eb93239942638cc6cad95afec72627dc35b280f8e9893e5d6f3ae3d399ffe78f0a77cae9161812d7afe61ab0129be8d91199a803d7f3680862cc4a32180
6
+ metadata.gz: 673c6ce995204f0cba090c12cd7e841d75dd4766ffb44dee8d9690b938774c4889fd0c562f798281bc961085354d0146f983da55419499f683773c3f9a33071e
7
+ data.tar.gz: 510c0413cd0c921193a068a9184c2ddb8f3c1618512fe6c7c88b010486a7fadc2c74ce37b1020fa63b94bc399c26428bbadfb0e7cf5050916937179d04e30c98
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Release History: opentelemetry-metrics-sdk
2
2
 
3
+ ### v0.21.0 / 2026-10-13
4
+
5
+ * ADDED: Fix duplicate instrument registration warning (#2417)
6
+
3
7
  ### v0.20.0 / 2026-10-05
4
8
 
5
9
  * BREAKING CHANGE: Move the global meter provider behind an opt-in require (#2416)
@@ -13,6 +13,10 @@ module OpenTelemetry
13
13
  class Meter < OpenTelemetry::Metrics::Meter
14
14
  NAME_REGEX = %r{\A[a-zA-Z][-./\w]{0,254}\z}
15
15
 
16
+ # Identifying fields of a created instrument, used to detect duplicate registrations.
17
+ InstrumentDescriptor = Struct.new(:name, :kind, :unit, :description, :instrument)
18
+ private_constant(:InstrumentDescriptor)
19
+
16
20
  # @api private
17
21
  #
18
22
  # Returns a new {Meter} instance.
@@ -25,7 +29,8 @@ module OpenTelemetry
25
29
  # @return [Meter]
26
30
  def initialize(name, version, meter_provider, attributes: nil)
27
31
  @mutex = Mutex.new
28
- @instrument_registry = {}
32
+ # Every built instrument, including conflicting duplicates, keyed by downcased name.
33
+ @instrument_descriptors = {}
29
34
  @instrumentation_scope = InstrumentationScope.new(name, version, attributes || {}.freeze)
30
35
  @meter_provider = meter_provider
31
36
  end
@@ -56,34 +61,97 @@ module OpenTelemetry
56
61
  end
57
62
 
58
63
  # @api private
64
+ #
65
+ # Not synchronized on @mutex: create_instrument holds it while waiting on the
66
+ # meter provider's mutex, which the caller of this method already holds.
67
+ # Iterates a snapshot so a concurrent registration cannot mutate the hash mid-iteration.
59
68
  def add_metric_reader(metric_reader)
60
- @instrument_registry.each_value do |instrument|
61
- instrument.register_with_new_metric_store(metric_reader.metric_store)
69
+ @instrument_descriptors.values.flatten.each do |descriptor|
70
+ descriptor.instrument.register_with_new_metric_store(metric_reader.metric_store)
62
71
  end
63
72
  end
64
73
 
65
74
  # Validates the given instrument options and creates the instrument of the given kind.
66
75
  # TODO: advisory parameters are accepted but not yet applied to stream configuration.
67
76
  def create_instrument(kind, name, unit, description, callback, exemplar_filter, exemplar_reservoir, advisory)
68
- raise InstrumentNameError if name.nil?
69
- raise InstrumentNameError if name.empty?
70
- raise InstrumentNameError unless NAME_REGEX.match?(name)
77
+ raise InstrumentNameError if invalid_name?(name)
71
78
 
72
79
  unit ||= ''
73
80
  description ||= ''
74
81
 
75
- super do
76
- case kind
77
- when :counter then OpenTelemetry::SDK::Metrics::Instrument::Counter.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
78
- when :observable_counter then OpenTelemetry::SDK::Metrics::Instrument::ObservableCounter.new(name, unit, description, callback, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
79
- when :gauge then OpenTelemetry::SDK::Metrics::Instrument::Gauge.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
80
- when :histogram then OpenTelemetry::SDK::Metrics::Instrument::Histogram.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
81
- when :observable_gauge then OpenTelemetry::SDK::Metrics::Instrument::ObservableGauge.new(name, unit, description, callback, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
82
- when :up_down_counter then OpenTelemetry::SDK::Metrics::Instrument::UpDownCounter.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
83
- when :observable_up_down_counter then OpenTelemetry::SDK::Metrics::Instrument::ObservableUpDownCounter.new(name, unit, description, callback, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
82
+ @mutex.synchronize do
83
+ # Read without inserting; the key is only added once an instrument is built.
84
+ descriptors = @instrument_descriptors.fetch(name.downcase, [])
85
+
86
+ # Instrument names are case-insensitive, so use the first-seen casing.
87
+ # if first_seen is count, name is Count, then name become count
88
+ first_seen = descriptors.first&.name
89
+ if first_seen && first_seen != name
90
+ OpenTelemetry.logger.warn("case-insensitive duplicate instrument registration occurred:'#{name}' first seen as '#{first_seen}'")
91
+ name = first_seen
84
92
  end
93
+
94
+ identical = descriptors.find { |descriptor| identical?(descriptor, kind, unit, description) }
95
+
96
+ if identical
97
+ OpenTelemetry.logger.warn("repeated observable instrument creation with callbacks for instrument name '#{name}'. Ignoring new callbacks. Use Meter#register_callback to add callbacks.") unless Array(callback).empty?
98
+ identical.instrument
99
+ else
100
+ # Found the first conflicting instrument with the same name but different attributes (kind, unit, description).
101
+ unless descriptors.empty?
102
+ # TODO: implement the function that can determine if the duplicate registration can be resolved by a view
103
+ # (current View can't rename the instrument name and description)
104
+ OpenTelemetry.logger.warn("duplicate instrument registration occurred for instrument name '#{name}'")
105
+ warn_conflicting_fields(descriptors.first, kind, unit, description)
106
+ end
107
+
108
+ # Build and register a new instrument since (still build the instrument even though a conflicting one exists) it has different attributes.
109
+ instrument = build_instrument(kind, name, unit, description, callback, exemplar_filter, exemplar_reservoir)
110
+ # Replace rather than append so arrays already snapshotted by add_metric_reader are never mutated.
111
+ descriptors << InstrumentDescriptor.new(name, kind, unit, description, instrument)
112
+ @instrument_descriptors[name.downcase] = descriptors
113
+ instrument
114
+ end
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # Return true if name is nil/empty or not match NAME_REGEX
121
+ def invalid_name?(name)
122
+ name.to_s.empty? || !NAME_REGEX.match?(name)
123
+ end
124
+
125
+ # Returns a new SDK instrument of the given kind.
126
+ def build_instrument(kind, name, unit, description, callback, exemplar_filter, exemplar_reservoir)
127
+ case kind
128
+ when :counter then OpenTelemetry::SDK::Metrics::Instrument::Counter.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
129
+ when :observable_counter then OpenTelemetry::SDK::Metrics::Instrument::ObservableCounter.new(name, unit, description, callback, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
130
+ when :gauge then OpenTelemetry::SDK::Metrics::Instrument::Gauge.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
131
+ when :histogram then OpenTelemetry::SDK::Metrics::Instrument::Histogram.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
132
+ when :observable_gauge then OpenTelemetry::SDK::Metrics::Instrument::ObservableGauge.new(name, unit, description, callback, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
133
+ when :up_down_counter then OpenTelemetry::SDK::Metrics::Instrument::UpDownCounter.new(name, unit, description, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
134
+ when :observable_up_down_counter then OpenTelemetry::SDK::Metrics::Instrument::ObservableUpDownCounter.new(name, unit, description, callback, @instrumentation_scope, @meter_provider, exemplar_filter, exemplar_reservoir)
85
135
  end
86
136
  end
137
+
138
+ # Returns whether an existing registration has the same identifying fields.
139
+ def identical?(descriptor, kind, unit, description)
140
+ descriptor.kind == kind &&
141
+ descriptor.unit.to_s == unit.to_s &&
142
+ descriptor.description.to_s == description.to_s
143
+ end
144
+
145
+ # Warns which identifying fields differ from the existing registration.
146
+ # The spec's View-based recipes are omitted because Views cannot rename a stream or override its description.
147
+ def warn_conflicting_fields(existing, kind, unit, description)
148
+ conflicts = []
149
+ conflicts << "kind (#{existing.kind} and #{kind})" if existing.kind != kind
150
+ conflicts << "unit (#{existing.unit.inspect} and #{unit.inspect})" if existing.unit.to_s != unit.to_s
151
+ conflicts << "description (#{existing.description.inspect} and #{description.inspect})" if existing.description.to_s != description.to_s
152
+
153
+ OpenTelemetry.logger.warn("conflicting fields: #{conflicts.join(', ')}")
154
+ end
87
155
  end
88
156
  end
89
157
  end
@@ -8,7 +8,7 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Metrics
10
10
  # Current OpenTelemetry metrics sdk version
11
- VERSION = '0.20.0'
11
+ VERSION = '0.21.0'
12
12
  end
13
13
  end
14
14
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-metrics-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTelemetry Authors
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.9.0
32
+ version: 0.10.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.9.0
39
+ version: 0.10.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: opentelemetry-sdk
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -122,10 +122,10 @@ homepage: https://github.com/open-telemetry/opentelemetry-ruby
122
122
  licenses:
123
123
  - Apache-2.0
124
124
  metadata:
125
- changelog_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.20.0/file/CHANGELOG.md
126
- source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-metrics-sdk/v0.20.0/metrics_sdk
125
+ changelog_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.21.0/file/CHANGELOG.md
126
+ source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-metrics-sdk/v0.21.0/metrics_sdk
127
127
  bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby/issues
128
- documentation_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.20.0
128
+ documentation_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.21.0
129
129
  rdoc_options: []
130
130
  require_paths:
131
131
  - lib