Skip to content

Commit 3026b9b

Browse files
committed
Sidecar support in Sentry-Ruby
1 parent 4c8110c commit 3026b9b

File tree

9 files changed

+181
-0
lines changed

9 files changed

+181
-0
lines changed

sentry-ruby/lib/sentry-ruby.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
require "sentry/background_worker"
2323
require "sentry/session_flusher"
2424
require "sentry/cron/monitor_check_ins"
25+
require "sentry/spotlight"
2526

2627
[
2728
"sentry/rake",

sentry-ruby/lib/sentry/configuration.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require "sentry/dsn"
88
require "sentry/release_detector"
99
require "sentry/transport/configuration"
10+
require "sentry/spotlight/configuration"
1011
require "sentry/linecache"
1112
require "sentry/interfaces/stacktrace_builder"
1213

@@ -234,6 +235,10 @@ def capture_exception_frame_locals=(value)
234235
# @return [Boolean, nil]
235236
attr_reader :enable_tracing
236237

238+
# Returns the Spotlight::Configuration object
239+
# @return [Spotlight::Configuration]
240+
attr_reader :spotlight
241+
237242
# Send diagnostic client reports about dropped events, true by default
238243
# tries to attach to an existing envelope max once every 30s
239244
# @return [Boolean]
@@ -358,6 +363,8 @@ def initialize
358363
@transport = Transport::Configuration.new
359364
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
360365

366+
@spotlight = Spotlight::Configuration.new
367+
361368
run_post_initialization_callbacks
362369
end
363370

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require "sentry/spotlight/configuration"
2+
require "sentry/spotlight/transport"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module Sentry
2+
module Spotlight
3+
# Sentry Spotlight configuration.
4+
class Configuration
5+
6+
# When enabled, Sentry will send all events and traces to the provided
7+
# Spotlight Sidecar URL.
8+
# Defaults to false.
9+
# @return [Boolean]
10+
attr_reader :enabled
11+
12+
# Spotlight Sidecar URL as a String.
13+
# Defaults to "http://localhost:8969/stream"
14+
# @return [String]
15+
attr_accessor :sidecar_url
16+
17+
def initialize
18+
@enabled = false
19+
@sidecar_url = "http://localhost:8969/stream"
20+
end
21+
22+
def enabled?
23+
enabled
24+
end
25+
26+
# Enables or disables Spotlight.
27+
def enabled=(value)
28+
unless [true, false].include?(value)
29+
raise ArgumentError, "Spotlight config.enabled must be a boolean"
30+
end
31+
32+
if value == true
33+
unless ['development', 'test'].include?(environment_from_env)
34+
# Using the default logger here for a one-off warning.
35+
::Sentry::Logger.new(STDOUT).warn("[Spotlight] Spotlight is enabled in a non-development environment!")
36+
end
37+
end
38+
end
39+
40+
private
41+
42+
# TODO: Sentry::Configuration already reads the env the same way as below, but it also has a way to _set_ environment
43+
# in it's config. So this introduces a bug where env could be different, depending on whether the user set the environment
44+
# manually.
45+
def environment_from_env
46+
ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
47+
end
48+
end
49+
end
50+
end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# frozen_string_literal: true
2+
3+
require "net/http"
4+
require "zlib"
5+
6+
module Sentry
7+
module Spotlight
8+
9+
10+
# Spotlight Transport class is like HTTPTransport,
11+
# but it's experimental, with limited featureset.
12+
# - It does not care about rate limits, assuming working with local Sidecar proxy
13+
# - Designed to just report events to Spotlight in development.
14+
#
15+
# TODO: This needs a cleanup, we could extract most of common code into a module.
16+
class Transport
17+
18+
GZIP_ENCODING = "gzip"
19+
GZIP_THRESHOLD = 1024 * 30
20+
CONTENT_TYPE = 'application/x-sentry-envelope'
21+
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
22+
23+
# Initialize a new Spotlight transport
24+
# with the provided Spotlight configuration.
25+
def initialize(spotlight_configuration)
26+
@configuration = spotlight_configuration
27+
end
28+
29+
def send_data(data)
30+
encoding = ""
31+
32+
if should_compress?(data)
33+
data = Zlib.gzip(data)
34+
encoding = GZIP_ENCODING
35+
end
36+
37+
headers = {
38+
'Content-Type' => CONTENT_TYPE,
39+
'Content-Encoding' => encoding,
40+
'X-Sentry-Auth' => generate_auth_header,
41+
'User-Agent' => USER_AGENT
42+
}
43+
44+
response = conn.start do |http|
45+
request = ::Net::HTTP::Post.new(@configuration.sidecar_url, headers)
46+
request.body = data
47+
http.request(request)
48+
end
49+
50+
unless response.code.match?(/\A2\d{2}/)
51+
error_info = "the server responded with status #{response.code}"
52+
error_info += "\nbody: #{response.body}"
53+
error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
54+
55+
raise Sentry::ExternalError, error_info
56+
end
57+
rescue SocketError => e
58+
raise Sentry::ExternalError.new(e.message)
59+
end
60+
61+
private
62+
63+
def should_compress?(data)
64+
@transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
65+
end
66+
67+
# Similar to HTTPTransport connection, but does not support Proxy and SSL
68+
def conn
69+
sidecar = URL(@configuration.sidecar_url)
70+
connection = ::Net::HTTP.new(sidecar.hostname, sidecar.port, nil)
71+
connection.use_ssl = false
72+
connection
73+
end
74+
75+
end
76+
end
77+
end

sentry-ruby/lib/sentry/transport.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Transport
3232
def initialize(configuration)
3333
@logger = configuration.logger
3434
@transport_configuration = configuration.transport
35+
@spotlight_configuration = configuration.spotlight
3536
@dsn = configuration.dsn
3637
@rate_limits = {}
3738
@send_client_reports = configuration.send_client_reports

sentry-ruby/lib/sentry/transport/http_transport.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@ def initialize(*args)
2929
@endpoint = @dsn.envelope_endpoint
3030

3131
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}")
32+
33+
if @spotlight_configuration.enabled?
34+
@spotlight_transport = Sentry::Spotlight::Transport.new(@transport_configuration)
35+
end
3236
end
3337

