From a1b133d322a092dd2c67bcb99f7fc2a7d6006dfd Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:30:00 +0000 Subject: [PATCH 1/5] feat(metrics): Attribute integration-emitted metrics Co-Authored-By: Johannes Daxboeck --- sentry-ruby/lib/sentry/hub.rb | 4 +- sentry-ruby/lib/sentry/metric_event.rb | 11 +++- sentry-ruby/lib/sentry/metrics.rb | 18 +++-- .../lib/sentry/telemetry_event_buffer.rb | 66 +++++++++---------- sentry-ruby/spec/sentry/metrics_spec.rb | 39 +++++++++++ sentry-yabeda/lib/sentry/yabeda/adapter.rb | 12 ++-- .../spec/sentry/yabeda/adapter_spec.rb | 48 ++++++++++---- 7 files changed, 139 insertions(+), 59 deletions(-) diff --git a/sentry-ruby/lib/sentry/hub.rb b/sentry-ruby/lib/sentry/hub.rb index b649500d7..9e986a649 100644 --- a/sentry-ruby/lib/sentry/hub.rb +++ b/sentry-ruby/lib/sentry/hub.rb @@ -240,8 +240,9 @@ def capture_log_event(message, **options) # @param value [Numeric] the metric value # @param unit [String, nil] (optional) the metric unit # @param attributes [Hash, nil] (optional) additional attributes for the metric + # @param integration [String, Symbol, nil] (optional) registered integration emitting the metric # @return [void] - def capture_metric(name:, type:, value:, unit: nil, attributes: nil) + def capture_metric(name:, type:, value:, unit: nil, attributes: nil, integration: nil) return unless current_client metric = MetricEvent.new( @@ -250,6 +251,7 @@ def capture_metric(name:, type:, value:, unit: nil, attributes: nil) type: type, unit: unit, attributes: attributes&.dup, + sdk_meta: Sentry.integrations[integration.to_s] ) current_client.buffer_metric_event(metric, current_scope) diff --git a/sentry-ruby/lib/sentry/metric_event.rb b/sentry-ruby/lib/sentry/metric_event.rb index 0116fee76..edc250ced 100644 --- a/sentry-ruby/lib/sentry/metric_event.rb +++ b/sentry-ruby/lib/sentry/metric_event.rb @@ -6,7 +6,7 @@ module Sentry class MetricEvent include Sentry::Utils::TelemetryAttributes - attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes + attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes, :sdk_meta attr_writer :trace_id, :span_id, :attributes def initialize( @@ -14,13 +14,20 @@ def initialize( type:, value:, unit: nil, - attributes: nil + attributes: nil, + sdk_meta: nil ) @name = name @type = type @value = value @unit = unit @attributes = attributes || {} + @sdk_meta = sdk_meta || Sentry.sdk_meta + + if sdk_meta + @attributes["sentry.sdk.name"] = sdk_meta[:name] || sdk_meta["name"] + @attributes["sentry.sdk.version"] = sdk_meta[:version] || sdk_meta["version"] + end @timestamp = Sentry.utc_now @trace_id = nil diff --git a/sentry-ruby/lib/sentry/metrics.rb b/sentry-ruby/lib/sentry/metrics.rb index 5b7e53871..217975d80 100644 --- a/sentry-ruby/lib/sentry/metrics.rb +++ b/sentry-ruby/lib/sentry/metrics.rb @@ -9,15 +9,17 @@ class << self # @param name [String] the metric name # @param value [Numeric] the value to increment by (default: 1) # @param attributes [Hash, nil] additional attributes for the metric (optional) + # @param integration [String, Symbol, nil] registered integration emitting the metric (optional) # @return [void] - def count(name, value: 1, attributes: nil) + def count(name, value: 1, attributes: nil, integration: nil) return unless Sentry.initialized? Sentry.get_current_hub.capture_metric( name: name, type: :counter, value: value, - attributes: attributes + attributes: attributes, + integration: integration ) end @@ -26,8 +28,9 @@ def count(name, value: 1, attributes: nil) # @param value [Numeric] the gauge value # @param unit [String, nil] the metric unit (optional) # @param attributes [Hash, nil] additional attributes for the metric (optional) + # @param integration [String, Symbol, nil] registered integration emitting the metric (optional) # @return [void] - def gauge(name, value, unit: nil, attributes: nil) + def gauge(name, value, unit: nil, attributes: nil, integration: nil) return unless Sentry.initialized? Sentry.get_current_hub.capture_metric( @@ -35,7 +38,8 @@ def gauge(name, value, unit: nil, attributes: nil) type: :gauge, value: value, unit: unit, - attributes: attributes + attributes: attributes, + integration: integration ) end @@ -44,8 +48,9 @@ def gauge(name, value, unit: nil, attributes: nil) # @param value [Numeric] the distribution value # @param unit [String, nil] the metric unit (optional) # @param attributes [Hash, nil] additional attributes for the metric (optional) + # @param integration [String, Symbol, nil] registered integration emitting the metric (optional) # @return [void] - def distribution(name, value, unit: nil, attributes: nil) + def distribution(name, value, unit: nil, attributes: nil, integration: nil) return unless Sentry.initialized? Sentry.get_current_hub.capture_metric( @@ -53,7 +58,8 @@ def distribution(name, value, unit: nil, attributes: nil) type: :distribution, value: value, unit: unit, - attributes: attributes + attributes: attributes, + integration: integration ) end end diff --git a/sentry-ruby/lib/sentry/telemetry_event_buffer.rb b/sentry-ruby/lib/sentry/telemetry_event_buffer.rb index 29f348af8..e30aba4d0 100644 --- a/sentry-ruby/lib/sentry/telemetry_event_buffer.rb +++ b/sentry-ruby/lib/sentry/telemetry_event_buffer.rb @@ -89,52 +89,52 @@ def clear! private def send_items - envelope = Envelope.new( - event_id: Sentry::Utils.uuid, - sent_at: Sentry.utc_now.iso8601, - dsn: @dsn, - sdk: Sentry.sdk_meta - ) - discarded_count = 0 discarded_bytes = 0 - envelope_items = [] - - if @before_send - @pending_items.each do |item| - processed_item = @before_send.call(item) - - if processed_item - envelope_items << processed_item.to_h - else - discarded_count += 1 - discarded_bytes += JSON.generate(item.to_h).bytesize - end + processed_items = [] + + @pending_items.each do |item| + processed_item = @before_send ? @before_send.call(item) : item + + if processed_item + processed_items << processed_item + else + discarded_count += 1 + discarded_bytes += JSON.generate(item.to_h).bytesize end - else - envelope_items = @pending_items.map(&:to_h) end unless discarded_count.zero? @client.transport.record_lost_event(:before_send, @data_category, num: discarded_count, num_bytes: discarded_bytes) end - return if envelope_items.empty? - - envelope.add_item( - { - type: @envelope_type, - item_count: envelope_items.size, - content_type: @envelope_content_type - }, - { items: envelope_items } - ) - - @client.send_envelope(envelope) + processed_items.group_by { |item| sdk_meta_for(item) }.each do |sdk_meta, items| + envelope = Envelope.new( + event_id: Sentry::Utils.uuid, + sent_at: Sentry.utc_now.iso8601, + dsn: @dsn, + sdk: sdk_meta + ) + + envelope.add_item( + { + type: @envelope_type, + item_count: items.size, + content_type: @envelope_content_type + }, + { items: items.map(&:to_h) } + ) + + @client.send_envelope(envelope) + end rescue => e log_error("[#{self.class}] Failed to send #{@event_class}", e, debug: @debug) ensure clear! end + + def sdk_meta_for(item) + item.respond_to?(:sdk_meta) ? item.sdk_meta : Sentry.sdk_meta + end end end diff --git a/sentry-ruby/spec/sentry/metrics_spec.rb b/sentry-ruby/spec/sentry/metrics_spec.rb index 72353f066..1ac79cba0 100644 --- a/sentry-ruby/spec/sentry/metrics_spec.rb +++ b/sentry-ruby/spec/sentry/metrics_spec.rb @@ -229,6 +229,45 @@ expect(metric_names).to contain_exactly("test.counter1", "test.counter2", "test.gauge") end + context "with integration attribution" do + let(:integration_meta) { { name: "sentry.ruby.test", version: "1.2.3" }.freeze } + + before do + Sentry.integrations["test"] = integration_meta + end + + after do + Sentry.integrations.delete("test") + end + + it "sets metric attributes and the envelope sdk from the integration" do + Sentry.metrics.count("test.counter", integration: :test) + + Sentry.get_current_client.flush + + expect(sentry_envelopes.first.headers[:sdk]).to eq(integration_meta) + expect(sentry_metrics.first[:attributes]["sentry.sdk.name"]).to eq( + { type: "string", value: "sentry.ruby.test" } + ) + expect(sentry_metrics.first[:attributes]["sentry.sdk.version"]).to eq( + { type: "string", value: "1.2.3" } + ) + end + + it "partitions mixed metrics into envelopes by sdk" do + Sentry.metrics.count("test.manual") + Sentry.metrics.count("test.integration", integration: "test") + + Sentry.get_current_client.flush + + expect(sentry_envelopes.count).to eq(2) + expect(sentry_envelopes.map { |envelope| envelope.headers[:sdk] }).to contain_exactly( + Sentry.sdk_meta, + integration_meta + ) + end + end + describe "envelope structure" do it "includes correct envelope headers" do Sentry.metrics.count("test.counter") diff --git a/sentry-yabeda/lib/sentry/yabeda/adapter.rb b/sentry-yabeda/lib/sentry/yabeda/adapter.rb index a93327408..859e16424 100644 --- a/sentry-yabeda/lib/sentry/yabeda/adapter.rb +++ b/sentry-yabeda/lib/sentry/yabeda/adapter.rb @@ -17,7 +17,8 @@ def perform_counter_increment!(counter, tags, increment) Sentry.metrics.count( metric_name(counter), value: increment, - attributes: attributes_for(tags) + attributes: attributes_for(tags), + integration: :yabeda ) end @@ -28,7 +29,8 @@ def perform_gauge_set!(gauge, tags, value) metric_name(gauge), value, unit: unit_for(gauge), - attributes: attributes_for(tags) + attributes: attributes_for(tags), + integration: :yabeda ) end @@ -39,7 +41,8 @@ def perform_histogram_measure!(histogram, tags, value) metric_name(histogram), value, unit: unit_for(histogram), - attributes: attributes_for(tags) + attributes: attributes_for(tags), + integration: :yabeda ) end @@ -50,7 +53,8 @@ def perform_summary_observe!(summary, tags, value) metric_name(summary), value, unit: unit_for(summary), - attributes: attributes_for(tags) + attributes: attributes_for(tags), + integration: :yabeda ) end diff --git a/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb b/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb index 8c6fbcdb5..9b2c2dffc 100644 --- a/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb +++ b/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb @@ -20,7 +20,9 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup counter = build_metric(:counter, name: :orders_created, group: :myapp) - expect(Sentry.metrics).to receive(:count).with("myapp.orders_created", value: 1, attributes: nil) + expect(Sentry.metrics).to receive(:count).with( + "myapp.orders_created", value: 1, attributes: nil, integration: :yabeda + ) adapter.perform_counter_increment!(counter, {}, 1) end @@ -29,7 +31,9 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup counter = build_metric(:counter, name: :total_requests) - expect(Sentry.metrics).to receive(:count).with("total_requests", value: 1, attributes: nil) + expect(Sentry.metrics).to receive(:count).with( + "total_requests", value: 1, attributes: nil, integration: :yabeda + ) adapter.perform_counter_increment!(counter, {}, 1) end @@ -43,7 +47,7 @@ def build_metric(type, name:, group: nil, unit: nil) expect(Sentry.metrics).to receive(:count).with( "rails.requests", value: 5, - attributes: tags + attributes: tags, integration: :yabeda ) adapter.perform_counter_increment!(counter, tags, 5) @@ -56,7 +60,7 @@ def build_metric(type, name:, group: nil, unit: nil) expect(Sentry.metrics).to receive(:count).with( "rails.requests", value: 1, - attributes: nil + attributes: nil, integration: :yabeda ) adapter.perform_counter_increment!(counter, {}, 1) @@ -72,7 +76,7 @@ def build_metric(type, name:, group: nil, unit: nil) "sidekiq.queue_depth", 42, unit: nil, - attributes: tags + attributes: tags, integration: :yabeda ) adapter.perform_gauge_set!(gauge, tags, 42) @@ -86,7 +90,7 @@ def build_metric(type, name:, group: nil, unit: nil) "process.memory_usage", 1024, unit: "byte", - attributes: nil + attributes: nil, integration: :yabeda ) adapter.perform_gauge_set!(gauge, {}, 1024) @@ -102,7 +106,7 @@ def build_metric(type, name:, group: nil, unit: nil) "rails.request_duration", 150.5, unit: "millisecond", - attributes: tags + attributes: tags, integration: :yabeda ) adapter.perform_histogram_measure!(histogram, tags, 150.5) @@ -118,7 +122,7 @@ def build_metric(type, name:, group: nil, unit: nil) "http.response_size", 2048, unit: "byte", - attributes: tags + attributes: tags, integration: :yabeda ) adapter.perform_summary_observe!(summary, tags, 2048) @@ -131,7 +135,7 @@ def build_metric(type, name:, group: nil, unit: nil) histogram = build_metric(:histogram, name: :duration, group: :rails, unit: :seconds) expect(Sentry.metrics).to receive(:distribution).with( - "rails.duration", 1.5, unit: "second", attributes: nil + "rails.duration", 1.5, unit: "second", attributes: nil, integration: :yabeda ) adapter.perform_histogram_measure!(histogram, {}, 1.5) @@ -142,7 +146,7 @@ def build_metric(type, name:, group: nil, unit: nil) histogram = build_metric(:histogram, name: :latency, unit: :milliseconds) expect(Sentry.metrics).to receive(:distribution).with( - "latency", 250.0, unit: "millisecond", attributes: nil + "latency", 250.0, unit: "millisecond", attributes: nil, integration: :yabeda ) adapter.perform_histogram_measure!(histogram, {}, 250.0) @@ -153,7 +157,7 @@ def build_metric(type, name:, group: nil, unit: nil) gauge = build_metric(:gauge, name: :threads) expect(Sentry.metrics).to receive(:gauge).with( - "threads", 5, unit: nil, attributes: nil + "threads", 5, unit: nil, attributes: nil, integration: :yabeda ) adapter.perform_gauge_set!(gauge, {}, 5) @@ -164,7 +168,7 @@ def build_metric(type, name:, group: nil, unit: nil) gauge = build_metric(:gauge, name: :uptime, unit: :second) expect(Sentry.metrics).to receive(:gauge).with( - "uptime", 3600, unit: "second", attributes: nil + "uptime", 3600, unit: "second", attributes: nil, integration: :yabeda ) adapter.perform_gauge_set!(gauge, {}, 3600) @@ -198,6 +202,24 @@ def build_metric(type, name:, group: nil, unit: nil) end end + describe "integration attribution" do + it "emits metrics with the Yabeda sdk identity" do + perform_basic_setup + + counter = build_metric(:counter, name: :requests, group: :rails) + adapter.perform_counter_increment!(counter, {}, 1) + Sentry.get_current_client.flush + + envelope = sentry_envelopes.first + metric = envelope.items.first.payload[:items].first + + expect(envelope.headers[:sdk]).to eq(Sentry.integrations["yabeda"]) + expect(metric[:attributes]["sentry.sdk.name"]).to eq( + { type: "string", value: "sentry.ruby.yabeda" } + ) + end + end + describe "tag passthrough" do it "passes all tags as Sentry attributes" do perform_basic_setup @@ -208,7 +230,7 @@ def build_metric(type, name:, group: nil, unit: nil) expect(Sentry.metrics).to receive(:count).with( "rails.requests", value: 1, - attributes: complex_tags + attributes: complex_tags, integration: :yabeda ) adapter.perform_counter_increment!(counter, complex_tags, 1) From 2357557c010466775f41f161d30f70bb5d0d7491 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:42:41 +0000 Subject: [PATCH 2/5] refactor(metrics): Use integration metric helpers --- sentry-ruby/lib/sentry/integrable.rb | 12 ++++ sentry-ruby/spec/sentry/integrable_spec.rb | 16 +++++ sentry-yabeda/lib/sentry/yabeda/adapter.rb | 20 +++--- .../spec/sentry/yabeda/adapter_spec.rb | 62 +++++++++---------- 4 files changed, 67 insertions(+), 43 deletions(-) diff --git a/sentry-ruby/lib/sentry/integrable.rb b/sentry-ruby/lib/sentry/integrable.rb index a45dd7839..4fdeeb3fe 100644 --- a/sentry-ruby/lib/sentry/integrable.rb +++ b/sentry-ruby/lib/sentry/integrable.rb @@ -32,5 +32,17 @@ def capture_check_in(slug, status, **options, &block) options[:hint][:integration] = integration_name Sentry.capture_check_in(slug, status, **options, &block) end + + def count(name, value: 1, attributes: nil) + Sentry.metrics.count(name, value: value, attributes: attributes, integration: integration_name) + end + + def gauge(name, value, unit: nil, attributes: nil) + Sentry.metrics.gauge(name, value, unit: unit, attributes: attributes, integration: integration_name) + end + + def distribution(name, value, unit: nil, attributes: nil) + Sentry.metrics.distribution(name, value, unit: unit, attributes: attributes, integration: integration_name) + end end end diff --git a/sentry-ruby/spec/sentry/integrable_spec.rb b/sentry-ruby/spec/sentry/integrable_spec.rb index 4a6677a87..d690a0fdb 100644 --- a/sentry-ruby/spec/sentry/integrable_spec.rb +++ b/sentry-ruby/spec/sentry/integrable_spec.rb @@ -101,5 +101,21 @@ module AnotherIntegration; end event = Sentry.capture_message(message) expect(event.sdk).to eq(Sentry.sdk_meta) end + + it "generates integration-attributed metric helpers" do + expect(Sentry.metrics).to receive(:count).with( + "test.count", value: 2, attributes: { source: "test" }, integration: "fake_integration" + ) + expect(Sentry.metrics).to receive(:gauge).with( + "test.gauge", 3, unit: "item", attributes: { source: "test" }, integration: "fake_integration" + ) + expect(Sentry.metrics).to receive(:distribution).with( + "test.distribution", 4, unit: "second", attributes: { source: "test" }, integration: "fake_integration" + ) + + Sentry::FakeIntegration.count("test.count", value: 2, attributes: { source: "test" }) + Sentry::FakeIntegration.gauge("test.gauge", 3, unit: "item", attributes: { source: "test" }) + Sentry::FakeIntegration.distribution("test.distribution", 4, unit: "second", attributes: { source: "test" }) + end end end diff --git a/sentry-yabeda/lib/sentry/yabeda/adapter.rb b/sentry-yabeda/lib/sentry/yabeda/adapter.rb index 859e16424..d4a47a372 100644 --- a/sentry-yabeda/lib/sentry/yabeda/adapter.rb +++ b/sentry-yabeda/lib/sentry/yabeda/adapter.rb @@ -14,47 +14,43 @@ def register_summary!(_metric); end def perform_counter_increment!(counter, tags, increment) return unless enabled? - Sentry.metrics.count( + Sentry::Yabeda.count( metric_name(counter), value: increment, - attributes: attributes_for(tags), - integration: :yabeda + attributes: attributes_for(tags) ) end def perform_gauge_set!(gauge, tags, value) return unless enabled? - Sentry.metrics.gauge( + Sentry::Yabeda.gauge( metric_name(gauge), value, unit: unit_for(gauge), - attributes: attributes_for(tags), - integration: :yabeda + attributes: attributes_for(tags) ) end def perform_histogram_measure!(histogram, tags, value) return unless enabled? - Sentry.metrics.distribution( + Sentry::Yabeda.distribution( metric_name(histogram), value, unit: unit_for(histogram), - attributes: attributes_for(tags), - integration: :yabeda + attributes: attributes_for(tags) ) end def perform_summary_observe!(summary, tags, value) return unless enabled? - Sentry.metrics.distribution( + Sentry::Yabeda.distribution( metric_name(summary), value, unit: unit_for(summary), - attributes: attributes_for(tags), - integration: :yabeda + attributes: attributes_for(tags) ) end diff --git a/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb b/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb index 9b2c2dffc..e869bda20 100644 --- a/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb +++ b/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb @@ -20,8 +20,8 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup counter = build_metric(:counter, name: :orders_created, group: :myapp) - expect(Sentry.metrics).to receive(:count).with( - "myapp.orders_created", value: 1, attributes: nil, integration: :yabeda + expect(Sentry::Yabeda).to receive(:count).with( + "myapp.orders_created", value: 1, attributes: nil ) adapter.perform_counter_increment!(counter, {}, 1) @@ -31,8 +31,8 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup counter = build_metric(:counter, name: :total_requests) - expect(Sentry.metrics).to receive(:count).with( - "total_requests", value: 1, attributes: nil, integration: :yabeda + expect(Sentry::Yabeda).to receive(:count).with( + "total_requests", value: 1, attributes: nil ) adapter.perform_counter_increment!(counter, {}, 1) @@ -40,14 +40,14 @@ def build_metric(type, name:, group: nil, unit: nil) end describe "#perform_counter_increment!" do - it "calls Sentry.metrics.count with correct arguments" do + it "calls Sentry::Yabeda.count with correct arguments" do perform_basic_setup counter = build_metric(:counter, name: :requests, group: :rails) - expect(Sentry.metrics).to receive(:count).with( + expect(Sentry::Yabeda).to receive(:count).with( "rails.requests", value: 5, - attributes: tags, integration: :yabeda + attributes: tags ) adapter.perform_counter_increment!(counter, tags, 5) @@ -57,10 +57,10 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup counter = build_metric(:counter, name: :requests, group: :rails) - expect(Sentry.metrics).to receive(:count).with( + expect(Sentry::Yabeda).to receive(:count).with( "rails.requests", value: 1, - attributes: nil, integration: :yabeda + attributes: nil ) adapter.perform_counter_increment!(counter, {}, 1) @@ -68,15 +68,15 @@ def build_metric(type, name:, group: nil, unit: nil) end describe "#perform_gauge_set!" do - it "calls Sentry.metrics.gauge with correct arguments" do + it "calls Sentry::Yabeda.gauge with correct arguments" do perform_basic_setup gauge = build_metric(:gauge, name: :queue_depth, group: :sidekiq) - expect(Sentry.metrics).to receive(:gauge).with( + expect(Sentry::Yabeda).to receive(:gauge).with( "sidekiq.queue_depth", 42, unit: nil, - attributes: tags, integration: :yabeda + attributes: tags ) adapter.perform_gauge_set!(gauge, tags, 42) @@ -86,11 +86,11 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup gauge = build_metric(:gauge, name: :memory_usage, group: :process, unit: :bytes) - expect(Sentry.metrics).to receive(:gauge).with( + expect(Sentry::Yabeda).to receive(:gauge).with( "process.memory_usage", 1024, unit: "byte", - attributes: nil, integration: :yabeda + attributes: nil ) adapter.perform_gauge_set!(gauge, {}, 1024) @@ -98,15 +98,15 @@ def build_metric(type, name:, group: nil, unit: nil) end describe "#perform_histogram_measure!" do - it "calls Sentry.metrics.distribution with correct arguments" do + it "calls Sentry::Yabeda.distribution with correct arguments" do perform_basic_setup histogram = build_metric(:histogram, name: :request_duration, group: :rails, unit: :milliseconds) - expect(Sentry.metrics).to receive(:distribution).with( + expect(Sentry::Yabeda).to receive(:distribution).with( "rails.request_duration", 150.5, unit: "millisecond", - attributes: tags, integration: :yabeda + attributes: tags ) adapter.perform_histogram_measure!(histogram, tags, 150.5) @@ -114,15 +114,15 @@ def build_metric(type, name:, group: nil, unit: nil) end describe "#perform_summary_observe!" do - it "calls Sentry.metrics.distribution with correct arguments" do + it "calls Sentry::Yabeda.distribution with correct arguments" do perform_basic_setup summary = build_metric(:summary, name: :response_size, group: :http, unit: :bytes) - expect(Sentry.metrics).to receive(:distribution).with( + expect(Sentry::Yabeda).to receive(:distribution).with( "http.response_size", 2048, unit: "byte", - attributes: tags, integration: :yabeda + attributes: tags ) adapter.perform_summary_observe!(summary, tags, 2048) @@ -134,8 +134,8 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup histogram = build_metric(:histogram, name: :duration, group: :rails, unit: :seconds) - expect(Sentry.metrics).to receive(:distribution).with( - "rails.duration", 1.5, unit: "second", attributes: nil, integration: :yabeda + expect(Sentry::Yabeda).to receive(:distribution).with( + "rails.duration", 1.5, unit: "second", attributes: nil ) adapter.perform_histogram_measure!(histogram, {}, 1.5) @@ -145,8 +145,8 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup histogram = build_metric(:histogram, name: :latency, unit: :milliseconds) - expect(Sentry.metrics).to receive(:distribution).with( - "latency", 250.0, unit: "millisecond", attributes: nil, integration: :yabeda + expect(Sentry::Yabeda).to receive(:distribution).with( + "latency", 250.0, unit: "millisecond", attributes: nil ) adapter.perform_histogram_measure!(histogram, {}, 250.0) @@ -156,8 +156,8 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup gauge = build_metric(:gauge, name: :threads) - expect(Sentry.metrics).to receive(:gauge).with( - "threads", 5, unit: nil, attributes: nil, integration: :yabeda + expect(Sentry::Yabeda).to receive(:gauge).with( + "threads", 5, unit: nil, attributes: nil ) adapter.perform_gauge_set!(gauge, {}, 5) @@ -167,8 +167,8 @@ def build_metric(type, name:, group: nil, unit: nil) perform_basic_setup gauge = build_metric(:gauge, name: :uptime, unit: :second) - expect(Sentry.metrics).to receive(:gauge).with( - "uptime", 3600, unit: "second", attributes: nil, integration: :yabeda + expect(Sentry::Yabeda).to receive(:gauge).with( + "uptime", 3600, unit: "second", attributes: nil ) adapter.perform_gauge_set!(gauge, {}, 3600) @@ -195,7 +195,7 @@ def build_metric(type, name:, group: nil, unit: nil) describe "guard conditions" do it "does not emit metrics when Sentry is not initialized" do - expect(Sentry.metrics).not_to receive(:count) + expect(Sentry::Yabeda).not_to receive(:count) counter = build_metric(:counter, name: :requests, group: :rails) adapter.perform_counter_increment!(counter, {}, 1) @@ -227,10 +227,10 @@ def build_metric(type, name:, group: nil, unit: nil) complex_tags = { controller: "orders", action: "create", region: "eu-west", status: 200 } counter = build_metric(:counter, name: :requests, group: :rails) - expect(Sentry.metrics).to receive(:count).with( + expect(Sentry::Yabeda).to receive(:count).with( "rails.requests", value: 1, - attributes: complex_tags, integration: :yabeda + attributes: complex_tags ) adapter.perform_counter_increment!(counter, complex_tags, 1) From 7007ec55457be7253b62f4910e643636e3d74247 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:59:57 +0000 Subject: [PATCH 3/5] refactor(metrics): Enrich integration sdk in scope --- sentry-ruby/lib/sentry/hub.rb | 2 +- sentry-ruby/lib/sentry/metric_event.rb | 11 +++-------- sentry-ruby/lib/sentry/scope.rb | 6 ++++-- sentry-ruby/lib/sentry/telemetry_event_buffer.rb | 3 ++- sentry-ruby/spec/sentry/metric_event_spec.rb | 13 +++++++++++++ 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/sentry-ruby/lib/sentry/hub.rb b/sentry-ruby/lib/sentry/hub.rb index 9e986a649..81f7aee6f 100644 --- a/sentry-ruby/lib/sentry/hub.rb +++ b/sentry-ruby/lib/sentry/hub.rb @@ -251,7 +251,7 @@ def capture_metric(name:, type:, value:, unit: nil, attributes: nil, integration type: type, unit: unit, attributes: attributes&.dup, - sdk_meta: Sentry.integrations[integration.to_s] + integration_meta: Sentry.integrations[integration.to_s] ) current_client.buffer_metric_event(metric, current_scope) diff --git a/sentry-ruby/lib/sentry/metric_event.rb b/sentry-ruby/lib/sentry/metric_event.rb index edc250ced..ec45df900 100644 --- a/sentry-ruby/lib/sentry/metric_event.rb +++ b/sentry-ruby/lib/sentry/metric_event.rb @@ -6,7 +6,7 @@ module Sentry class MetricEvent include Sentry::Utils::TelemetryAttributes - attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes, :sdk_meta + attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes, :integration_meta attr_writer :trace_id, :span_id, :attributes def initialize( @@ -15,19 +15,14 @@ def initialize( value:, unit: nil, attributes: nil, - sdk_meta: nil + integration_meta: nil ) @name = name @type = type @value = value @unit = unit @attributes = attributes || {} - @sdk_meta = sdk_meta || Sentry.sdk_meta - - if sdk_meta - @attributes["sentry.sdk.name"] = sdk_meta[:name] || sdk_meta["name"] - @attributes["sentry.sdk.version"] = sdk_meta[:version] || sdk_meta["version"] - end + @integration_meta = integration_meta @timestamp = Sentry.utc_now @trace_id = nil diff --git a/sentry-ruby/lib/sentry/scope.rb b/sentry-ruby/lib/sentry/scope.rb index e12df2154..95a8ac0fc 100644 --- a/sentry-ruby/lib/sentry/scope.rb +++ b/sentry-ruby/lib/sentry/scope.rb @@ -102,8 +102,10 @@ def apply_to_telemetry(telemetry) configuration = Sentry.configuration return telemetry unless configuration - telemetry.attributes["sentry.sdk.name"] ||= Sentry.sdk_meta["name"] - telemetry.attributes["sentry.sdk.version"] ||= Sentry.sdk_meta["version"] + sdk_meta = telemetry.respond_to?(:integration_meta) ? telemetry.integration_meta : nil + sdk_meta ||= Sentry.sdk_meta + telemetry.attributes["sentry.sdk.name"] ||= sdk_meta[:name] || sdk_meta["name"] + telemetry.attributes["sentry.sdk.version"] ||= sdk_meta[:version] || sdk_meta["version"] telemetry.attributes["sentry.environment"] ||= configuration.environment if configuration.environment telemetry.attributes["sentry.release"] ||= configuration.release if configuration.release telemetry.attributes["server.address"] ||= configuration.server_name if configuration.server_name diff --git a/sentry-ruby/lib/sentry/telemetry_event_buffer.rb b/sentry-ruby/lib/sentry/telemetry_event_buffer.rb index e30aba4d0..6d22277fc 100644 --- a/sentry-ruby/lib/sentry/telemetry_event_buffer.rb +++ b/sentry-ruby/lib/sentry/telemetry_event_buffer.rb @@ -134,7 +134,8 @@ def send_items end def sdk_meta_for(item) - item.respond_to?(:sdk_meta) ? item.sdk_meta : Sentry.sdk_meta + integration_meta = item.respond_to?(:integration_meta) ? item.integration_meta : nil + integration_meta || Sentry.sdk_meta end end end diff --git a/sentry-ruby/spec/sentry/metric_event_spec.rb b/sentry-ruby/spec/sentry/metric_event_spec.rb index 413f3b4d8..afc8a4ed3 100644 --- a/sentry-ruby/spec/sentry/metric_event_spec.rb +++ b/sentry-ruby/spec/sentry/metric_event_spec.rb @@ -50,6 +50,19 @@ expect(event.attributes).to eq({ "foo" => "bar" }) end + + it "holds integration metadata without applying sdk attributes" do + integration_meta = { name: "sentry.ruby.test", version: "1.2.3" } + event = described_class.new( + name: "test.metric", + type: :counter, + value: 1, + integration_meta: integration_meta + ) + + expect(event.integration_meta).to eq(integration_meta) + expect(event.attributes).to eq({}) + end end describe "#to_h" do From 0da22d0600ab6031b169712127639980b42cb6ef Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:13:02 +0000 Subject: [PATCH 4/5] refactor(telemetry): Simplify envelope headers --- .../lib/sentry/telemetry_event_buffer.rb | 62 +++++++++---------- sentry-ruby/spec/sentry/metrics_spec.rb | 17 ++--- .../spec/sentry/yabeda/adapter_spec.rb | 2 +- 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/sentry-ruby/lib/sentry/telemetry_event_buffer.rb b/sentry-ruby/lib/sentry/telemetry_event_buffer.rb index 6d22277fc..714957943 100644 --- a/sentry-ruby/lib/sentry/telemetry_event_buffer.rb +++ b/sentry-ruby/lib/sentry/telemetry_event_buffer.rb @@ -89,53 +89,47 @@ def clear! private def send_items + envelope = Envelope.new(sent_at: Sentry.utc_now.iso8601) + discarded_count = 0 discarded_bytes = 0 - processed_items = [] - - @pending_items.each do |item| - processed_item = @before_send ? @before_send.call(item) : item - - if processed_item - processed_items << processed_item - else - discarded_count += 1 - discarded_bytes += JSON.generate(item.to_h).bytesize + envelope_items = [] + + if @before_send + @pending_items.each do |item| + processed_item = @before_send.call(item) + + if processed_item + envelope_items << processed_item.to_h + else + discarded_count += 1 + discarded_bytes += JSON.generate(item.to_h).bytesize + end end + else + envelope_items = @pending_items.map(&:to_h) end unless discarded_count.zero? @client.transport.record_lost_event(:before_send, @data_category, num: discarded_count, num_bytes: discarded_bytes) end - processed_items.group_by { |item| sdk_meta_for(item) }.each do |sdk_meta, items| - envelope = Envelope.new( - event_id: Sentry::Utils.uuid, - sent_at: Sentry.utc_now.iso8601, - dsn: @dsn, - sdk: sdk_meta - ) - - envelope.add_item( - { - type: @envelope_type, - item_count: items.size, - content_type: @envelope_content_type - }, - { items: items.map(&:to_h) } - ) - - @client.send_envelope(envelope) - end + return if envelope_items.empty? + + envelope.add_item( + { + type: @envelope_type, + item_count: envelope_items.size, + content_type: @envelope_content_type + }, + { items: envelope_items } + ) + + @client.send_envelope(envelope) rescue => e log_error("[#{self.class}] Failed to send #{@event_class}", e, debug: @debug) ensure clear! end - - def sdk_meta_for(item) - integration_meta = item.respond_to?(:integration_meta) ? item.integration_meta : nil - integration_meta || Sentry.sdk_meta - end end end diff --git a/sentry-ruby/spec/sentry/metrics_spec.rb b/sentry-ruby/spec/sentry/metrics_spec.rb index 1ac79cba0..970568998 100644 --- a/sentry-ruby/spec/sentry/metrics_spec.rb +++ b/sentry-ruby/spec/sentry/metrics_spec.rb @@ -240,12 +240,11 @@ Sentry.integrations.delete("test") end - it "sets metric attributes and the envelope sdk from the integration" do + it "sets metric sdk attributes from the integration" do Sentry.metrics.count("test.counter", integration: :test) Sentry.get_current_client.flush - expect(sentry_envelopes.first.headers[:sdk]).to eq(integration_meta) expect(sentry_metrics.first[:attributes]["sentry.sdk.name"]).to eq( { type: "string", value: "sentry.ruby.test" } ) @@ -254,17 +253,15 @@ ) end - it "partitions mixed metrics into envelopes by sdk" do + it "batches metrics with different sdk attributes into one envelope" do Sentry.metrics.count("test.manual") Sentry.metrics.count("test.integration", integration: "test") Sentry.get_current_client.flush - expect(sentry_envelopes.count).to eq(2) - expect(sentry_envelopes.map { |envelope| envelope.headers[:sdk] }).to contain_exactly( - Sentry.sdk_meta, - integration_meta - ) + expect(sentry_envelopes.count).to eq(1) + sdk_names = sentry_metrics.map { |metric| metric[:attributes]["sentry.sdk.name"][:value] } + expect(sdk_names).to contain_exactly("sentry.ruby", "sentry.ruby.test") end end @@ -276,10 +273,8 @@ envelope = sentry_envelopes.first headers = envelope.headers - expect(headers[:event_id]).to match(/\A[0-9a-f]{32}\z/) # UUID format + expect(headers.keys).to contain_exactly(:sent_at) expect(headers[:sent_at]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/) # ISO8601 timestamp - expect(headers[:dsn]).to eq(Sentry.configuration.dsn) - expect(headers[:sdk]).to eq(Sentry.sdk_meta) end it "includes correct envelope item headers" do diff --git a/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb b/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb index e869bda20..fa196e639 100644 --- a/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb +++ b/sentry-yabeda/spec/sentry/yabeda/adapter_spec.rb @@ -213,7 +213,7 @@ def build_metric(type, name:, group: nil, unit: nil) envelope = sentry_envelopes.first metric = envelope.items.first.payload[:items].first - expect(envelope.headers[:sdk]).to eq(Sentry.integrations["yabeda"]) + expect(envelope.headers.keys).to contain_exactly(:sent_at) expect(metric[:attributes]["sentry.sdk.name"]).to eq( { type: "string", value: "sentry.ruby.yabeda" } ) From 7b9945afb3cce2556f6c8e0aa8e44129b16dcc62 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:16:26 +0000 Subject: [PATCH 5/5] test(metrics): Remove redundant metadata spec --- sentry-ruby/spec/sentry/metric_event_spec.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/sentry-ruby/spec/sentry/metric_event_spec.rb b/sentry-ruby/spec/sentry/metric_event_spec.rb index afc8a4ed3..413f3b4d8 100644 --- a/sentry-ruby/spec/sentry/metric_event_spec.rb +++ b/sentry-ruby/spec/sentry/metric_event_spec.rb @@ -50,19 +50,6 @@ expect(event.attributes).to eq({ "foo" => "bar" }) end - - it "holds integration metadata without applying sdk attributes" do - integration_meta = { name: "sentry.ruby.test", version: "1.2.3" } - event = described_class.new( - name: "test.metric", - type: :counter, - value: 1, - integration_meta: integration_meta - ) - - expect(event.integration_meta).to eq(integration_meta) - expect(event.attributes).to eq({}) - end end describe "#to_h" do