sentry-ruby 5.10.0 → 5.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +12 -13
  4. data/README.md +26 -11
  5. data/Rakefile +9 -11
  6. data/bin/console +2 -0
  7. data/lib/sentry/attachment.rb +40 -0
  8. data/lib/sentry/background_worker.rb +11 -5
  9. data/lib/sentry/backpressure_monitor.rb +45 -0
  10. data/lib/sentry/backtrace.rb +12 -9
  11. data/lib/sentry/baggage.rb +7 -7
  12. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  13. data/lib/sentry/breadcrumb.rb +13 -6
  14. data/lib/sentry/check_in_event.rb +61 -0
  15. data/lib/sentry/client.rb +214 -25
  16. data/lib/sentry/configuration.rb +221 -38
  17. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  18. data/lib/sentry/cron/configuration.rb +23 -0
  19. data/lib/sentry/cron/monitor_check_ins.rb +77 -0
  20. data/lib/sentry/cron/monitor_config.rb +53 -0
  21. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  22. data/lib/sentry/dsn.rb +4 -4
  23. data/lib/sentry/envelope/item.rb +88 -0
  24. data/lib/sentry/envelope.rb +2 -68
  25. data/lib/sentry/error_event.rb +2 -2
  26. data/lib/sentry/event.rb +28 -47
  27. data/lib/sentry/excon/middleware.rb +77 -0
  28. data/lib/sentry/excon.rb +10 -0
  29. data/lib/sentry/faraday.rb +77 -0
  30. data/lib/sentry/graphql.rb +9 -0
  31. data/lib/sentry/hub.rb +138 -6
  32. data/lib/sentry/integrable.rb +10 -0
  33. data/lib/sentry/interface.rb +1 -0
  34. data/lib/sentry/interfaces/exception.rb +5 -3
  35. data/lib/sentry/interfaces/mechanism.rb +20 -0
  36. data/lib/sentry/interfaces/request.rb +8 -8
  37. data/lib/sentry/interfaces/single_exception.rb +13 -9
  38. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  39. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  40. data/lib/sentry/linecache.rb +3 -3
  41. data/lib/sentry/log_event.rb +206 -0
  42. data/lib/sentry/log_event_buffer.rb +75 -0
  43. data/lib/sentry/logger.rb +1 -1
  44. data/lib/sentry/metrics/aggregator.rb +248 -0
  45. data/lib/sentry/metrics/configuration.rb +47 -0
  46. data/lib/sentry/metrics/counter_metric.rb +25 -0
  47. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  48. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  49. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  50. data/lib/sentry/metrics/metric.rb +19 -0
  51. data/lib/sentry/metrics/set_metric.rb +28 -0
  52. data/lib/sentry/metrics/timing.rb +51 -0
  53. data/lib/sentry/metrics.rb +56 -0
  54. data/lib/sentry/net/http.rb +27 -44
  55. data/lib/sentry/profiler/helpers.rb +46 -0
  56. data/lib/sentry/profiler.rb +41 -60
  57. data/lib/sentry/propagation_context.rb +135 -0
  58. data/lib/sentry/puma.rb +12 -5
  59. data/lib/sentry/rack/capture_exceptions.rb +17 -8
  60. data/lib/sentry/rack.rb +2 -2
  61. data/lib/sentry/rake.rb +4 -15
  62. data/lib/sentry/redis.rb +10 -4
  63. data/lib/sentry/release_detector.rb +5 -5
  64. data/lib/sentry/rspec.rb +91 -0
  65. data/lib/sentry/scope.rb +75 -39
  66. data/lib/sentry/session.rb +2 -2
  67. data/lib/sentry/session_flusher.rb +15 -43
  68. data/lib/sentry/span.rb +92 -8
  69. data/lib/sentry/std_lib_logger.rb +50 -0
  70. data/lib/sentry/structured_logger.rb +138 -0
  71. data/lib/sentry/test_helper.rb +42 -13
  72. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  73. data/lib/sentry/transaction.rb +44 -43
  74. data/lib/sentry/transaction_event.rb +10 -6
  75. data/lib/sentry/transport/configuration.rb +73 -1
  76. data/lib/sentry/transport/http_transport.rb +71 -41
  77. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  78. data/lib/sentry/transport.rb +53 -49
  79. data/lib/sentry/utils/argument_checking_helper.rb +12 -0
  80. data/lib/sentry/utils/env_helper.rb +21 -0
  81. data/lib/sentry/utils/http_tracing.rb +74 -0
  82. data/lib/sentry/utils/logging_helper.rb +10 -7
  83. data/lib/sentry/utils/real_ip.rb +2 -2
  84. data/lib/sentry/utils/request_id.rb +1 -1
  85. data/lib/sentry/utils/uuid.rb +13 -0
  86. data/lib/sentry/vernier/output.rb +89 -0
  87. data/lib/sentry/vernier/profiler.rb +132 -0
  88. data/lib/sentry/version.rb +1 -1
  89. data/lib/sentry-ruby.rb +206 -35
  90. data/sentry-ruby-core.gemspec +3 -1
  91. data/sentry-ruby.gemspec +15 -6
  92. metadata +61 -11
