sentry-ruby 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 732989200a2cbf0a935c386cf84037d655332953ea2fef828099fbf68b756ab9
4
- data.tar.gz: 4aecc66958496eede273d4cc4b2e3861e0ce7eb3cf769912a4a6675efeba693e
3
+ metadata.gz: 517c3e0bc0ab170dbd508034bf74b3a9ba62a1a32f13a992ba84d65f81249e01
4
+ data.tar.gz: 8a86690e05e1695f15e2da33002ecd52b4596ef8dbd2e9c0a7461f94a8868906
5
5
  SHA512:
6
- metadata.gz: 798be41100ec3c8eb09039f9432df02fd0de323427fd0d8bf6d9ced8f7b39d420e972c6006bcb4490002e32f03acfad2311b8ddab826c6927bd53b2a7316c7cb
7
- data.tar.gz: 78cc7ec6d5c1a7f6603f9f2e4917b26e12ad5b13c0387a00645282acf8c910518c4132c171e2c9a8311f11e82afa3eb0454d2102bf48abd4c44d6625fd4ce4cf
6
+ metadata.gz: 3cc45e503a6ed19a5cc507ab0791087e432b612581498e84403fd806e0d9538ff02f57c2fbb64294ba9895514218bca05610f637a8708a752e6eaf42fa61bfaa
7
+ data.tar.gz: 9eb027f449b6bdee43316e3b78364d1680d56442b5f34ec82b49059a9f28418a51448e24e45318efb022e526b62e067e410a8e78eb3b67678f1e18ce46866004
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.0
4
+
5
+ - Separate rack integration [#1138](https://github.com/getsentry/sentry-ruby/pull/1138)
6
+ - Fixes [#1136](https://github.com/getsentry/sentry-ruby/pull/1136)
7
+ - Fix event sampling [#1144](https://github.com/getsentry/sentry-ruby/pull/1144)
8
+ - Merge & rename 2 Rack middlewares [#1147](https://github.com/getsentry/sentry-ruby/pull/1147)
9
+ - Fixes [#1153](https://github.com/getsentry/sentry-ruby/pull/1153)
10
+ - Removed `Sentry::Rack::Tracing` middleware and renamed `Sentry::Rack::CaptureException` to `Sentry::Rack::CaptureExceptions`.
11
+ - Deep-copy spans [#1148](https://github.com/getsentry/sentry-ruby/pull/1148)
12
+ - Move span recorder related code from Span to Transaction [#1149](https://github.com/getsentry/sentry-ruby/pull/1149)
13
+ - Check SDK initialization before running integrations [#1151](https://github.com/getsentry/sentry-ruby/pull/1151)
14
+ - Fixes [#1145](https://github.com/getsentry/sentry-ruby/pull/1145)
15
+ - Refactor transport [#1154](https://github.com/getsentry/sentry-ruby/pull/1154)
16
+ - Implement non-blocking event sending [#1155](https://github.com/getsentry/sentry-ruby/pull/1155)
17
+ - Added `background_worker_threads` configuration option.
18
+
19
+ ### Noticeable Changes
20
+
21
+ #### Middleware Changes
22
+
23
+ `Sentry::Rack::Tracing` is now removed. And `Sentry::Rack::CaptureException` has been renamed to `Sentry::Rack::CaptureExceptions`.
24
+
25
+ #### Events Are Sent Asynchronously
26
+
27
+ `sentry-ruby` now sends events asynchronously by default. The functionality works like this:
28
+
29
+ 1. When the SDK is initialized, a `Sentry::BackgroundWorker` will be initialized too.
30
+ 2. When an event is passed to `Client#capture_event`, instead of sending it directly with `Client#send_event`, we'll let the worker do it.
31
+ 3. The worker will have a number of threads. And the one of the idle threads will pick the job and call `Client#send_event`.
32
+ - If all the threads are busy, new jobs will be put into a queue, which has a limit of 30.
33
+ - If the queue size is exceeded, new events will be dropped.
34
+
35
+ However, if you still prefer to use your own async approach, that's totally fine. If you have `config.async` set, the worker won't initialize a thread pool and won't be used either.
36
+
37
+ This functionality also introduces a new `background_worker_threads` config option. It allows you to decide how many threads should the worker hold. By default, the value will be the number of the processors your machine has. For example, if your machine has 4 processors, the value would be 4.
38
+
39
+ Of course, you can always override the value to fit your use cases, like
40
+
41
+ ```ruby
42
+ config.background_worker_threads = 5 # the worker will have 5 threads for sending events
43
+ ```
44
+
45
+ You can also disable this new non-blocking behaviour by giving a `0` value:
46
+
47
+ ```ruby
48
+ config.background_worker_threads = 0 # all events will be sent synchronously
49
+ ```
50
+
3
51
  ## 4.0.1
4
52
 
5
53
  - Add rake integration: [1137](https://github.com/getsentry/sentry-ruby/pull/1137)
data/Gemfile CHANGED
@@ -8,7 +8,7 @@ gem "rspec", "~> 3.0"
8
8
  gem "codecov"
9
9
 
10
10
  gem "pry"
11
- gem "rack"
11
+ gem "rack" unless ENV["WITHOUT_RACK"] == "1"
12
12
 
13
13
  gem "benchmark-ips"
14
14
  gem "benchmark_driver"
data/README.md CHANGED
@@ -115,8 +115,7 @@ Sentry.init do |config|
115
115
  end
116
116
  end
117
117
 
118
- use Sentry::Rack::Tracing # this needs to be placed first
119
- use Sentry::Rack::CaptureException
118
+ use Sentry::Rack::CaptureExceptions
120
119
  ```
121
120
 
122
121
  Otherwise, Sentry you can always use the capture helpers manually
@@ -140,9 +139,9 @@ We also provide integrations with popular frameworks/libraries with the related
140
139
 
141
140
  You're all set - but there's a few more settings you may want to know about too!
142
141
 
143
- #### async
142
+ #### Blocking v.s. Non-blocking
144
143
 
145
- When an error or message occurs, the notification is immediately sent to Sentry. Sentry can be configured to send asynchronously:
144
+ **Before version 4.1.0**, `sentry-ruby` sends every event immediately. But it can be configured to send asynchronously:
146
145
 
147
146
  ```ruby
148
147
  config.async = lambda { |event|
@@ -166,6 +165,41 @@ class SentryJob < ActiveJob::Base
166
165
  end
167
166
  ```
168
167
 
168
+
169
+ **After version 4.1.0**, `sentry-ruby` sends events asynchronously by default. The functionality works like this:
170
+
171
+ 1. When the SDK is initialized, a `Sentry::BackgroundWorker` will be initialized too.
172
+ 2. When an event is passed to `Client#capture_event`, instead of sending it directly with `Client#send_event`, we'll let the worker do it.
173
+ 3. The worker will have a number of threads. And the one of the idle threads will pick the job and call `Client#send_event`.
174
+ - If all the threads are busy, new jobs will be put into a queue, which has a limit of 30.
175
+ - If the queue size is exceeded, new events will be dropped.
176
+
177
+ However, if you still prefer to use your own async approach, that's totally fine. If you have `config.async` set, the worker won't initialize a thread pool and won't be used either.
178
+
179
+ ##### About `Sentry::BackgroundWorker`
180
+
181
+ - The worker is built on top of the [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) gem's [ThreadPoolExecutor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadPoolExecutor.html), which is also used by Rails ActiveJob's async adapter. This should minimize the risk of messing up client applications with our own thread pool implementaion.
182
+
183
+ This functionality also introduces a new `background_worker_threads` config option. It allows you to decide how many threads should the worker hold. By default, the value will be the number of the processors your machine has. For example, if your machine has 4 processors, the value would be 4.
184
+
185
+ Of course, you can always override the value to fit your use cases, like
186
+
187
+ ```ruby
188
+ config.background_worker_threads = 5 # the worker will have 5 threads for sending events
189
+ ```
190
+
191
+ You can also disable this new non-blocking behaviour by giving a `0` value:
192
+
193
+ ```ruby
194
+ config.background_worker_threads = 0 # all events will be sent synchronously
195
+ ```
196
+
197
+ If you want to send a particular event immediately, you can use event hints to do it:
198
+
199
+ ```ruby
200
+ Sentry.capture_message("send me now!", hint: { background: false })
201
+ ```
202
+
169
203
  #### Contexts
170
204
 
171
205
  In sentry-ruby, every event will inherit their contextual data from the current scope. So you can enrich the event's data by configuring the current scope like:
@@ -191,7 +225,6 @@ Or use top-level setters
191
225
  Sentry.set_user(id: 1, email: "test@example.com")
192
226
  Sentry.set_tags(tag_1: "foo", tag_2: "bar")
193
227
  Sentry.set_extras(order_number: 1234, tickets_count: 4)
194
-
195
228
  ```
196
229
 
197
230
  Or build up a temporary scope for local information:
@@ -222,3 +255,4 @@ Sentry.capture_exception(exception, tags: {foo: "bar"})
222
255
  * [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues)
223
256
  * [Forum](https://forum.sentry.io/)
224
257
  - [Discord](https://discord.gg/ez5KZN7)
258
+
@@ -1,4 +1,5 @@
1
1
  require "forwardable"
2
+ require "time"
2
3
 
3
4
  require "sentry/version"
4
5
  require "sentry/core_ext/object/deep_dup"
@@ -9,7 +10,7 @@ require "sentry/transaction_event"
9
10
  require "sentry/span"
10
11
  require "sentry/transaction"
11
12
  require "sentry/hub"
12
- require "sentry/rack"
13
+ require "sentry/background_worker"
13
14
 
14
15
  def safely_require(lib)
15
16
  begin
@@ -19,6 +20,7 @@ def safely_require(lib)
19
20
  end
20
21
 
21
22
  safely_require "sentry/rake"
23
+ safely_require "sentry/rack"
22
24
 
23
25
  module Sentry
24
26
  class Error < StandardError
@@ -43,6 +45,8 @@ module Sentry
43
45
 
44
46
  def_delegators :get_current_scope, :set_tags, :set_extras, :set_user
45
47
 
48
+ attr_accessor :background_worker
49
+
46
50
  def init(&block)
47
51
  config = Configuration.new
48
52
  yield(config)
@@ -51,6 +55,11 @@ module Sentry
51
55
  hub = Hub.new(client, scope)
52
56
  Thread.current[THREAD_LOCAL] = hub
53
57
  @main_hub = hub
58
+ @background_worker = Sentry::BackgroundWorker.new(config)
59
+ end
60
+
61
+ def initialized?
62
+ !!@main_hub
54
63
  end
55
64
 
56
65
  def get_main_hub
@@ -0,0 +1,37 @@
1
+ require "concurrent/executor/thread_pool_executor"
2
+ require "concurrent/executor/immediate_executor"
3
+
4
+ module Sentry
5
+ class BackgroundWorker
6
+ attr_reader :max_queue, :number_of_threads
7
+
8
+ def initialize(configuration)
9
+ @max_queue = 30
10
+ @number_of_threads = configuration.background_worker_threads
11
+
12
+ @executor =
13
+ if configuration.async?
14
+ configuration.logger.debug(LOGGER_PROGNAME) { "config.async is set, BackgroundWorker is disabled" }
15
+ Concurrent::ImmediateExecutor.new
16
+ elsif @number_of_threads == 0
17
+ configuration.logger.debug(LOGGER_PROGNAME) { "config.background_worker_threads is set to 0, all events will be sent synchronously" }
18
+ Concurrent::ImmediateExecutor.new
19
+ else
20
+ configuration.logger.debug(LOGGER_PROGNAME) { "initialized a background worker with #{@number_of_threads} threads" }
21
+
22
+ Concurrent::ThreadPoolExecutor.new(
23
+ min_threads: 0,
24
+ max_threads: @number_of_threads,
25
+ max_queue: @max_queue,
26
+ fallback_policy: :discard
27
+ )
28
+ end
29
+ end
30
+
31
+ def perform(&block)
32
+ @executor.post do
33
+ block.call
34
+ end
35
+ end
36
+ end
37
+ end
@@ -20,7 +20,9 @@ module Sentry
20
20
  end
21
21
  end
22
22
 
23
- def capture_event(event, scope, hint = nil)
23
+ def capture_event(event, scope, hint = {})
24
+ return false unless configuration.sending_allowed?
25
+
24
26
  scope.apply_to_event(event, hint)
25
27
 
26
28
  if configuration.async?
@@ -33,7 +35,13 @@ module Sentry
33
35
  send_event(event, hint)
34
36
  end
35
37
  else
36
- send_event(event, hint)
38
+ if hint.fetch(:background, true)
39
+ Sentry.background_worker.perform do
40
+ send_event(event, hint)
41
+ end
42
+ else
43
+ send_event(event, hint)
44
+ end
37
45
  end
38
46
 
39
47
  event
@@ -64,8 +72,6 @@ module Sentry
64
72
  end
65
73
 
66
74
  def send_event(event, hint = nil)
67
- return false unless configuration.sending_allowed?
68
-
69
75
  event = configuration.before_send.call(event, hint) if configuration.before_send
70
76
  if event.nil?
71
77
  configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
@@ -1,3 +1,5 @@
1
+ require "concurrent/utility/processor_counter"
2
+
1
3
  require "sentry/utils/exception_cause_chain"
2
4
  require "sentry/dsn"
3
5
  require "sentry/transport/configuration"
@@ -15,6 +17,15 @@ module Sentry
15
17
  attr_reader :async
16
18
  alias async? async
17
19
 
20
+ # to send events in a non-blocking way, sentry-ruby has its own background worker
21
+ # by default, the worker holds a thread pool that has [the number of processors] threads
22
+ # but you can configure it with this configuration option
23
+ # E.g.: config.background_worker_threads = 5
24
+ #
25
+ # if you want to send events synchronously, set the value to 0
26
+ # E.g.: config.background_worker_threads = 0
27
+ attr_accessor :background_worker_threads
28
+
18
29
  # a proc/lambda that takes an array of stack traces
19
30
  # it'll be used to silence (reduce) backtrace of the exception
20
31
  #
@@ -146,6 +157,7 @@ module Sentry
146
157
 
147
158
  def initialize
148
159
  self.async = false
160
+ self.background_worker_threads = Concurrent.processor_count
149
161
  self.breadcrumbs_logger = []
150
162
  self.context_lines = 3
151
163
  self.environment = environment_from_env
@@ -97,7 +97,7 @@ module Sentry
97
97
  def capture_event(event, **options, &block)
98
98
  return unless current_client
99
99
 
100
- hint = options.delete(:hint)
100
+ hint = options.delete(:hint) || {}
101
101
  scope = current_scope.dup
102
102
 
103
103
  if block
@@ -1,5 +1,3 @@
1
- require 'rack'
2
-
3
1
  module Sentry
4
2
  class RequestInterface < Interface
5
3
  REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
@@ -18,25 +16,6 @@ module Sentry
18
16
  self.cookies = nil
19
17
  end
20
18
 
21
- def from_rack(env_hash)
22
- req = ::Rack::Request.new(env_hash)
23
-
24
- if Sentry.configuration.send_default_pii
25
- self.data = read_data_from(req)
26
- self.cookies = req.cookies
27
- else
28
- # need to completely wipe out ip addresses
29
- IP_HEADERS.each { |h| env_hash.delete(h) }
30
- end
31
-
32
- self.url = req.scheme && req.url.split('?').first
33
- self.method = req.request_method
34
- self.query_string = req.query_string
35
-
36
- self.headers = format_headers_for_sentry(env_hash)
37
- self.env = format_env_for_sentry(env_hash)
38
- end
39
-
40
19
  private
41
20
 
42
21
  # See Sentry server default limits at
@@ -1,5 +1,4 @@
1
- require 'time'
2
1
  require 'rack'
3
2
 
4
- require 'sentry/rack/capture_exception'
5
- require 'sentry/rack/tracing'
3
+ require 'sentry/rack/capture_exceptions'
4
+ require 'sentry/rack/interface'
@@ -1,45 +1,54 @@
1
1
  module Sentry
2
2
  module Rack
3
- class CaptureException
3
+ class CaptureExceptions
4
4
  def initialize(app)
5
5
  @app = app
6
6
  end
7
7
 
8
8
  def call(env)
9
- # this call clones the main (global) hub
10
- # and assigns it to the current thread's Sentry#get_current_hub
11
- # it's essential for multi-thread servers (e.g. puma)
12
- Sentry.clone_hub_to_current_thread unless Sentry.get_current_hub
13
- # this call creates an isolated scope for every request
14
- # it's essential for multi-process servers (e.g. unicorn)
9
+ return @app.call(env) unless Sentry.initialized?
10
+
11
+ # make sure the current thread has a clean hub
12
+ Sentry.clone_hub_to_current_thread
13
+
15
14
  Sentry.with_scope do |scope|
16
- # there could be some breadcrumbs already stored in the top-level scope
17
- # and for request information, we don't need those breadcrumbs
18
15
  scope.clear_breadcrumbs
19
- env['sentry.client'] = Sentry.get_current_client
20
-
21
16
  scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
22
17
  scope.set_rack_env(env)
23
18
 
19
+ span = Sentry.start_transaction(name: scope.transaction_name, op: "rack.request")
20
+ scope.set_span(span)
21
+
24
22
  begin
25
23
  response = @app.call(env)
26
24
  rescue Sentry::Error
25
+ finish_span(span, 500)
27
26
  raise # Don't capture Sentry errors
28
27
  rescue Exception => e
29
28
  Sentry.capture_exception(e)
29
+ finish_span(span, 500)
30
30
  raise
31
31
  end
32
32
 
33
33
  exception = collect_exception(env)
34
34
  Sentry.capture_exception(exception) if exception
35
35
 
36
+ finish_span(span, response[0])
37
+
36
38
  response
37
39
  end
38
40
  end
39
41
 
42
+ private
43
+
40
44
  def collect_exception(env)
41
45
  env['rack.exception'] || env['sinatra.error']
42
46
  end
47
+
48
+ def finish_span(span, status_code)
49
+ span.set_http_status(status_code)
50
+ span.finish
51
+ end
43
52
  end
44
53
  end
45
54
  end
@@ -0,0 +1,22 @@
1
+ module Sentry
2
+ class RequestInterface
3
+ def from_rack(env_hash)
4
+ req = ::Rack::Request.new(env_hash)
5
+
6
+ if Sentry.configuration.send_default_pii
7
+ self.data = read_data_from(req)
8
+ self.cookies = req.cookies
9
+ else
10
+ # need to completely wipe out ip addresses
11
+ IP_HEADERS.each { |h| env_hash.delete(h) }
12
+ end
13
+
14
+ self.url = req.scheme && req.url.split('?').first
15
+ self.method = req.request_method
16
+ self.query_string = req.query_string
17
+
18
+ self.headers = format_headers_for_sentry(env_hash)
19
+ self.env = format_env_for_sentry(env_hash)
20
+ end
21
+ end
22
+ end
@@ -5,11 +5,11 @@ module Rake
5
5
  class Application
6
6
  alias orig_display_error_messsage display_error_message
7
7
  def display_error_message(ex)
8
- Sentry.capture_exception(ex) do |scope|
8
+ Sentry.capture_exception(ex, hint: { background: false }) do |scope|
9
9
  task_name = top_level_tasks.join(' ')
10
10
  scope.set_transaction_name(task_name)
11
11
  scope.set_tag("rake_task", task_name)
12
- end
12
+ end if Sentry.initialized?
13
13
 
14
14
  orig_display_error_messsage(ex)
15
15
  end
@@ -29,7 +29,7 @@ module Sentry
29
29
  event.level = level
30
30
  event.transaction = transaction_names.last
31
31
  event.breadcrumbs = breadcrumbs
32
- event.rack_env = rack_env
32
+ event.rack_env = rack_env if rack_env
33
33
 
34
34
  unless @event_processors.empty?
35
35
  @event_processors.each do |processor_block|
@@ -57,7 +57,7 @@ module Sentry
57
57
  copy.user = user.deep_dup
58
58
  copy.transaction_names = transaction_names.deep_dup
59
59
  copy.fingerprint = fingerprint.deep_dup
60
- copy.span = span
60
+ copy.span = span.deep_dup
61
61
  copy
62
62
  end
63
63
 
@@ -35,11 +35,6 @@ module Sentry
35
35
  @tags = {}
36
36
  end
37
37
 
38
- def set_span_recorder
39
- @span_recorder = SpanRecorder.new(1000)
40
- @span_recorder.add(self)
41
- end
42
-
43
38
  def finish
44
39
  # already finished
45
40
  return if @timestamp
@@ -82,14 +77,7 @@ module Sentry
82
77
 
83
78
  def start_child(**options)
84
79
  options = options.dup.merge(trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
85
- child_span = Span.new(options)
86
- child_span.span_recorder = @span_recorder
87
-
88
- if @span_recorder && @sampled
89
- @span_recorder.add(child_span)
90
- end
91
-
92
- child_span
80
+ Span.new(options)
93
81
  end
94
82
 
95
83
  def with_child_span(**options, &block)
@@ -100,6 +88,10 @@ module Sentry
100
88
  child_span.finish
101
89
  end
102
90
 
91
+ def deep_dup
92
+ dup
93
+ end
94
+
103
95
  def set_op(op)
104
96
  @op = op
105
97
  end
@@ -136,20 +128,5 @@ module Sentry
136
128
  def set_tag(key, value)
137
129
  @tags[key] = value
138
130
  end
139
-
140
- class SpanRecorder
141
- attr_reader :max_length, :spans
142
-
143
- def initialize(max_length)
144
- @max_length = max_length
145
- @spans = []
146
- end
147
-
148
- def add(span)
149
- if @spans.count < @max_length
150
- @spans << span
151
- end
152
- end
153
- end
154
131
  end
155
132
  end
@@ -20,6 +20,11 @@ module Sentry
20
20
  set_span_recorder
21
21
  end
22
22
 
23
+ def set_span_recorder
24
+ @span_recorder = SpanRecorder.new(1000)
25
+ @span_recorder.add(self)
26
+ end
27
+
23
28
  def self.from_sentry_trace(sentry_trace, **options)
24
29
  return unless sentry_trace
25
30
 
@@ -37,6 +42,30 @@ module Sentry
37
42
  hash
38
43
  end
39
44
 
45
+ def start_child(**options)
46
+ child_span = super
47
+ child_span.span_recorder = @span_recorder
48
+
49
+ if @sampled
50
+ @span_recorder.add(child_span)
51
+ end
52
+
53
+ child_span
54
+ end
55
+
56
+ def deep_dup
57
+ copy = super
58
+ copy.set_span_recorder
59
+
60
+ @span_recorder.spans.each do |span|
61
+ # span_recorder's first span is the current span, which should not be added to the copy's spans
62
+ next if span == self
63
+ copy.span_recorder.add(span.dup)
64
+ end
65
+
66
+ copy
67
+ end
68
+
40
69
  def set_initial_sample_desicion(sampling_context = {})
41
70
  unless Sentry.configuration.tracing_enabled?
42
71
  @sampled = false
@@ -109,5 +138,20 @@ module Sentry
109
138
  result += " <#{@name}>" if @name
110
139
  result
111
140
  end
141
+
142
+ class SpanRecorder
143
+ attr_reader :max_length, :spans
144
+
145
+ def initialize(max_length)
146
+ @max_length = max_length
147
+ @spans = []
148
+ end
149
+
150
+ def add(span)
151
+ if @spans.count < @max_length
152
+ @spans << span
153
+ end
154
+ end
155
+ end
112
156
  end
113
157
  end
@@ -1,20 +1,17 @@
1
1
  require "json"
2
2
  require "base64"
3
- require "sentry/transport/state"
4
3
 
5
4
  module Sentry
6
5
  class Transport
7
6
  PROTOCOL_VERSION = '5'
8
7
  USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
9
- CONTENT_TYPE = 'application/json'
10
8
 
11
- attr_accessor :configuration, :state
9
+ attr_accessor :configuration
12
10
 
13
11
  def initialize(configuration)
14
12
  @configuration = configuration
15
13
  @transport_configuration = configuration.transport
16
14
  @dsn = configuration.dsn
17
- @state = State.new
18
15
  end
19
16
 
20
17
  def send_data(data, options = {})
@@ -22,13 +19,17 @@ module Sentry
22
19
  end
23
20
 
24
21
  def send_event(event)
25
- content_type, encoded_data = prepare_encoded_event(event)
22
+ unless configuration.sending_allowed?
23
+ configuration.logger.debug(LOGGER_PROGNAME) { "Event not sent: #{configuration.error_messages}" }
24
+ return
25
+ end
26
+
27
+ encoded_data = prepare_encoded_event(event)
26
28
 
27
29
  return nil unless encoded_data
28
30
 
29
- send_data(encoded_data, content_type: content_type)
31
+ send_data(encoded_data)
30
32
 
31
- state.success
32
33
  event
33
34
  rescue => e
34
35
  failed_for_exception(e, event)
@@ -57,7 +58,7 @@ module Sentry
57
58
  #{JSON.generate(event_hash)}
58
59
  ENVELOPE
59
60
 
60
- [CONTENT_TYPE, envelope]
61
+ envelope
61
62
  end
62
63
 
63
64
  private
@@ -66,27 +67,16 @@ module Sentry
66
67
  # Convert to hash
67
68
  event_hash = event.to_hash
68
69
 
69
- unless @state.should_try?
70
- failed_for_previous_failure(event_hash)
71
- return
72
- end
73
-
74
70
  event_id = event_hash[:event_id] || event_hash['event_id']
75
71
  configuration.logger.info(LOGGER_PROGNAME) { "Sending event #{event_id} to Sentry" }
76
72
  encode(event_hash)
77
73
  end
78
74
 
79
75
  def failed_for_exception(e, event)
80
- @state.failure
81
76
  configuration.logger.warn(LOGGER_PROGNAME) { "Unable to record event with remote Sentry server (#{e.class} - #{e.message}):\n#{e.backtrace[0..10].join("\n")}" }
82
77
  log_not_sending(event)
83
78
  end
84
79
 
85
- def failed_for_previous_failure(event)
86
- configuration.logger.warn(LOGGER_PROGNAME) { "Not sending event due to previous failure(s)." }
87
- log_not_sending(event)
88
- end
89
-
90
80
  def log_not_sending(event)
91
81
  configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event. Unreported Event: #{Event.get_log_message(event.to_hash)}" }
92
82
  end
@@ -2,6 +2,7 @@ require 'faraday'
2
2
 
3
3
  module Sentry
4
4
  class HTTPTransport < Transport
5
+ CONTENT_TYPE = 'application/json'
5
6
  attr_reader :conn, :adapter
6
7
 
7
8
  def initialize(*args)
@@ -11,13 +12,9 @@ module Sentry
11
12
  @endpoint = @dsn.envelope_endpoint
12
13
  end
13
14
 
14
- def send_data(data, options = {})
15
- unless configuration.sending_allowed?
16
- logger.debug(LOGGER_PROGNAME) { "Event not sent: #{configuration.error_messages}" }
17
- end
18
-
15
+ def send_data(data)
19
16
  conn.post @endpoint do |req|
20
- req.headers['Content-Type'] = options[:content_type]
17
+ req.headers['Content-Type'] = CONTENT_TYPE
21
18
  req.headers['X-Sentry-Auth'] = generate_auth_header
22
19
  req.body = data
23
20
  end
@@ -1,3 +1,3 @@
1
1
  module Sentry
2
- VERSION = "4.0.1"
2
+ VERSION = "4.1.0"
3
3
  end
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
 
25
25
  spec.add_dependency "faraday", ">= 1.0"
26
+ spec.add_dependency "concurrent-ruby"
26
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-11 00:00:00.000000000 Z
11
+ date: 2020-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: A gem that provides a client interface for the Sentry error logger
28
42
  email: accounts@sentry.io
29
43
  executables: []
@@ -45,6 +59,7 @@ files:
45
59
  - bin/console
46
60
  - bin/setup
47
61
  - lib/sentry-ruby.rb
62
+ - lib/sentry/background_worker.rb
48
63
  - lib/sentry/backtrace.rb
49
64
  - lib/sentry/benchmarks/benchmark_transport.rb
50
65
  - lib/sentry/breadcrumb.rb
@@ -65,8 +80,8 @@ files:
65
80
  - lib/sentry/linecache.rb
66
81
  - lib/sentry/logger.rb
67
82
  - lib/sentry/rack.rb
68
- - lib/sentry/rack/capture_exception.rb
69
- - lib/sentry/rack/tracing.rb
83
+ - lib/sentry/rack/capture_exceptions.rb
84
+ - lib/sentry/rack/interface.rb
70
85
  - lib/sentry/rake.rb
71
86
  - lib/sentry/scope.rb
72
87
  - lib/sentry/span.rb
@@ -76,7 +91,6 @@ files:
76
91
  - lib/sentry/transport/configuration.rb
77
92
  - lib/sentry/transport/dummy_transport.rb
78
93
  - lib/sentry/transport/http_transport.rb
79
- - lib/sentry/transport/state.rb
80
94
  - lib/sentry/utils/exception_cause_chain.rb
81
95
  - lib/sentry/utils/real_ip.rb
82
96
  - lib/sentry/utils/request_id.rb
@@ -1,39 +0,0 @@
1
- module Sentry
2
- module Rack
3
- class Tracing
4
- def initialize(app)
5
- @app = app
6
- end
7
-
8
- def call(env)
9
- Sentry.clone_hub_to_current_thread unless Sentry.get_current_hub
10
-
11
- if Sentry.configuration.traces_sample_rate.to_f == 0.0
12
- return @app.call(env)
13
- end
14
-
15
- Sentry.with_scope do |scope|
16
- scope.clear_breadcrumbs
17
- scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
18
- span = Sentry.start_transaction(name: scope.transaction_name, op: "rack.request")
19
- scope.set_span(span)
20
-
21
- begin
22
- response = @app.call(env)
23
- rescue
24
- finish_span(span, 500)
25
- raise
26
- end
27
-
28
- finish_span(span, response[0])
29
- response
30
- end
31
- end
32
-
33
- def finish_span(span, status_code)
34
- span.set_http_status(status_code)
35
- span.finish
36
- end
37
- end
38
- end
39
- end
@@ -1,40 +0,0 @@
1
- module Sentry
2
- class Transport
3
- class State
4
- def initialize
5
- reset
6
- end
7
-
8
- def should_try?
9
- return true if @status == :online
10
-
11
- interval = @retry_after || [@retry_number, 6].min**2
12
- return true if Sentry.utc_now - @last_check >= interval
13
-
14
- false
15
- end
16
-
17
- def failure(retry_after = nil)
18
- @status = :error
19
- @retry_number += 1
20
- @last_check = Sentry.utc_now
21
- @retry_after = retry_after
22
- end
23
-
24
- def success
25
- reset
26
- end
27
-
28
- def reset
29
- @status = :online
30
- @retry_number = 0
31
- @last_check = nil
32
- @retry_after = nil
33
- end
34
-
35
- def failed?
36
- @status == :error
37
- end
38
- end
39
- end
40
- end