diff --git a/sentry-ruby/lib/sentry/faraday.rb b/sentry-ruby/lib/sentry/faraday.rb index 320a82fce..bc92c0193 100644 --- a/sentry-ruby/lib/sentry/faraday.rb +++ b/sentry-ruby/lib/sentry/faraday.rb @@ -14,10 +14,13 @@ def initialize(url = nil, options = nil) # Ensure that we attach instrumentation only if the adapter is not net/http # because if is is, then the net/http instrumentation will take care of it - if builder.adapter.name != "Faraday::Adapter::NetHttp" + if builder.adapter.name != "Faraday::Adapter::NetHttp" && + !builder.locked? && + !builder.instance_variable_defined?(:@sentry_patched) # Make sure that it's going to be the first middleware so that it can capture # the entire request processing involving other middlewares builder.insert(0, ::Faraday::Request::Instrumentation, name: OP_NAME, instrumenter: Instrumenter.new) + builder.instance_variable_set(:@sentry_patched, true) end end end diff --git a/sentry-ruby/spec/sentry/faraday_spec.rb b/sentry-ruby/spec/sentry/faraday_spec.rb index 90a25a0b3..66a484676 100644 --- a/sentry-ruby/spec/sentry/faraday_spec.rb +++ b/sentry-ruby/spec/sentry/faraday_spec.rb @@ -20,6 +20,27 @@ end context "with tracing enabled" do + it "does not insert instrumentation more than once when a builder is reused" do + stubs = Faraday::Adapter::Test::Stubs.new do |stub| + stub.get("/") { [200, {}, "ok"] } + end + + builder = Faraday::RackBuilder.new do |connection| + connection.adapter :test, stubs + end + + primary = Faraday.new("https://primary.example", builder: builder) + Faraday.new("https://secondary.example", builder: builder) + + expect(builder.handlers.count { |handler| handler == Faraday::Request::Instrumentation }).to eq(1) + + primary.get("/") + + expect { + Faraday.new("https://fallback.example", builder: builder) + }.not_to raise_error + end + let(:http) do Faraday.new(url) do |f| f.request :json