@@ -2,21 +2,18 @@
2
2
 
3
3
  require "sentry/baggage"
4
4
  require "sentry/profiler"
5
+ require "sentry/propagation_context"
5
6
 
6
7
  module Sentry
7
8
  class Transaction < Span
8
- SENTRY_TRACE_REGEXP = Regexp.new(
9
- "^[ \t]*" + # whitespace
10
- "([0-9a-f]{32})?" + # trace_id
11
- "-?([0-9a-f]{16})?" + # span_id
12
- "-?([01])?" + # sampled
13
- "[ \t]*$" # whitespace
14
- )
15
- UNLABELD_NAME = "<unlabeled transaction>".freeze
9
+ # @deprecated Use Sentry::PropagationContext::SENTRY_TRACE_REGEXP instead.
10
+ SENTRY_TRACE_REGEXP = PropagationContext::SENTRY_TRACE_REGEXP
11
+
12
+ UNLABELD_NAME = "<unlabeled transaction>"
16
13
  MESSAGE_PREFIX = "[Tracing]"
17
14
 
18
15
  # https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
19
- SOURCES = %i(custom url route view component task)
16
+ SOURCES = %i[custom url route view component task]
20
17
 
21
18
  include LoggingHelper
22
19
 
@@ -48,9 +45,6 @@ module Sentry
48
45
  # @deprecated Use Sentry.configuration instead.
49
46
  attr_reader :configuration
50
47
 
51
- # @deprecated Use Sentry.logger instead.
52
- attr_reader :logger
53
-
54
48
  # The effective sample rate at which this transaction was sampled.
55
49
  # @return [Float, nil]
56
50
  attr_reader :effective_sample_rate
@@ -81,17 +75,23 @@ module Sentry
81
75
  @tracing_enabled = hub.configuration.tracing_enabled?
82
76
  @traces_sampler = hub.configuration.traces_sampler
83
77
  @traces_sample_rate = hub.configuration.traces_sample_rate
84
- @logger = hub.configuration.logger
78
+ @sdk_logger = hub.configuration.sdk_logger
85
79
  @release = hub.configuration.release
86
80
  @environment = hub.configuration.environment
87
81
  @dsn = hub.configuration.dsn
88
82
  @effective_sample_rate = nil
89
83
  @contexts = {}
90
84
  @measurements = {}
91
- @profiler = Profiler.new(@configuration)
85
+
86
+ unless @hub.profiler_running?
87
+ @profiler = @configuration.profiler_class.new(@configuration)
88
+ end
89
+
92
90
  init_span_recorder
93
91
  end
94
92
 
93
+ # @deprecated use Sentry.continue_trace instead.
94
+ #
95
95
  # Initalizes a Transaction instance with a Sentry trace string from another transaction (usually from an external request).
96
96
  #
97
97
  # The original transaction will become the parent of the new Transaction instance. And they will share the same `trace_id`.
@@ -111,14 +111,15 @@ module Sentry
111
111
 
112
112
  trace_id, parent_span_id, parent_sampled = sentry_trace_data
113
113
 
114
- baggage = if baggage && !baggage.empty?
115
- Baggage.from_incoming_header(baggage)
116
- else
117
- # If there's an incoming sentry-trace but no incoming baggage header,
118
- # for instance in traces coming from older SDKs,
119
- # baggage will be empty and frozen and won't be populated as head SDK.
120
- Baggage.new({})
121
- end
114
+ baggage =
115
+ if baggage && !baggage.empty?
116
+ Baggage.from_incoming_header(baggage)
117
+ else
118
+ # If there's an incoming sentry-trace but no incoming baggage header,
119
+ # for instance in traces coming from older SDKs,
120
+ # baggage will be empty and frozen and won't be populated as head SDK.
121
+ Baggage.new({})
122
+ end
122
123
 