3438
def send_data(data)
39+
@spotlight_transport.send_data(data) unless @spotlight_transport.nil?
40+
3541
encoding = ""
3642

3743
if should_compress?(data)

sentry-ruby/spec/sentry/configuration_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,14 @@
256256
end
257257
end
258258

259+
describe "#spotlight" do
260+
it "returns initialized Spotlight config by default" do
261+
spotlight_config = subject.spotlight
262+
expect(spotlight_config.enabled).to eq(false)
263+
expect(spotlight_config.sidecar_url).to eq("http://localhost:8969/stream")
264+
end
265+
end
266+
259267
context 'configuring for async' do
260268
it 'should be configurable to send events async' do
261269
subject.async = ->(_e) { :ok }

sentry-ruby/spec/sentry/transport/http_transport_spec.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,33 @@
321321
end
322322
end
323323
end
324+
325+
describe "Spotlight integration" do
326+
let(:fake_response) { build_fake_response("200") }
327+
context "when spotlight is enabled" do
328+
let(:spotlight_transport) { Sentry::Spotlight::Transport.new(configuration.spotlight) }
329+
330+
it "calls @spotlight_transport.send_data(data)" do
331+
configuration.spotlight.enabled = true
332+
333+
stub_request(fake_response)
334+
335+
subject.instance_variable_set(:@spotlight_transport, spotlight_transport)
336+
expect( spotlight_transport ).to receive(:send_data).with(data)
337+
subject.send_data(data)
338+
end
339+
end
340+
341+
context "when spotlight integration is disabled" do
342+
let(:spotlight_transport) { nil }
343+
it "does not call @spotlight_transport.send_data(data)" do
344+
configuration.spotlight.enabled = false
345+
346+
stub_request(fake_response)
347+
348+
expect( spotlight_transport ).not_to receive(:send_data).with(data)
349+
subject.send_data(data)
350+
end
351+
end
352+
end
324353
end

0 commit comments

Comments
 (0)