Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 1 addition & 2 deletions sentry-ruby/lib/sentry/transport/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
87 changes: 52 additions & 35 deletions sentry-ruby/lib/sentry/transport/http_transport.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require 'faraday'
require 'zlib'
require "net/http"
require "zlib"

module Sentry
class HTTPTransport < Transport
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
1 change: 0 additions & 1 deletion sentry-ruby/sentry-ruby-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion sentry-ruby/sentry-ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 0 additions & 10 deletions sentry-ruby/spec/sentry/transport/http_transport_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down