123
124
  baggage.freeze!
124
125
 
@@ -132,18 +133,10 @@ module Sentry
132
133
  )
133
134
  end
134
135
 
135
- # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
136
- #
137
- # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
136
+ # @deprecated Use Sentry::PropagationContext.extract_sentry_trace instead.
138
137
  # @return [Array, nil]
139
138
  def self.extract_sentry_trace(sentry_trace)
140
- match = SENTRY_TRACE_REGEXP.match(sentry_trace)
141
- return nil if match.nil?
142
-
143
- trace_id, parent_span_id, sampled_flag = match[1..3]
144
- parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
145
-
146
- [trace_id, parent_span_id, parent_sampled]
139
+ PropagationContext.extract_sentry_trace(sentry_trace)
147
140
  end
148
141
 
149
142
  # @return [Hash]
@@ -227,7 +220,12 @@ module Sentry
227
220
  if sample_rate == true
228
221
  @sampled = true
229
222
  else
230
- @sampled = Random.rand < sample_rate
223
+ if Sentry.backpressure_monitor
224
+ factor = Sentry.backpressure_monitor.downsample_factor
225
+ @effective_sample_rate /= 2**factor
226
+ end
227
+
228
+ @sampled = Random.rand < @effective_sample_rate
231
229
  end
232
230
 
233
231
  if @sampled
@@ -260,13 +258,16 @@ module Sentry
260
258
  @name = UNLABELD_NAME
261
259
  end
262
260
 
263
- @profiler.stop
261
+ @hub.stop_profiler!(self)
264
262
 
265
263
  if @sampled
266
264
  event = hub.current_client.event_from_transaction(self)
267
265
  hub.capture_event(event)
268
266
  else
269
- hub.current_client.transport.record_lost_event(:sample_rate, 'transaction')
267
+ is_backpressure = Sentry.backpressure_monitor&.downsample_factor&.positive?
268
+ reason = is_backpressure ? :backpressure : :sample_rate
269
+ hub.current_client.transport.record_lost_event(reason, "transaction")
270
+ hub.current_client.transport.record_lost_event(reason, "span")
270
271
  end
271
272
  end
272
273
 
@@ -299,10 +300,17 @@ module Sentry
299
300
  # Start the profiler.
300
301
  # @return [void]
301
302
  def start_profiler!
303
+ return unless profiler
304
+
302
305
  profiler.set_initial_sample_decision(sampled)
303
306
  profiler.start
304
307
  end
305
308
 
309
+ # These are high cardinality and thus bad
310
+ def source_low_quality?
311
+ source == :url
312
+ end
313
+
306
314
  protected
307
315
 
308
316
  def init_span_recorder(limit = 1000)
