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 +4 -4
- data/CHANGELOG.md +48 -0
- data/Gemfile +1 -1
- data/README.md +39 -5
- data/lib/sentry-ruby.rb +10 -1
- data/lib/sentry/background_worker.rb +37 -0
- data/lib/sentry/client.rb +10 -4
- data/lib/sentry/configuration.rb +12 -0
- data/lib/sentry/hub.rb +1 -1
- data/lib/sentry/interfaces/request.rb +0 -21
- data/lib/sentry/rack.rb +2 -3
- data/lib/sentry/rack/{capture_exception.rb → capture_exceptions.rb} +20 -11
- data/lib/sentry/rack/interface.rb +22 -0
- data/lib/sentry/rake.rb +2 -2
- data/lib/sentry/scope.rb +2 -2
- data/lib/sentry/span.rb +5 -28
- data/lib/sentry/transaction.rb +44 -0
- data/lib/sentry/transport.rb +9 -19
- data/lib/sentry/transport/http_transport.rb +3 -6
- data/lib/sentry/version.rb +1 -1
- data/sentry-ruby.gemspec +1 -0
- metadata +19 -5
- data/lib/sentry/rack/tracing.rb +0 -39
- data/lib/sentry/transport/state.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 517c3e0bc0ab170dbd508034bf74b3a9ba62a1a32f13a992ba84d65f81249e01
|
4
|
+
data.tar.gz: 8a86690e05e1695f15e2da33002ecd52b4596ef8dbd2e9c0a7461f94a8868906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cc45e503a6ed19a5cc507ab0791087e432b612581498e84403fd806e0d9538ff02f57c2fbb64294ba9895514218bca05610f637a8708a752e6eaf42fa61bfaa
|
7
|
+
data.tar.gz: 9eb027f449b6bdee43316e3b78364d1680d56442b5f34ec82b49059a9f28418a51448e24e45318efb022e526b62e067e410a8e78eb3b67678f1e18ce46866004
|
data/CHANGELOG.md
CHANGED
@@ -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
data/README.md
CHANGED
@@ -115,8 +115,7 @@ Sentry.init do |config|
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
-
use Sentry::Rack::
|
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
|
-
####
|
142
|
+
#### Blocking v.s. Non-blocking
|
144
143
|
|
145
|
-
|
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
|
+
|
data/lib/sentry-ruby.rb
CHANGED
@@ -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/
|
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
|
data/lib/sentry/client.rb
CHANGED
@@ -20,7 +20,9 @@ module Sentry
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def capture_event(event, scope, hint =
|
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
|
-
|
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" }
|
data/lib/sentry/configuration.rb
CHANGED
@@ -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
|
data/lib/sentry/hub.rb
CHANGED
@@ -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
|
data/lib/sentry/rack.rb
CHANGED
@@ -1,45 +1,54 @@
|
|
1
1
|
module Sentry
|
2
2
|
module Rack
|
3
|
-
class
|
3
|
+
class CaptureExceptions
|
4
4
|
def initialize(app)
|
5
5
|
@app = app
|
6
6
|
end
|
7
7
|
|
8
8
|
def call(env)
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
Sentry.clone_hub_to_current_thread
|
13
|
-
|
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
|
data/lib/sentry/rake.rb
CHANGED
@@ -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
|
data/lib/sentry/scope.rb
CHANGED
@@ -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
|
|
data/lib/sentry/span.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/sentry/transaction.rb
CHANGED
@@ -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
|
data/lib/sentry/transport.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
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
|
-
|
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
|
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'] =
|
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
|
data/lib/sentry/version.rb
CHANGED
data/sentry-ruby.gemspec
CHANGED
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
|
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
|
+
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/
|
69
|
-
- lib/sentry/rack/
|
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
|
data/lib/sentry/rack/tracing.rb
DELETED
@@ -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
|