Skip to content

Commit

Permalink
Sidecar support in Sentry-Ruby
Browse files Browse the repository at this point in the history
  • Loading branch information
natikgadzhi committed Nov 27, 2023
1 parent 4c8110c commit 3026b9b
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 0 deletions.
1 change: 1 addition & 0 deletions sentry-ruby/lib/sentry-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
require "sentry/background_worker"
require "sentry/session_flusher"
require "sentry/cron/monitor_check_ins"
require "sentry/spotlight"

[
"sentry/rake",
Expand Down
7 changes: 7 additions & 0 deletions sentry-ruby/lib/sentry/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require "sentry/dsn"
require "sentry/release_detector"
require "sentry/transport/configuration"
require "sentry/spotlight/configuration"
require "sentry/linecache"
require "sentry/interfaces/stacktrace_builder"

Expand Down Expand Up @@ -234,6 +235,10 @@ def capture_exception_frame_locals=(value)
# @return [Boolean, nil]
attr_reader :enable_tracing

# Returns the Spotlight::Configuration object
# @return [Spotlight::Configuration]
attr_reader :spotlight

# Send diagnostic client reports about dropped events, true by default
# tries to attach to an existing envelope max once every 30s
# @return [Boolean]
Expand Down Expand Up @@ -358,6 +363,8 @@ def initialize
@transport = Transport::Configuration.new
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)

@spotlight = Spotlight::Configuration.new

run_post_initialization_callbacks
end

Expand Down
2 changes: 2 additions & 0 deletions sentry-ruby/lib/sentry/spotlight.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require "sentry/spotlight/configuration"
require "sentry/spotlight/transport"
50 changes: 50 additions & 0 deletions sentry-ruby/lib/sentry/spotlight/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Sentry
module Spotlight
# Sentry Spotlight configuration.
class Configuration

# When enabled, Sentry will send all events and traces to the provided
# Spotlight Sidecar URL.
# Defaults to false.
# @return [Boolean]
attr_reader :enabled

# Spotlight Sidecar URL as a String.
# Defaults to "http://localhost:8969/stream"
# @return [String]
attr_accessor :sidecar_url

def initialize
@enabled = false
@sidecar_url = "http://localhost:8969/stream"
end

def enabled?
enabled
end

# Enables or disables Spotlight.
def enabled=(value)
unless [true, false].include?(value)
raise ArgumentError, "Spotlight config.enabled must be a boolean"
end

if value == true
unless ['development', 'test'].include?(environment_from_env)
# Using the default logger here for a one-off warning.
::Sentry::Logger.new(STDOUT).warn("[Spotlight] Spotlight is enabled in a non-development environment!")
end
end
end

private

# TODO: Sentry::Configuration already reads the env the same way as below, but it also has a way to _set_ environment
# in it's config. So this introduces a bug where env could be different, depending on whether the user set the environment
# manually.
def environment_from_env
ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
end
end
end
end
77 changes: 77 additions & 0 deletions sentry-ruby/lib/sentry/spotlight/transport.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# frozen_string_literal: true

require "net/http"
require "zlib"

module Sentry
module Spotlight


# Spotlight Transport class is like HTTPTransport,
# but it's experimental, with limited featureset.
# - It does not care about rate limits, assuming working with local Sidecar proxy
# - Designed to just report events to Spotlight in development.
#
# TODO: This needs a cleanup, we could extract most of common code into a module.
class Transport

GZIP_ENCODING = "gzip"
GZIP_THRESHOLD = 1024 * 30
CONTENT_TYPE = 'application/x-sentry-envelope'
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"

# Initialize a new Spotlight transport
# with the provided Spotlight configuration.
def initialize(spotlight_configuration)
@configuration = spotlight_configuration
end

def send_data(data)
encoding = ""

if should_compress?(data)
data = Zlib.gzip(data)
encoding = GZIP_ENCODING
end

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(@configuration.sidecar_url, headers)
request.body = data
http.request(request)
end

unless response.code.match?(/\A2\d{2}/)
error_info = "the server responded with status #{response.code}"
error_info += "\nbody: #{response.body}"
error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']

raise Sentry::ExternalError, error_info
end
rescue SocketError => e
raise Sentry::ExternalError.new(e.message)
end

private

def should_compress?(data)
@transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
end

# Similar to HTTPTransport connection, but does not support Proxy and SSL
def conn
sidecar = URL(@configuration.sidecar_url)
connection = ::Net::HTTP.new(sidecar.hostname, sidecar.port, nil)
connection.use_ssl = false
connection
end

end
end
end
1 change: 1 addition & 0 deletions sentry-ruby/lib/sentry/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Transport
def initialize(configuration)
@logger = configuration.logger
@transport_configuration = configuration.transport
@spotlight_configuration = configuration.spotlight
@dsn = configuration.dsn
@rate_limits = {}
@send_client_reports = configuration.send_client_reports
Expand Down
6 changes: 6 additions & 0 deletions sentry-ruby/lib/sentry/transport/http_transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@ def initialize(*args)
@endpoint = @dsn.envelope_endpoint

log_debug("Sentry HTTP Transport will connect to #{@dsn.server}")

if @spotlight_configuration.enabled?
@spotlight_transport = Sentry::Spotlight::Transport.new(@transport_configuration)
end
end

def send_data(data)
@spotlight_transport.send_data(data) unless @spotlight_transport.nil?

encoding = ""

if should_compress?(data)
Expand Down
8 changes: 8 additions & 0 deletions sentry-ruby/spec/sentry/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,14 @@
end
end

describe "#spotlight" do
it "returns initialized Spotlight config by default" do
spotlight_config = subject.spotlight
expect(spotlight_config.enabled).to eq(false)
expect(spotlight_config.sidecar_url).to eq("http://localhost:8969/stream")
end
end

context 'configuring for async' do
it 'should be configurable to send events async' do
subject.async = ->(_e) { :ok }
Expand Down
29 changes: 29 additions & 0 deletions sentry-ruby/spec/sentry/transport/http_transport_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,33 @@
end
end
end

describe "Spotlight integration" do
let(:fake_response) { build_fake_response("200") }
context "when spotlight is enabled" do
let(:spotlight_transport) { Sentry::Spotlight::Transport.new(configuration.spotlight) }

it "calls @spotlight_transport.send_data(data)" do
configuration.spotlight.enabled = true

stub_request(fake_response)

subject.instance_variable_set(:@spotlight_transport, spotlight_transport)
expect( spotlight_transport ).to receive(:send_data).with(data)
subject.send_data(data)
end
end

context "when spotlight integration is disabled" do
let(:spotlight_transport) { nil }
it "does not call @spotlight_transport.send_data(data)" do
configuration.spotlight.enabled = false

stub_request(fake_response)

expect( spotlight_transport ).not_to receive(:send_data).with(data)
subject.send_data(data)
end
end
end
end

0 comments on commit 3026b9b

Please sign in to comment.