@@ -323,6 +331,7 @@ module Sentry
323
331
  items = {
324
332
  "trace_id" => trace_id,
325
333
  "sample_rate" => effective_sample_rate&.to_s,
334
+ "sampled" => sampled&.to_s,
326
335
  "environment" => @environment,
327
336
  "release" => @release,
328
337
  "public_key" => @dsn&.public_key
@@ -330,18 +339,10 @@ module Sentry
330
339
 
331
340
  items["transaction"] = name unless source_low_quality?
332
341
 
333
- user = @hub.current_scope&.user
334
- items["user_segment"] = user["segment"] if user && user["segment"]
335
-
336
342
  items.compact!
337
343
  @baggage = Baggage.new(items, mutable: false)
338
344
  end
339
345
 
340
- # These are high cardinality and thus bad
341
- def source_low_quality?
342
- source == :url
343
- end
344
-
345
346
  class SpanRecorder
346
347
  attr_reader :max_length, :spans
347
348
 
@@ -8,9 +8,6 @@ module Sentry
8
8
  # @return [<Array[Span]>]
9
9
  attr_accessor :spans
10
10
 
11
- # @return [Hash, nil]
12
- attr_accessor :dynamic_sampling_context
13
-
14
11
  # @return [Hash]
15
12
  attr_accessor :measurements
16
13
 
@@ -20,6 +17,9 @@ module Sentry
20
17
  # @return [Hash, nil]
21
18
  attr_accessor :profile
22
19
 
20
+ # @return [Hash, nil]
21
+ attr_accessor :metrics_summary
22
+
23
23
  def initialize(transaction:, **options)
24
24
  super(**options)
25
25
 
@@ -32,6 +32,7 @@ module Sentry
32
32
  self.tags = transaction.tags
33
33
  self.dynamic_sampling_context = transaction.get_baggage.dynamic_sampling_context
34
34
  self.measurements = transaction.measurements
35
+ self.metrics_summary = transaction.metrics_summary
35
36
 
36
37
  finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
37
38
  self.spans = finished_spans.map(&:to_hash)
@@ -52,13 +53,17 @@ module Sentry
52
53
  data[:spans] = @spans.map(&:to_hash) if @spans
53
54
  data[:start_timestamp] = @start_timestamp
54
55
  data[:measurements] = @measurements
56
+ data[:_metrics_summary] = @metrics_summary if @metrics_summary
55
57
  data
56
58
  end
57
59
 
58
60
  private
59
61
 
62
+ EMPTY_PROFILE = {}.freeze
63
+
60
64
  def populate_profile(transaction)
61
- profile_hash = transaction.profiler.to_hash
65
+ profile_hash = transaction.profiler&.to_hash || EMPTY_PROFILE
66
+
62
67
  return if profile_hash.empty?
63
68
 
64
69
  profile_hash.merge!(
@@ -72,8 +77,7 @@ module Sentry
72
77
  id: event_id,
73
78
  name: transaction.name,
74
79
  trace_id: transaction.trace_id,
75
- # TODO-neel-profiler stubbed for now, see thread_id note in profiler.rb
76
- active_thead_id: '0'
80
+ active_thread_id: transaction.profiler.active_thread_id.to_s
77
81
  }
78
82
  )
79
83
 
@@ -3,7 +3,79 @@
3
3
  module Sentry
4
4
  class Transport
5
5
  class Configuration
6
- attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :encoding
6
+ # The timeout in seconds to open a connection to Sentry, in seconds.
7
+ # Default value is 2.
8
+ #
9
+ # @return [Integer]
10
+ attr_accessor :timeout
11
+
12
+ # The timeout in seconds to read data from Sentry, in seconds.
13
+ # Default value is 1.
14
+ #
15
+ # @return [Integer]
16
+ attr_accessor :open_timeout
17
+
18
+ # The proxy configuration to use to connect to Sentry.
19
+ # Accepts either a URI formatted string, URI, or a hash with the `uri`,
20
+ # `user`, and `password` keys.
21
+ #
22
+ # @example
23
+ # # setup proxy using a string:
24
+ # config.transport.proxy = "https://user:password@proxyhost:8080"
25
+ #
26
+ # # setup proxy using a URI:
27
+ # config.transport.proxy = URI("https://user:password@proxyhost:8080")
28
+ #
29
+ # # setup proxy using a hash:
30
+ # config.transport.proxy = {
31
+ # uri: URI("https://proxyhost:8080"),
32
+ # user: "user",
33
+ # password: "password"
34
+ # }
35
+ #
36
+ # If you're using the default transport (`Sentry::HTTPTransport`),
37
+ # proxy settings will also automatically be read from tne environment
38
+ # variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`).
39
+ #
40
+ # @return [String, URI, Hash, nil]
41
+ attr_accessor :proxy
42
+
43
+ # The SSL configuration to use to connect to Sentry.
44
+ # You can either pass a `Hash` containing `ca_file` and `verification` keys,
45
+ # or you can set those options directly on the `Sentry::HTTPTransport::Configuration` object:
46
+ #
47
+ # @example
48
+ # config.transport.ssl = {
49
+ # ca_file: "/path/to/ca_file",
50
+ # verification: true
51
+ # end
52
+ #
53
+ # @return [Hash, nil]
54
+ attr_accessor :ssl
55
+
56
+ # The path to the CA file to use to verify the SSL connection.
57
+ # Default value is `nil`.
58
+ #
59
+ # @return [String, nil]
60
+ attr_accessor :ssl_ca_file
61
+
62
+ # Whether to verify that the peer certificate is valid in SSL connections.
63
+ # Default value is `true`.
64
+ #
65
+ # @return [Boolean]
66
+ attr_accessor :ssl_verification
67
+
68
+ # The encoding to use to compress the request body.
69
+ # Default value is `Sentry::HTTPTransport::GZIP_ENCODING`.
70
+ #
71
+ # @return [String]
72
+ attr_accessor :encoding
73
+
74
+ # The class to use as a transport to connect to Sentry.
75
+ # If this option not set, it will return `nil`, and Sentry will use
76
+ # `Sentry::HTTPTransport` by default.
77
+ #
78
+ # @return [Class, nil]
7
79
  attr_reader :transport_class
