From 966e9855d8077679d53e41acf654ed3885df56bf Mon Sep 17 00:00:00 2001 From: "americodls@gmail.com" Date: Thu, 2 Oct 2025 14:04:39 +0100 Subject: [PATCH] feat: add example.id attribute to RSpec spans Record RSpec's example.id as a span attribute to provide unique identification for each test example. This is particularly valuable when examples are created dynamically (e.g., within loops), where multiple examples share the same file location but have distinct IDs. --- .../instrumentation/rspec/formatter.rb | 1 + .../instrumentation/rspec/formatter_test.rb | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb b/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb index 11a37b8f82..00cf2baad8 100644 --- a/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb +++ b/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb @@ -58,6 +58,7 @@ def example_group_finished(notification) def example_started(notification) example = notification.example attributes = { + 'rspec.example.id' => example.id.to_s, 'rspec.example.location' => example.location.to_s, 'rspec.example.full_description' => example.full_description.to_s, 'rspec.example.described_class' => example.metadata[:described_class].to_s diff --git a/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb b/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb index d54e6a96d1..5d1c51e78a 100644 --- a/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb +++ b/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb @@ -131,6 +131,10 @@ def run_example(...) _(subject.attributes['rspec.example.location']).must_match %r{\./test/opentelemetry/instrumentation/rspec/formatter_test.rb:\d+} end + it 'has an id attribute' do + _(subject.attributes['rspec.example.id']).must_match %r{\./test/opentelemetry/instrumentation/rspec/formatter_test.rb\[\d+:\d+\]} + end + it 'records when the example passes' do _(subject.attributes['rspec.example.result']).must_equal 'passed' end @@ -326,6 +330,32 @@ def run_example(...) _(subject.events[1].attributes['exception.message']).must_equal 'another-error' end end + + describe 'dynamic examples with same location' do + it 'have unique example.id attributes' do + spans = run_rspec_with_tracing do + RSpec.describe('dynamic examples') do + [1, 2, 3].each do |num| + example("example #{num}") { expect(num).to be_positive } + end + end + end + + example_spans = spans.select { |span| span.name.start_with?('example ') } + _(example_spans.size).must_equal 3 + + # All examples have the same location (same line in the loop) + locations = example_spans.map { |span| span.attributes['rspec.example.location'] } + _(locations.uniq.size).must_equal 1 + + # But each has a unique id + ids = example_spans.map { |span| span.attributes['rspec.example.id'] } + _(ids.uniq.size).must_equal 3 + ids.each do |id| + _(id).must_match %r{\./test/opentelemetry/instrumentation/rspec/formatter_test.rb\[\d+:\d+\]} + end + end + end end describe 'using a custom tracer provider' do