sentry-ruby 0.3.0 → 4.1.2

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: de5539f70eb6f49164765e4e13416bef15f8b697349f2d2e71f63ac57916f325
4
- data.tar.gz: 89121fce3fb31be7fe783599ee204ab86354f70a870ce775c79e4a33f6141e13
3
+ metadata.gz: cbe0af63148c410c71208f7bac743d24e224b5265102d645831a9538262961dc
4
+ data.tar.gz: 9933997d430fc94d400f97b9e2319ab14ccfe1bd2ff42635eda5b13bd4e11621
5
5
  SHA512:
6
- metadata.gz: ca35ae1d033f68fcebfe2c3e9e4b91fa2784a56261423a1bc5e5c7550b6304eb63963bf4273b22e924db27e1287f256e712b7e74781d866e71bae288308e13ae
7
- data.tar.gz: e5853d409d5bbbb4b60ecee8c1edc3c6de91971b39938c03a19895ce790246cc234e5441d848aaba745ccc90cc4bbd3f1df4eb3108c18ea68736dab41f2ca93f
6
+ metadata.gz: a813b4fad61840a850b6dd82451d689272800b5d4e405ab50f80f99cfe69a371f770c1a8151491ccd2f10400822e0e4a98bc8c2ca1265b562139bd7ffc1cdd02
7
+ data.tar.gz: 1331331801ea39234c96e50e9fc469713efd48ee316e5620ae2c200f97a4a4d88810f942d707fb77dab7b458284ee11cad4b6153632cde4aa6014e2df5a89820
@@ -1,5 +1,76 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.2
4
+
5
+ - before_send callback shouldn't be applied to transaction events [#1167](https://github.com/getsentry/sentry-ruby/pull/1167)
6
+ - Transaction improvements [#1170](https://github.com/getsentry/sentry-ruby/pull/1170)
7
+ - Support Ruby 3 [#1172](https://github.com/getsentry/sentry-ruby/pull/1172)
8
+ - Add Integrable module [#1177](https://github.com/getsentry/sentry-ruby/pull/1177)
9
+
10
+ ## 4.1.1
11
+
12
+ - Fix NoMethodError when sending is not allowed [#1161](https://github.com/getsentry/sentry-ruby/pull/1161)
13
+ - Add notification for users who still use deprecated middlewares [#1160](https://github.com/getsentry/sentry-ruby/pull/1160)
14
+ - Improve top-level api safety [#1162](https://github.com/getsentry/sentry-ruby/pull/1162)
15
+
16
+ ## 4.1.0
17
+
18
+ - Separate rack integration [#1138](https://github.com/getsentry/sentry-ruby/pull/1138)
19
+ - Fixes [#1136](https://github.com/getsentry/sentry-ruby/pull/1136)
20
+ - Fix event sampling [#1144](https://github.com/getsentry/sentry-ruby/pull/1144)
21
+ - Merge & rename 2 Rack middlewares [#1147](https://github.com/getsentry/sentry-ruby/pull/1147)
22
+ - Fixes [#1153](https://github.com/getsentry/sentry-ruby/pull/1153)
23
+ - Removed `Sentry::Rack::Tracing` middleware and renamed `Sentry::Rack::CaptureException` to `Sentry::Rack::CaptureExceptions`.
24
+ - Deep-copy spans [#1148](https://github.com/getsentry/sentry-ruby/pull/1148)
25
+ - Move span recorder related code from Span to Transaction [#1149](https://github.com/getsentry/sentry-ruby/pull/1149)
26
+ - Check SDK initialization before running integrations [#1151](https://github.com/getsentry/sentry-ruby/pull/1151)
27
+ - Fixes [#1145](https://github.com/getsentry/sentry-ruby/pull/1145)
28
+ - Refactor transport [#1154](https://github.com/getsentry/sentry-ruby/pull/1154)
29
+ - Implement non-blocking event sending [#1155](https://github.com/getsentry/sentry-ruby/pull/1155)
30
+ - Added `background_worker_threads` configuration option.
31
+
32
+ ### Noticeable Changes
33
+
34
+ #### Middleware Changes
35
+
36
+ `Sentry::Rack::Tracing` is now removed. And `Sentry::Rack::CaptureException` has been renamed to `Sentry::Rack::CaptureExceptions`.
37
+
38
+ #### Events Are Sent Asynchronously
39
+
40
+ `sentry-ruby` now sends events asynchronously by default. The functionality works like this:
41
+
42
+ 1. When the SDK is initialized, a `Sentry::BackgroundWorker` will be initialized too.
43
+ 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.
44
+ 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`.
45
+ - If all the threads are busy, new jobs will be put into a queue, which has a limit of 30.
46
+ - If the queue size is exceeded, new events will be dropped.
47
+
48
+ 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.
49
+
50
+ 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.
51
+
52
+ Of course, you can always override the value to fit your use cases, like
53
+
54
+ ```ruby
55
+ config.background_worker_threads = 5 # the worker will have 5 threads for sending events
56
+ ```
57
+
58
+ You can also disable this new non-blocking behaviour by giving a `0` value:
59
+
60
+ ```ruby
61
+ config.background_worker_threads = 0 # all events will be sent synchronously
62
+ ```
63
+
64
+ ## 4.0.1
65
+
66
+ - Add rake integration: [1137](https://github.com/getsentry/sentry-ruby/pull/1137)
67
+ - Make Event's interfaces accessible: [1135](https://github.com/getsentry/sentry-ruby/pull/1135)
68
+ - ActiveSupportLogger should only record events that has a started time: [1132](https://github.com/getsentry/sentry-ruby/pull/1132)
69
+
70
+ ## 4.0.0
71
+
72
+ - Only documents update for the official release and no API/feature changes.
73
+
3
74
  ## 0.3.0
4
75
 
5
76
  - Major API changes: [1123](https://github.com/getsentry/sentry-ruby/pull/1123)
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
@@ -7,6 +7,8 @@
7
7
 
8
8
  # sentry-ruby, the Ruby Client for Sentry
9
9
 
10
+ **The old `sentry-raven` client has entered maintenance mode and was moved to [here](https://github.com/getsentry/sentry-ruby/tree/master/sentry-raven).**
11
+
10
12
  ---
11
13
 
12
14
 
@@ -17,7 +19,7 @@
17
19
  [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver)
18
20
 
19
21
 
20
- [Documentation](https://docs.sentry.io/clients/ruby/) | [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues) | [Forum](https://forum.sentry.io/) | IRC: irc.freenode.net, #sentry
22
+ [Documentation](https://docs.sentry.io/platforms/ruby/) | [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues) | [Forum](https://forum.sentry.io/) | IRC: irc.freenode.net, #sentry
21
23
 
22
24
  The official Ruby-language client and integration layer for the [Sentry](https://github.com/getsentry/sentry) error reporting API.
23
25
 
@@ -26,6 +28,10 @@ The official Ruby-language client and integration layer for the [Sentry](https:/
26
28
 
27
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.
28
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
+
29
35
  ## Getting Started
30
36
 
31
37
  ### Install
@@ -70,17 +76,46 @@ Sentry.init do |config|
70
76
  end
71
77
  ```
72
78
 
79
+ ### Performance Monitoring
80
+
81
+ You can activate performance monitoring by enabling traces sampling:
82
+
83
+ ```ruby
84
+ Sentry.init do |config|
85
+ # set a uniform sample rate between 0.0 and 1.0
86
+ config.traces_sample_rate = 0.2
87
+
88
+ # or control sampling dynamically
89
+ config.traces_sampler = lambda do |sampling_context|
90
+ # sampling_context[:transaction_context] contains the information about the transaction
91
+ # sampling_context[:parent_sampled] contains the transaction's parent's sample decision
92
+ true # return value can be a boolean or a float between 0.0 and 1.0
93
+ end
94
+ end
95
+ ```
96
+
97
+ To lean more about performance monitoring, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/performance).
98
+
73
99
  ### Usage
74
100
 
75
101
  `sentry-ruby` has a default integration with `Rack`, so you only need to use the middleware in your application like:
76
102
 
77
- ```
78
- require 'rack'
103
+ ```ruby
79
104
  require 'sentry-ruby'
80
105
 
81
- use Sentry::Rack::CaptureException
106
+ Sentry.init do |config|
107
+ config.dsn = 'https://examplePublicKey@o0.ingest.sentry.io/0'
108
+
109
+ # To activate performance monitoring, set one of these options.
110
+ # We recommend adjusting the value in production:
111
+ config.traces_sample_rate = 0.5
112
+ # or
113
+ config.traces_sampler = lambda do |context|
114
+ true
115
+ end
116
+ end
82
117
 
83
- run theapp
118
+ use Sentry::Rack::CaptureExceptions
84
119
  ```
85
120
 
86
121
  Otherwise, Sentry you can always use the capture helpers manually
@@ -104,9 +139,9 @@ We also provide integrations with popular frameworks/libraries with the related
104
139
 
105
140
  You're all set - but there's a few more settings you may want to know about too!
106
141
 
107
- #### async
142
+ #### Blocking v.s. Non-blocking
108
143
 
109
- 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:
110
145
 
111
146
  ```ruby
112
147
  config.async = lambda { |event|
@@ -130,6 +165,41 @@ class SentryJob < ActiveJob::Base
130
165
  end
131
166
  ```
132
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
+
133
203
  #### Contexts
134
204
 
135
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:
@@ -148,6 +218,15 @@ end
148
218
  Sentry.capture_exception(exception) # the event will carry all those information now
149
219
  ```
150
220
 
221
+ Or use top-level setters
222
+
223
+
224
+ ```ruby
225
+ Sentry.set_user(id: 1, email: "test@example.com")
226
+ Sentry.set_tags(tag_1: "foo", tag_2: "bar")
227
+ Sentry.set_extras(order_number: 1234, tickets_count: 4)
228
+ ```
229
+
151
230
  Or build up a temporary scope for local information:
152
231
 
153
232
  ```ruby
@@ -172,7 +251,8 @@ Sentry.capture_exception(exception, tags: {foo: "bar"})
172
251
 
173
252
  ## More Information
174
253
 
175
- * [Documentation](https://docs.sentry.io/clients/ruby/)
254
+ * [Documentation](https://docs.sentry.io/platforms/ruby/)
176
255
  * [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues)
177
256
  * [Forum](https://forum.sentry.io/)
178
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,24 @@ 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
 
35
57
  def_delegators :get_current_scope, :set_tags, :set_extras, :set_user
36
58
 
59
+ attr_accessor :background_worker
60
+
37
61
  def init(&block)
38
62
  config = Configuration.new
39
63
  yield(config)
@@ -42,6 +66,11 @@ module Sentry
42
66
  hub = Hub.new(client, scope)
43
67
  Thread.current[THREAD_LOCAL] = hub
44
68
  @main_hub = hub
69
+ @background_worker = Sentry::BackgroundWorker.new(config)
70
+ end
71
+
72
+ def initialized?
73
+ !!@main_hub
45
74
  end
46
75
 
47
76
  def get_main_hub
@@ -61,7 +90,7 @@ module Sentry
61
90
  end
62
91
 
63
92
  def get_current_client
64
- get_current_hub.current_client
93
+ get_current_hub&.current_client
65
94
  end
66
95
 
67
96
  def get_current_hub
@@ -78,15 +107,15 @@ module Sentry
78
107
  end
79
108
 
80
109
  def get_current_scope
81
- get_current_hub.current_scope
110
+ get_current_hub&.current_scope
82
111
  end
83
112
 
84
113
  def with_scope(&block)
85
- get_current_hub.with_scope(&block)
114
+ get_current_hub&.with_scope(&block)
86
115
  end
87
116
 
88
117
  def configure_scope(&block)
89
- get_current_hub.configure_scope(&block)
118
+ get_current_hub&.configure_scope(&block)
90
119
  end
91
120
 
92
121
  def send_event(event)
@@ -94,23 +123,23 @@ module Sentry
94
123
  end
95
124
 
96
125
  def capture_event(event)
97
- get_current_hub.capture_event(event)
126
+ get_current_hub&.capture_event(event)
98
127
  end
99
128
 
100
129
  def capture_exception(exception, **options, &block)
101
- get_current_hub.capture_exception(exception, **options, &block)
130
+ get_current_hub&.capture_exception(exception, **options, &block)
102
131
  end
103
132
 
104
133
  def capture_message(message, **options, &block)
105
- get_current_hub.capture_message(message, **options, &block)
134
+ get_current_hub&.capture_message(message, **options, &block)
106
135
  end
107
136
 
108
137
  def start_transaction(**options)
109
- get_current_hub.start_transaction(**options)
138
+ get_current_hub&.start_transaction(**options)
110
139
  end
111
140
 
112
141
  def last_event_id
113
- get_current_hub.last_event_id
142
+ get_current_hub&.last_event_id
114
143
  end
115
144
 
116
145
  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
@@ -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 unless configuration.sending_allowed?
25
+
24
26
  scope.apply_to_event(event, hint)
25
27
 
26
28
  if configuration.async?
@@ -33,22 +35,30 @@ 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
40
48
  end
41
49
 
42
- def event_from_exception(exception)
50
+ def event_from_exception(exception, hint = {})
51
+ integration_meta = Sentry.integrations[hint[:integration]]
43
52
  return unless @configuration.exception_class_allowed?(exception)
44
53
 
45
- Event.new(configuration: configuration).tap do |event|
54
+ Event.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
46
55
  event.add_exception_interface(exception)
47
56
  end
48
57
  end
49
58
 
50
- def event_from_message(message)
51
- Event.new(configuration: configuration, message: message)
59
+ def event_from_message(message, hint = {})
60
+ integration_meta = Sentry.integrations[hint[:integration]]
61
+ Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
52
62
  end
53
63
 
54
64
  def event_from_transaction(transaction)
@@ -64,9 +74,9 @@ module Sentry
64
74
  end
65
75
 
66
76
  def send_event(event, hint = nil)
67
- return false unless configuration.sending_allowed?
77
+ event_type = event.is_a?(Event) ? event.type : event["type"]
78
+ event = configuration.before_send.call(event, hint) if configuration.before_send && event_type == "event"
68
79
 
69
- event = configuration.before_send.call(event, hint) if configuration.before_send
70
80
  if event.nil?
71
81
  configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
72
82
  return
@@ -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
@@ -18,9 +18,9 @@ module Sentry
18
18
  )
19
19
 
20
20
  attr_accessor(*ATTRIBUTES)
21
- attr_reader :configuration
21
+ attr_reader :configuration, :request, :exception, :stacktrace
22
22
 
23
- def initialize(configuration:, message: nil)
23
+ def initialize(configuration:, integration_meta: nil, message: nil)
24
24
  # this needs to go first because some setters rely on configuration
25
25
  @configuration = configuration
26
26
 
@@ -28,7 +28,7 @@ module Sentry
28
28
  @event_id = SecureRandom.uuid.delete("-")
29
29
  @timestamp = Sentry.utc_now.iso8601
30
30
  @platform = :ruby
31
- @sdk = Sentry.sdk_meta
31
+ @sdk = integration_meta || Sentry.sdk_meta
32
32
 
33
33
  @user = {}
34
34
  @extra = {}
@@ -75,7 +75,7 @@ module Sentry
75
75
  end
76
76
 
77
77
  def rack_env=(env)
78
- unless @request || env.empty?
78
+ unless request || env.empty?
79
79
  @request = Sentry::RequestInterface.new.tap do |int|
80
80
  int.from_rack(env)
81
81
  end
@@ -96,9 +96,9 @@ module Sentry
96
96
  def to_hash
97
97
  data = serialize_attributes
98
98
  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
99
- data[:stacktrace] = @stacktrace.to_hash if @stacktrace
100
- data[:request] = @request.to_hash if @request
101
- data[:exception] = @exception.to_hash if @exception
99
+ data[:stacktrace] = stacktrace.to_hash if stacktrace
100
+ data[:request] = request.to_hash if request
101
+ data[:exception] = exception.to_hash if exception
102
102
 
103
103
  data
104
104
  end
@@ -76,12 +76,12 @@ module Sentry
76
76
  def capture_exception(exception, **options, &block)
77
77
  return unless current_client
78
78
 
79
- event = current_client.event_from_exception(exception)
79
+ options[:hint] ||= {}
80
+ options[:hint][:exception] = exception
81
+ event = current_client.event_from_exception(exception, options[:hint])
80
82
 
81
83
  return unless event
82
84
 
83
- options[:hint] ||= {}
84
- options[:hint] = options[:hint].merge(exception: exception)
85
85
  capture_event(event, **options, &block)
86
86
  end
87
87
 
@@ -89,15 +89,15 @@ module Sentry
89
89
  return unless current_client
90
90
 
91
91
  options[:hint] ||= {}
92
- options[:hint] = options[:hint].merge(message: message)
93
- event = current_client.event_from_message(message)
92
+ options[:hint][:message] = message
93
+ event = current_client.event_from_message(message, options[:hint])
94
94
  capture_event(event, **options, &block)
95
95
  end
96
96
 
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
@@ -110,7 +110,7 @@ module Sentry
110
110
 
111
111
  event = current_client.capture_event(event, scope, hint)
112
112
 
113
- @last_event_id = event.event_id
113
+ @last_event_id = event&.event_id
114
114
  event
115
115
  end
116
116
 
@@ -0,0 +1,24 @@
1
+ module Sentry
2
+ module Integrable
3
+ def register_integration(name:, version:)
4
+ Sentry.register_integration(name, version)
5
+ @integration_name = name
6
+ end
7
+
8
+ def integration_name
9
+ @integration_name
10
+ end
11
+
12
+ def capture_exception(exception, **options, &block)
13
+ options[:hint] ||= {}
14
+ options[:hint][:integration] = integration_name
15
+ Sentry.capture_exception(exception, **options, &block)
16
+ end
17
+
18
+ def capture_message(message, **options, &block)
19
+ options[:hint] ||= {}
20
+ options[:hint][:integration] = integration_name
21
+ Sentry.capture_message(message, **options, &block)
22
+ end
23
+ end
24
+ end
@@ -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,5 @@
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'
5
+ require 'sentry/rack/deprecations'
@@ -0,0 +1,62 @@
1
+ module Sentry
2
+ module Rack
3
+ class CaptureExceptions
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
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
+
14
+ Sentry.with_scope do |scope|
15
+ scope.clear_breadcrumbs
16
+ scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
17
+ scope.set_rack_env(env)
18
+
19
+ span = Sentry.start_transaction(name: scope.transaction_name, op: transaction_op)
20
+ scope.set_span(span)
21
+
22
+ begin
23
+ response = @app.call(env)
24
+ rescue Sentry::Error
25
+ finish_span(span, 500)
26
+ raise # Don't capture Sentry errors
27
+ rescue Exception => e
28
+ capture_exception(e)
29
+ finish_span(span, 500)
30
+ raise
31
+ end
32
+
33
+ exception = collect_exception(env)
34
+ capture_exception(exception) if exception
35
+
36
+ finish_span(span, response[0])
37
+
38
+ response
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def collect_exception(env)
45
+ env['rack.exception'] || env['sinatra.error']
46
+ end
47
+
48
+ def transaction_op
49
+ "rack.request".freeze
50
+ end
51
+
52
+ def capture_exception(exception)
53
+ Sentry.capture_exception(exception)
54
+ end
55
+
56
+ def finish_span(span, status_code)
57
+ span.set_http_status(status_code)
58
+ span.finish
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ module Sentry
2
+ module Rack
3
+ class DeprecatedMiddleware
4
+ def initialize(_)
5
+ raise Sentry::Error.new <<~MSG
6
+
7
+ You're seeing this message because #{self.class} has been replaced by Sentry::Rack::CaptureExceptions.
8
+ Removing this middleware from your app and upgrading sentry-rails to 4.1.0+ should solve the issue.
9
+ MSG
10
+ end
11
+ end
12
+
13
+ class Tracing < DeprecatedMiddleware
14
+ end
15
+
16
+ class CaptureException < DeprecatedMiddleware
17
+ end
18
+ end
19
+ 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
@@ -0,0 +1,17 @@
1
+ require "rake"
2
+ require "rake/task"
3
+
4
+ module Rake
5
+ class Application
6
+ alias orig_display_error_messsage display_error_message
7
+ def display_error_message(ex)
8
+ Sentry.capture_exception(ex, hint: { background: false }) do |scope|
9
+ task_name = top_level_tasks.join(' ')
10
+ scope.set_transaction_name(task_name)
11
+ scope.set_tag("rake_task", task_name)
12
+ end if Sentry.initialized?
13
+
14
+ orig_display_error_messsage(ex)
15
+ end
16
+ end
17
+ 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,17 @@ 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
- event_id = event_hash[:event_id] || event_hash['event_id']
75
- configuration.logger.info(LOGGER_PROGNAME) { "Sending event #{event_id} to Sentry" }
70
+ event_id = event_hash[:event_id] || event_hash["event_id"]
71
+ event_type = event_hash[:type] || event_hash["type"]
72
+ configuration.logger.info(LOGGER_PROGNAME) { "Sending #{event_type} #{event_id} to Sentry" }
76
73
  encode(event_hash)
77
74
  end
78
75
 
79
76
  def failed_for_exception(e, event)
80
- @state.failure
81
77
  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
78
  log_not_sending(event)
83
79
  end
84
80
 
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
81
  def log_not_sending(event)
91
82
  configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event. Unreported Event: #{Event.get_log_message(event.to_hash)}" }
92
83
  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 = "0.3.0"
2
+ VERSION = "4.1.2"
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", '~> 1.0', '>= 1.0.2'
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: 0.3.0
4
+ version: 4.1.2
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-04 00:00:00.000000000 Z
11
+ date: 2020-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -24,6 +24,26 @@ 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: '1.0'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 1.0.2
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.0'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.2
27
47
  description: A gem that provides a client interface for the Sentry error logger
28
48
  email: accounts@sentry.io
29
49
  executables: []
@@ -45,6 +65,7 @@ files:
45
65
  - bin/console
46
66
  - bin/setup
47
67
  - lib/sentry-ruby.rb
68
+ - lib/sentry/background_worker.rb
48
69
  - lib/sentry/backtrace.rb
49
70
  - lib/sentry/benchmarks/benchmark_transport.rb
50
71
  - lib/sentry/breadcrumb.rb
@@ -57,6 +78,7 @@ files:
57
78
  - lib/sentry/dsn.rb
58
79
  - lib/sentry/event.rb
59
80
  - lib/sentry/hub.rb
81
+ - lib/sentry/integrable.rb
60
82
  - lib/sentry/interface.rb
61
83
  - lib/sentry/interfaces/exception.rb
62
84
  - lib/sentry/interfaces/request.rb
@@ -65,8 +87,10 @@ files:
65
87
  - lib/sentry/linecache.rb
66
88
  - lib/sentry/logger.rb
67
89
  - lib/sentry/rack.rb
68
- - lib/sentry/rack/capture_exception.rb
69
- - lib/sentry/rack/tracing.rb
90
+ - lib/sentry/rack/capture_exceptions.rb
91
+ - lib/sentry/rack/deprecations.rb
92
+ - lib/sentry/rack/interface.rb
93
+ - lib/sentry/rake.rb
70
94
  - lib/sentry/scope.rb
71
95
  - lib/sentry/span.rb
72
96
  - lib/sentry/transaction.rb
@@ -75,7 +99,6 @@ files:
75
99
  - lib/sentry/transport/configuration.rb
76
100
  - lib/sentry/transport/dummy_transport.rb
77
101
  - lib/sentry/transport/http_transport.rb
78
- - lib/sentry/transport/state.rb
79
102
  - lib/sentry/utils/exception_cause_chain.rb
80
103
  - lib/sentry/utils/real_ip.rb
81
104
  - lib/sentry/utils/request_id.rb
@@ -1,45 +0,0 @@
1
- module Sentry
2
- module Rack
3
- class CaptureException
4
- def initialize(app)
5
- @app = app
6
- end
7
-
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)
15
- 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
- scope.clear_breadcrumbs
19
- env['sentry.client'] = Sentry.get_current_client
20
-
21
- scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
22
- scope.set_rack_env(env)
23
-
24
- begin
25
- response = @app.call(env)
26
- rescue Sentry::Error
27
- raise # Don't capture Sentry errors
28
- rescue Exception => e
29
- Sentry.capture_exception(e)
30
- raise
31
- end
32
-
33
- exception = collect_exception(env)
34
- Sentry.capture_exception(exception) if exception
35
-
36
- response
37
- end
38
- end
39
-
40
- def collect_exception(env)
41
- env['rack.exception'] || env['sinatra.error']
42
- end
43
- end
44
- end
45
- end
@@ -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