8
80
 
9
81
  def initialize
@@ -7,18 +7,25 @@ module Sentry
7
7
  class HTTPTransport < Transport
8
8
  GZIP_ENCODING = "gzip"
9
9
  GZIP_THRESHOLD = 1024 * 30
10
- CONTENT_TYPE = 'application/x-sentry-envelope'
10
+ CONTENT_TYPE = "application/x-sentry-envelope"
11
11
 
12
12
  DEFAULT_DELAY = 60
13
13
  RETRY_AFTER_HEADER = "retry-after"
14
14
  RATE_LIMIT_HEADER = "x-sentry-rate-limits"
15
15
  USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
16
16
 
17
+ # The list of errors ::Net::HTTP is known to raise
18
+ # See https://github.com/ruby/ruby/blob/b0c639f249165d759596f9579fa985cb30533de6/lib/bundler/fetcher.rb#L281-L286
19
+ HTTP_ERRORS = [
20
+ Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH,
21
+ Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
22
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
23
+ Zlib::BufError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
24
+ ].freeze
25
+
17
26
  def initialize(*args)
18
27
  super
19
- @endpoint = @dsn.envelope_endpoint
20
-
21
- log_debug("Sentry HTTP Transport will connect to #{@dsn.server}")
28
+ log_debug("Sentry HTTP Transport will connect to #{@dsn.server}") if @dsn
22
29
  end
23
30
 
24
31
  def send_data(data)
@@ -30,36 +37,78 @@ module Sentry
30
37
  end
31
38
 
32
39
  headers = {
33
- 'Content-Type' => CONTENT_TYPE,
34
- 'Content-Encoding' => encoding,
35
- 'X-Sentry-Auth' => generate_auth_header,
36
- 'User-Agent' => USER_AGENT
40
+ "Content-Type" => CONTENT_TYPE,
41
+ "Content-Encoding" => encoding,
42
+ "User-Agent" => USER_AGENT
37
43
  }
38
44
 
45
+ auth_header = generate_auth_header
46
+ headers["X-Sentry-Auth"] = auth_header if auth_header
47
+
39
48
  response = conn.start do |http|
40
- request = ::Net::HTTP::Post.new(@endpoint, headers)
49
+ request = ::Net::HTTP::Post.new(endpoint, headers)
41
50
  request.body = data
42
51
  http.request(request)
43
52
  end
44
53
 
45
54
  if response.code.match?(/\A2\d{2}/)
46
- if has_rate_limited_header?(response)
47
- handle_rate_limited_response(response)
48
- end
55
+ handle_rate_limited_response(response) if has_rate_limited_header?(response)
56
+ elsif response.code == "429"
57
+ log_debug("the server responded with status 429")
58
+ handle_rate_limited_response(response)
49
59
  else
50
60
  error_info = "the server responded with status #{response.code}"
61
+ error_info += "\nbody: #{response.body}"
62
+ error_info += " Error in headers is: #{response['x-sentry-error']}" if response["x-sentry-error"]
63
+
64
+ raise Sentry::ExternalError, error_info
65
+ end
66
+ rescue SocketError, *HTTP_ERRORS => e
67
+ on_error if respond_to?(:on_error)
68
+ raise Sentry::ExternalError.new(e&.message)
69
+ end
70
+
71
+ def endpoint
72
+ @dsn.envelope_endpoint
73
+ end
74
+
75
+ def generate_auth_header
76
+ return nil unless @dsn
77
+
78
+ now = Sentry.utc_now.to_i
79
+ fields = {
80
+ "sentry_version" => PROTOCOL_VERSION,
81
+ "sentry_client" => USER_AGENT,
82
+ "sentry_timestamp" => now,
83
+ "sentry_key" => @dsn.public_key
84
+ }
85
+ fields["sentry_secret"] = @dsn.secret_key if @dsn.secret_key
86
+ "Sentry " + fields.map { |key, value| "#{key}=#{value}" }.join(", ")
87
+ end
88
+
89
+ def conn
90
+ server = URI(@dsn.server)
51
91
 
