sentry-ruby 4.0.0 → 4.1.3

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: 517bd830d9052aef4d3c9011f95e1d95f7740fae3bfdd746ece393766f637d84
4
- data.tar.gz: 0b25ac7ffe3abf0c8b0a153681952bd36315fe406341add2e7e724632ca74d35
3
+ metadata.gz: 823d9f50e4335e2c08fa6a6afedac78e2c6243ea94a104db560870f0021c51c7
4
+ data.tar.gz: 385143cfb2e2218afb9fca2bebe07a26bca02565cd525b32a1abdde38bcd53e0
5
5
  SHA512:
6
- metadata.gz: a717d3e5286400706e44f8bf1a17d6901e5fc7149efbff7f2f70758b1ac5c4f0f134f0d875a5543fa95777013aa1b0b0168f7c41fa149316bd9ced3a2f4ed48e
7
- data.tar.gz: ad58cf9e97b482ff9b3107a677d1bc03d3429bba26052f908ce43b25635985f65525b3345afccd1488d5c94eabb0c6565b1ce16d003859da2e359f6ccbdd1eb9
6
+ metadata.gz: 143855b2a274c65d17b72a60adbe8ff72b2d71ad316796d7899803d3bf28f4645cba7afa1518aabae592b0093ba51f5f3c41d755ce9674cd65b3da3363a5bb72
7
+ data.tar.gz: a4b8fc9795553867da3b5d6c0eebd6f2d4198805c21f19a936149653ddac882b4db5cba46678f016982f3529314a95d5a23e9770173f53dbeb33ff7ba6a4b6c7
@@ -1,5 +1,83 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.3
4
+
5
+ - rm reference to old constant (from Rails v2.2) [#1184](https://github.com/getsentry/sentry-ruby/pull/1184)
6
+ - Use copied env in events [#1186](https://github.com/getsentry/sentry-ruby/pull/1186)
7
+ - Fixes [#1183](https://github.com/getsentry/sentry-ruby/issues/1183)
8
+ - Refactor RequestInterface [#1187](https://github.com/getsentry/sentry-ruby/pull/1187)
9
+ - Supply event hint to async callback when possible [#1189](https://github.com/getsentry/sentry-ruby/pull/1189)
10
+ - Fixes [#1188](https://github.com/getsentry/sentry-ruby/issues/1188)
11
+ - Refactor stacktrace parsing and increase test coverage [#1190](https://github.com/getsentry/sentry-ruby/pull/1190)
12
+ - Sentry.send_event should also take a hint [#1192](https://github.com/getsentry/sentry-ruby/pull/1192)
13
+
14
+ ## 4.1.2
15
+
16
+ - before_send callback shouldn't be applied to transaction events [#1167](https://github.com/getsentry/sentry-ruby/pull/1167)
17
+ - Transaction improvements [#1170](https://github.com/getsentry/sentry-ruby/pull/1170)
18
+ - Support Ruby 3 [#1172](https://github.com/getsentry/sentry-ruby/pull/1172)
19
+ - Add Integrable module [#1177](https://github.com/getsentry/sentry-ruby/pull/1177)
20
+
21
+ ## 4.1.1
22
+
23
+ - Fix NoMethodError when sending is not allowed [#1161](https://github.com/getsentry/sentry-ruby/pull/1161)
24
+ - Add notification for users who still use deprecated middlewares [#1160](https://github.com/getsentry/sentry-ruby/pull/1160)
25
+ - Improve top-level api safety [#1162](https://github.com/getsentry/sentry-ruby/pull/1162)
26
+
27
+ ## 4.1.0
28
+
29
+ - Separate rack integration [#1138](https://github.com/getsentry/sentry-ruby/pull/1138)
30
+ - Fixes [#1136](https://github.com/getsentry/sentry-ruby/pull/1136)
31
+ - Fix event sampling [#1144](https://github.com/getsentry/sentry-ruby/pull/1144)
32
+ - Merge & rename 2 Rack middlewares [#1147](https://github.com/getsentry/sentry-ruby/pull/1147)
33
+ - Fixes [#1153](https://github.com/getsentry/sentry-ruby/pull/1153)
34
+ - Removed `Sentry::Rack::Tracing` middleware and renamed `Sentry::Rack::CaptureException` to `Sentry::Rack::CaptureExceptions`.
35
+ - Deep-copy spans [#1148](https://github.com/getsentry/sentry-ruby/pull/1148)
36
+ - Move span recorder related code from Span to Transaction [#1149](https://github.com/getsentry/sentry-ruby/pull/1149)
37
+ - Check SDK initialization before running integrations [#1151](https://github.com/getsentry/sentry-ruby/pull/1151)
38
+ - Fixes [#1145](https://github.com/getsentry/sentry-ruby/pull/1145)
39
+ - Refactor transport [#1154](https://github.com/getsentry/sentry-ruby/pull/1154)
40
+ - Implement non-blocking event sending [#1155](https://github.com/getsentry/sentry-ruby/pull/1155)
41
+ - Added `background_worker_threads` configuration option.
42
+
43
+ ### Noticeable Changes
44
+
45
+ #### Middleware Changes
46
+
47
+ `Sentry::Rack::Tracing` is now removed. And `Sentry::Rack::CaptureException` has been renamed to `Sentry::Rack::CaptureExceptions`.
48
+
49
+ #### Events Are Sent Asynchronously
50
+
51
+ `sentry-ruby` now sends events asynchronously by default. The functionality works like this:
52
+
53
+ 1. When the SDK is initialized, a `Sentry::BackgroundWorker` will be initialized too.
54
+ 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.
55
+ 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`.
56
+ - If all the threads are busy, new jobs will be put into a queue, which has a limit of 30.
57
+ - If the queue size is exceeded, new events will be dropped.
58
+
59
+ 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.
60
+
61
+ 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.
62
+
63
+ Of course, you can always override the value to fit your use cases, like
64
+
65
+ ```ruby
66
+ config.background_worker_threads = 5 # the worker will have 5 threads for sending events
67
+ ```
68
+
69
+ You can also disable this new non-blocking behaviour by giving a `0` value:
70
+
71
+ ```ruby
72
+ config.background_worker_threads = 0 # all events will be sent synchronously
73
+ ```
74
+
75
+ ## 4.0.1
76
+
77
+ - Add rake integration: [1137](https://github.com/getsentry/sentry-ruby/pull/1137)
78
+ - Make Event's interfaces accessible: [1135](https://github.com/getsentry/sentry-ruby/pull/1135)
79
+ - ActiveSupportLogger should only record events that has a started time: [1132](https://github.com/getsentry/sentry-ruby/pull/1132)
80
+
3
81
  ## 4.0.0
4
82
 
5
83
  - Only documents update for the official release and no API/feature changes.
data/Gemfile CHANGED
@@ -5,10 +5,10 @@ gemspec
5
5
 
6
6
  gem "rake", "~> 12.0"
7
7
  gem "rspec", "~> 3.0"
8
- gem "codecov"
8
+ gem "codecov", "0.2.12"
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
@@ -28,6 +28,10 @@ The official Ruby-language client and integration layer for the [Sentry](https:/
28
28
 
29
29
  We test on Ruby 2.4, 2.5, 2.6 and 2.7 at the latest patchlevel/teeny version. We also support JRuby 9.0.
30
30
 
31
+ ## Migrate From sentry-raven
32
+
33
+ If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You can find the benefits of migrating and how to do it in our [migration guide](https://docs.sentry.io/platforms/ruby/migration/).
34
+
31
35
  ## Getting Started
32
36
 
33
37
  ### Install
@@ -90,7 +94,7 @@ Sentry.init do |config|
90
94
  end
91
95
  ```
92
96
 
93
- To lean more about performance monitoring, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/performance).
97
+ To learn more about performance monitoring, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/performance).
94
98
 
95
99
  ### Usage
96
100
 
@@ -111,8 +115,7 @@ Sentry.init do |config|
111
115
  end
112
116
  end
113
117
 
114
- use Sentry::Rack::Tracing # this needs to be placed first
115
- use Sentry::Rack::CaptureException
118
+ use Sentry::Rack::CaptureExceptions
116
119
  ```
117
120
 
118
121
  Otherwise, Sentry you can always use the capture helpers manually
@@ -136,13 +139,13 @@ We also provide integrations with popular frameworks/libraries with the related
136
139
 
137
140
  You're all set - but there's a few more settings you may want to know about too!
138
141
 
139
- #### async
142
+ #### Blocking v.s. Non-blocking
140
143
 
141
- 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:
142
145
 
143
146
  ```ruby
144
- config.async = lambda { |event|
145
- Thread.new { Sentry.send_event(event) }
147
+ config.async = lambda { |event, hint|
148
+ Thread.new { Sentry.send_event(event, hint) }
146
149
  }
147
150
  ```
148
151
 
@@ -151,17 +154,52 @@ Using a thread to send events will be adequate for truly parallel Ruby platforms
151
154
  Note that the naive example implementation has a major drawback - it can create an infinite number of threads. We recommend creating a background job, using your background job processor, that will send Sentry notifications in the background.
152
155
 
153
156
  ```ruby
154
- config.async = lambda { |event| SentryJob.perform_later(event) }
157
+ config.async = lambda { |event, hint| SentryJob.perform_later(event, hint) }
155
158
 
156
159
  class SentryJob < ActiveJob::Base
157
160
  queue_as :default
158
161
 
159
- def perform(event)
160
- Sentry.send_event(event)
162
+ def perform(event, hint)
163
+ Sentry.send_event(event, hint)
161
164
  end
162
165
  end
163
166
  ```
164
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
+
165
203
  #### Contexts
166
204
 
167
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:
@@ -187,7 +225,6 @@ Or use top-level setters
187
225
  Sentry.set_user(id: 1, email: "test@example.com")
188
226
  Sentry.set_tags(tag_1: "foo", tag_2: "bar")
189
227
  Sentry.set_extras(order_number: 1234, tickets_count: 4)
190
-
191
228
  ```
192
229
 
193
230
  Or build up a temporary scope for local information:
@@ -218,3 +255,4 @@ Sentry.capture_exception(exception, tags: {foo: "bar"})
218
255
  * [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues)
219
256
  * [Forum](https://forum.sentry.io/)
220
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,17 @@ 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"
14
+
15
+ def safely_require(lib)
16
+ begin
17
+ require lib
18
+ rescue LoadError
19
+ end
20
+ end
21
+
22
+ safely_require "sentry/rake"
23
+ safely_require "sentry/rack"
13
24
 
14
25
  module Sentry
15
26
  class Error < StandardError
@@ -29,11 +40,25 @@ module Sentry
29
40
  Time.now.utc
30
41
  end
31
42
 
43
+ class << self
44
+ def integrations
45
+ @integrations ||= {}
46
+ end
47
+
48
+ def register_integration(name, version)
49
+ meta = { name: "sentry.ruby.#{name}", version: version }.freeze
50
+ integrations[name.to_s] = meta
51
+ end
52
+ end
53
+
32
54
  class << self
33
55
  extend Forwardable
34
56
 
57
+ def_delegators :get_current_client, :configuration, :send_event
35
58
  def_delegators :get_current_scope, :set_tags, :set_extras, :set_user
36
59
 
60
+ attr_accessor :background_worker
61
+
37
62
  def init(&block)
38
63
  config = Configuration.new
39
64
  yield(config)
@@ -42,6 +67,11 @@ module Sentry
42
67
  hub = Hub.new(client, scope)
43
68
  Thread.current[THREAD_LOCAL] = hub
44
69
  @main_hub = hub
70
+ @background_worker = Sentry::BackgroundWorker.new(config)
71
+ end
72
+
73
+ def initialized?
74
+ !!@main_hub
45
75
  end
46
76
 
47
77
  def get_main_hub
@@ -56,12 +86,8 @@ module Sentry
56
86
  get_current_scope.breadcrumbs.record(breadcrumb, &block)
57
87
  end
58
88
 
59
- def configuration
60
- get_current_client.configuration
61
- end
62
-
63
89
  def get_current_client
64
- get_current_hub.current_client
90
+ get_current_hub&.current_client
65
91
  end
66
92
 
67
93
  def get_current_hub
@@ -78,39 +104,35 @@ module Sentry
78
104
  end
79
105
 
80
106
  def get_current_scope
81
- get_current_hub.current_scope
107
+ get_current_hub&.current_scope
82
108
  end
83
109
 
84
110
  def with_scope(&block)
85
- get_current_hub.with_scope(&block)
111
+ get_current_hub&.with_scope(&block)
86
112
  end
87
113
 
88
114
  def configure_scope(&block)
89
- get_current_hub.configure_scope(&block)
90
- end
91
-
92
- def send_event(event)
93
- get_current_client.send_event(event)
115
+ get_current_hub&.configure_scope(&block)
94
116
  end
95
117
 
96
118
  def capture_event(event)
97
- get_current_hub.capture_event(event)
119
+ get_current_hub&.capture_event(event)
98
120
  end
99
121
 
100
122
  def capture_exception(exception, **options, &block)
101
- get_current_hub.capture_exception(exception, **options, &block)
123
+ get_current_hub&.capture_exception(exception, **options, &block)
102
124
  end
103
125
 
104
126
  def capture_message(message, **options, &block)
105
- get_current_hub.capture_message(message, **options, &block)
127
+ get_current_hub&.capture_message(message, **options, &block)
106
128
  end
107
129
 
108
130
  def start_transaction(**options)
109
- get_current_hub.start_transaction(**options)
131
+ get_current_hub&.start_transaction(**options)
110
132
  end
111
133
 
112
134
  def last_event_id
113
- get_current_hub.last_event_id
135
+ get_current_hub&.last_event_id
114
136
  end
115
137
 
116
138
  def sys_command(command)
@@ -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
@@ -82,16 +82,14 @@ module Sentry
82
82
 
83
83
  # holder for an Array of Backtrace::Line instances
84
84
  attr_reader :lines
85
- attr_reader :configuration
86
85
 
87
- def self.parse(backtrace, configuration:)
86
+ def self.parse(backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback)
88
87
  ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)
89
88
 
90
- ruby_lines = configuration.backtrace_cleanup_callback.call(ruby_lines) if configuration&.backtrace_cleanup_callback
89
+ ruby_lines = backtrace_cleanup_callback.call(ruby_lines) if backtrace_cleanup_callback
91
90
 
92
91
  in_app_pattern ||= begin
93
- project_root = configuration.project_root&.to_s
94
- Regexp.new("^(#{project_root}/)?#{configuration.app_dirs_pattern || APP_DIRS_PATTERN}")
92
+ Regexp.new("^(#{project_root}/)?#{app_dirs_pattern || APP_DIRS_PATTERN}")
95
93
  end
96
94
 
97
95
  lines = ruby_lines.to_a.map do |unparsed_line|
@@ -20,35 +20,51 @@ 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 unless configuration.sending_allowed?
25
+
24
26
  scope.apply_to_event(event, hint)
25
27
 
26
- if configuration.async?
28
+ if async_block = configuration.async
27
29
  begin
28
30
  # We have to convert to a JSON-like hash, because background job
29
31
  # processors (esp ActiveJob) may not like weird types in the event hash
30
- configuration.async.call(event.to_json_compatible)
32
+ event_hash = event.to_json_compatible
33
+
34
+ if async_block.arity == 2
35
+ async_block.call(event_hash, hint)
36
+ else
37
+ async_block.call(event_hash)
38
+ end
31
39
  rescue => e
32
40
  configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
33
41
  send_event(event, hint)
34
42
  end
35
43
  else
36
- send_event(event, hint)
44
+ if hint.fetch(:background, true)
45
+ Sentry.background_worker.perform do
46
+ send_event(event, hint)
47
+ end
48
+ else
49
+ send_event(event, hint)
50
+ end
37
51
  end
38
52
 
39
53
  event
40
54
  end
41
55
 
42
- def event_from_exception(exception)
56
+ def event_from_exception(exception, hint = {})
57
+ integration_meta = Sentry.integrations[hint[:integration]]
43
58
  return unless @configuration.exception_class_allowed?(exception)
44
59
 
45
- Event.new(configuration: configuration).tap do |event|
60
+ Event.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
46
61
  event.add_exception_interface(exception)
47
62
  end
48
63
  end
49
64
 
50
- def event_from_message(message)
51
- Event.new(configuration: configuration, message: message)
65
+ def event_from_message(message, hint = {})
66
+ integration_meta = Sentry.integrations[hint[:integration]]
67
+ Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
52
68
  end
53
69
 
54
70
  def event_from_transaction(transaction)
@@ -64,9 +80,9 @@ module Sentry
64
80
  end
65
81
 
66
82
  def send_event(event, hint = nil)
67
- return false unless configuration.sending_allowed?
83
+ event_type = event.is_a?(Event) ? event.type : event["type"]
84
+ event = configuration.before_send.call(event, hint) if configuration.before_send && event_type == "event"
68
85
 
69
- event = configuration.before_send.call(event, hint) if configuration.before_send
70
86
  if event.nil?
71
87
  configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
72
88
  return