diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce5d1ea6..fc951202d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,132 @@ +## 5.0.0 + +### Breaking Change - Goodbye `faraday` 👋 + +This version removes the dependency of [faraday](https://github.com/lostisland/faraday) and replaces related implementation with the `Net::HTTP` standard library. + +(If you didn't notice it, the SDK previously used `faraday` as the HTTP client to send events to Sentry. You can find the implementation in [HTTPTransport](https://github.com/getsentry/sentry-ruby/blob/v4-9/sentry-ruby/lib/sentry/transport/http_transport.rb).) + +#### Why + +Since the old `sentry-raven` SDK, we've been using `faraday` as the SDKs' HTTP client for years. It's an amazing tool and saved us many work, allowing us to focus on SDK features. + +But because many users also use `faraday` in their own apps and have their own version requirements, managing this dependency has become harder and harder over the past few years. Just to list a few related issues: + +- [#944](https://github.com/getsentry/sentry-ruby/issues/944) +- [#1424](https://github.com/getsentry/sentry-ruby/issues/1424) +- [#1524](https://github.com/getsentry/sentry-ruby/issues/1524) + +And with the release of [faraday 2.0](https://github.com/lostisland/faraday/releases/tag/v2.0.0), we could only imagine it gets even more difficult (which it kinda did, see [#1663](https://github.com/getsentry/sentry-ruby/issues/1663)). + +So we decided to officially say goodbye to it with this release. + + +#### What's changed? + + +By default, the SDK used `faraday`'s `net_http` adapter, which is also built on top of `Net::HTTP`. So this change shouldn't impact most of the users. + +The only noticeable changes are the removal of 2 faraday-specific transport configurations: + +- `config.transport.http_adapter` +- `config.transport.faraday_builder` + +**If you are already on version `4.9.x` and do not use those configuration options, it'll be as simple as `bundle update`.** + +#### What if I still want to use `faraday` to send my events? + +`sentry-ruby` already allows users to set a custom transport class with: + +```ruby +Sentry.init do |config| + config.transport.transport_class = MyTransportClass +end +``` + +So to use a faraday-based transport, you can: + +1. Build a `FaradayTransport` like this: + +```rb +require 'sentry/transport/http_transport' +require 'faraday' + +class FaradayTransport < Sentry::HTTPTransport + attr_reader :adapter + + def initialize(*args) + @adapter = :net_http + super + end + + def send_data(data) + encoding = "" + + if should_compress?(data) + data = Zlib.gzip(data) + encoding = GZIP_ENCODING + end + + response = conn.post @endpoint do |req| + req.headers['Content-Type'] = CONTENT_TYPE + req.headers['Content-Encoding'] = encoding + req.headers['X-Sentry-Auth'] = generate_auth_header + req.body = data + end + + if has_rate_limited_header?(response.headers) + handle_rate_limited_response(response.headers) + end + rescue Faraday::Error => e + error_info = e.message + + if e.response + if e.response[:status] == 429 + handle_rate_limited_response(e.response[:headers]) + else + error_info += "\nbody: #{e.response[:body]}" + error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error'] + end + end + + raise Sentry::ExternalError, error_info + end + + private + + def set_conn + server = @dsn.server + + log_debug("Sentry HTTP Transport connecting to #{server}") + + Faraday.new(server, :ssl => ssl_configuration, :proxy => @transport_configuration.proxy) do |builder| + builder.response :raise_error + builder.options.merge! faraday_opts + builder.headers[:user_agent] = "sentry-ruby/#{Sentry::VERSION}" + builder.adapter(*adapter) + end + end + + def faraday_opts + [:timeout, :open_timeout].each_with_object({}) do |opt, memo| + memo[opt] = @transport_configuration.public_send(opt) if @transport_configuration.public_send(opt) + end + end + + def ssl_configuration + { + verify: @transport_configuration.ssl_verification, + ca_file: @transport_configuration.ssl_ca_file + }.merge(@transport_configuration.ssl || {}) + end +end +``` + +2. Set `config.transport.transport = FaradayTransport` + + +**Please keep in mind that this may not work in the future when the SDK changes its transport implementation.** + ## 4.9.2 ### Bug Fixes diff --git a/sentry-ruby/lib/sentry/transport/configuration.rb b/sentry-ruby/lib/sentry/transport/configuration.rb index e9728fd2e..13f63111c 100644 --- a/sentry-ruby/lib/sentry/transport/configuration.rb +++ b/sentry-ruby/lib/sentry/transport/configuration.rb @@ -3,8 +3,7 @@ module Sentry class Transport class Configuration - attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :http_adapter, :faraday_builder, - :encoding + attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :encoding attr_reader :transport_class def initialize diff --git a/sentry-ruby/lib/sentry/transport/http_transport.rb b/sentry-ruby/lib/sentry/transport/http_transport.rb index ccc0dd908..886a48ce6 100644 --- a/sentry-ruby/lib/sentry/transport/http_transport.rb +++ b/sentry-ruby/lib/sentry/transport/http_transport.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'faraday' -require 'zlib' +require "net/http" +require "zlib" module Sentry class HTTPTransport < Transport @@ -12,12 +12,12 @@ class HTTPTransport < Transport DEFAULT_DELAY = 60 RETRY_AFTER_HEADER = "retry-after" RATE_LIMIT_HEADER = "x-sentry-rate-limits" + USER_AGENT = "sentry-ruby/#{Sentry::VERSION}" - attr_reader :conn, :adapter + attr_reader :conn def initialize(*args) super - @adapter = @transport_configuration.http_adapter || Faraday.default_adapter @conn = set_conn @endpoint = @dsn.envelope_endpoint end @@ -30,29 +30,37 @@ def send_data(data) encoding = GZIP_ENCODING end - response = conn.post @endpoint do |req| - req.headers['Content-Type'] = CONTENT_TYPE - req.headers['Content-Encoding'] = encoding - req.headers['X-Sentry-Auth'] = generate_auth_header - req.body = data + headers = { + 'Content-Type' => CONTENT_TYPE, + 'Content-Encoding' => encoding, + 'X-Sentry-Auth' => generate_auth_header, + 'User-Agent' => USER_AGENT + } + + response = conn.start do |http| + request = ::Net::HTTP::Post.new(@endpoint, headers) + request.body = data + http.request(request) end - if has_rate_limited_header?(response.headers) - handle_rate_limited_response(response.headers) - end - rescue Faraday::Error => e - error_info = e.message + if response.code.match?(/\A2\d{2}/) + if has_rate_limited_header?(response) + handle_rate_limited_response(response) + end + else + error_info = "the server responded with status #{response.code}" - if e.response - if e.response[:status] == 429 - handle_rate_limited_response(e.response[:headers]) + if response.code == "429" + handle_rate_limited_response(response) else - error_info += "\nbody: #{e.response[:body]}" - error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error'] + error_info += "\nbody: #{response.body}" + error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error'] end - end - raise Sentry::ExternalError, error_info + raise Sentry::ExternalError, error_info + end + rescue SocketError => e + raise Sentry::ExternalError.new(e.message) end private @@ -120,31 +128,40 @@ def should_compress?(data) end def set_conn - server = @dsn.server + server = URI(@dsn.server) log_debug("Sentry HTTP Transport connecting to #{server}") - Faraday.new(server, :ssl => ssl_configuration, :proxy => @transport_configuration.proxy) do |builder| - @transport_configuration.faraday_builder&.call(builder) - builder.response :raise_error - builder.options.merge! faraday_opts - builder.headers[:user_agent] = "sentry-ruby/#{Sentry::VERSION}" - builder.adapter(*adapter) - end - end + use_ssl = server.scheme == "https" + port = use_ssl ? 443 : 80 - # TODO: deprecate and replace where possible w/Faraday Builder - def faraday_opts - [:timeout, :open_timeout].each_with_object({}) do |opt, memo| - memo[opt] = @transport_configuration.public_send(opt) if @transport_configuration.public_send(opt) + connection = + if proxy = @transport_configuration.proxy + ::Net::HTTP.new(server.hostname, port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password]) + else + ::Net::HTTP.new(server.hostname, port, nil) + end + + connection.use_ssl = use_ssl + connection.read_timeout = @transport_configuration.timeout + connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout) + connection.open_timeout = @transport_configuration.open_timeout + + ssl_configuration.each do |key, value| + connection.send("#{key}=", value) end + + connection end def ssl_configuration - { + configuration = { verify: @transport_configuration.ssl_verification, ca_file: @transport_configuration.ssl_ca_file }.merge(@transport_configuration.ssl || {}) + + configuration[:verify_mode] = configuration.delete(:verify) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + configuration end end end diff --git a/sentry-ruby/sentry-ruby-core.gemspec b/sentry-ruby/sentry-ruby-core.gemspec index 09064c7a8..23e3072d8 100644 --- a/sentry-ruby/sentry-ruby-core.gemspec +++ b/sentry-ruby/sentry-ruby-core.gemspec @@ -22,6 +22,5 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "faraday" spec.add_dependency "concurrent-ruby" end diff --git a/sentry-ruby/sentry-ruby.gemspec b/sentry-ruby/sentry-ruby.gemspec index cd499e3f7..9600e8331 100644 --- a/sentry-ruby/sentry-ruby.gemspec +++ b/sentry-ruby/sentry-ruby.gemspec @@ -18,6 +18,5 @@ Gem::Specification.new do |spec| spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md" spec.add_dependency "sentry-ruby-core", Sentry::VERSION - spec.add_dependency "faraday", "~> 1.0" spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2' end diff --git a/sentry-ruby/spec/sentry/transport/http_transport_spec.rb b/sentry-ruby/spec/sentry/transport/http_transport_spec.rb index ebef8fa34..2b44f1452 100644 --- a/sentry-ruby/spec/sentry/transport/http_transport_spec.rb +++ b/sentry-ruby/spec/sentry/transport/http_transport_spec.rb @@ -38,16 +38,6 @@ subject.send_data(data) end - it 'allows to customise faraday' do - builder = spy('faraday_builder') - expect(Faraday).to receive(:new).and_yield(builder) - configuration.transport.faraday_builder = proc { |b| b.request :instrumentation } - - subject - - expect(builder).to have_received(:request).with(:instrumentation) - end - it "accepts custom proxy" do configuration.transport.proxy = { uri: URI("https://example.com"), user: "stan", password: "foobar" }