52
- if response.code == "429"
53
- handle_rate_limited_response(response)
92
+ # connection respects proxy setting from @transport_configuration, or environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
93
+ # Net::HTTP will automatically read the env vars.
94
+ # See https://ruby-doc.org/3.2.2/stdlibs/net/Net/HTTP.html#class-Net::HTTP-label-Proxies
95
+ connection =
96
+ if proxy = normalize_proxy(@transport_configuration.proxy)
97
+ ::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
54
98
  else
55
- error_info += "\nbody: #{response.body}"
56
- error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
99
+ ::Net::HTTP.new(server.hostname, server.port)
57
100
  end
58
101
 
59
- raise Sentry::ExternalError, error_info
102
+ connection.use_ssl = server.scheme == "https"
103
+ connection.read_timeout = @transport_configuration.timeout
104
+ connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
105
+ connection.open_timeout = @transport_configuration.open_timeout
106
+
107
+ ssl_configuration.each do |key, value|
108
+ connection.send("#{key}=", value)
60
109
  end
61
- rescue SocketError => e
62
- raise Sentry::ExternalError.new(e.message)
110
+
111
+ connection
63
112
  end
64
113
 
65
114
  private
@@ -126,28 +175,9 @@ module Sentry
126
175
  @transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
127
176
  end
128
177
 
129
- def conn
130
- server = URI(@dsn.server)
131
-
132
- connection =
133
- if proxy = normalize_proxy(@transport_configuration.proxy)
134
- ::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
135
- else
136
- ::Net::HTTP.new(server.hostname, server.port, nil)
137
- end
138
-
139
- connection.use_ssl = server.scheme == "https"
140
- connection.read_timeout = @transport_configuration.timeout
141
- connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
142
- connection.open_timeout = @transport_configuration.open_timeout
143
-
144
- ssl_configuration.each do |key, value|
145
- connection.send("#{key}=", value)
146
- end
147
-
148
- connection
149
- end
150
-
178
+ # @param proxy [String, URI, Hash] Proxy config value passed into `config.transport`.
179
+ # Accepts either a URI formatted string, URI, or a hash with the `uri`, `user`, and `password` keys.
180
+ # @return [Hash] Normalized proxy config that will be passed into `Net::HTTP`
151
181
  def normalize_proxy(proxy)
152
182
  return proxy unless proxy
153
183
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "zlib"
5
+
6
+ module Sentry
7
+ # Designed to just report events to Spotlight in development.
8
+ class SpotlightTransport < HTTPTransport
9
+ DEFAULT_SIDECAR_URL = "http://localhost:8969/stream"
10
+ MAX_FAILED_REQUESTS = 3
11
+
12
+ def initialize(configuration)
13
+ super
14
+ @sidecar_url = configuration.spotlight.is_a?(String) ? configuration.spotlight : DEFAULT_SIDECAR_URL
15
+ @failed = 0
16
+ @logged = false
17
+
18
+ log_debug("[Spotlight] initialized for url #{@sidecar_url}")
19
+ end
20
+
21
+ def endpoint
22
+ "/stream"
23
+ end
24
+
25
+ def send_data(data)
26
+ if @failed >= MAX_FAILED_REQUESTS
27
+ unless @logged
28
+ log_debug("[Spotlight] disabling because of too many request failures")
29
+ @logged = true
30
+ end
31
+
32
+ return
33
+ end
34
+
35
+ super
36
+ end
37
+
38
+ def on_error
39
+ @failed += 1
40
+ end
41
+
42
+ # Similar to HTTPTransport connection, but does not support Proxy and SSL
43
+ def conn
44
+ sidecar = URI(@sidecar_url)
45
+ connection = ::Net::HTTP.new(sidecar.hostname, sidecar.port, nil)
46
+ connection.use_ssl = false
47
+ connection
48
+ end
49
+ end
50
+ end