sentry-ruby-core 4.3.2 → 4.5.0.pre.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.craft.yml +3 -3
- data/CHANGELOG.md +92 -0
- data/Gemfile +3 -0
- data/lib/sentry-ruby.rb +23 -0
- data/lib/sentry/background_worker.rb +7 -4
- data/lib/sentry/client.rb +18 -7
- data/lib/sentry/configuration.rb +15 -11
- data/lib/sentry/hub.rb +7 -3
- data/lib/sentry/net/http.rb +126 -0
- data/lib/sentry/scope.rb +1 -0
- data/lib/sentry/span.rb +10 -1
- data/lib/sentry/transaction.rb +26 -12
- data/lib/sentry/transport.rb +54 -4
- data/lib/sentry/transport/http_transport.rb +74 -4
- data/lib/sentry/utils/logging_helper.rb +24 -0
- data/lib/sentry/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39b0d720876099298375f0ea0130ced0effa7fe7cb3c153d5075cff400788d7a
|
4
|
+
data.tar.gz: 3e89ef0214a1b632274605984514aa4703d6aea3bcc6c625d8f32eaa2f552203
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 263a733c7d5c2ca356a1de8f5b75b7ec1266e1ea6abe57b20db0a4789a2bd7bb1444dababc06a1291089c009dbcae05b8ded8a11bcd21f721ece3df454e1fb8b
|
7
|
+
data.tar.gz: b256b35de1a11df8e0b3468d47365fec0646da828f05e3161cd4b0374a5b7fda4586d911146895d04bb6574306688b587012c6b85d3b09baf096771efb3d1e49
|
data/.craft.yml
CHANGED
@@ -13,9 +13,6 @@ targets:
|
|
13
13
|
# we always need to make sure sentry-ruby-core is present when pushing to any target
|
14
14
|
- name: gem
|
15
15
|
onlyIfPresent: /^sentry-ruby-core-\d.*\.gem$/
|
16
|
-
- name: github
|
17
|
-
onlyIfPresent: /^sentry-ruby-core-\d.*\.gem$/
|
18
|
-
tagPrefix: sentry-ruby-v
|
19
16
|
- name: registry
|
20
17
|
onlyIfPresent: /^sentry-ruby-core-\d.*\.gem$/
|
21
18
|
type: sdk
|
@@ -26,3 +23,6 @@ targets:
|
|
26
23
|
type: sdk
|
27
24
|
config:
|
28
25
|
canonical: 'gem:sentry-ruby-core'
|
26
|
+
- name: github
|
27
|
+
onlyIfPresent: /^sentry-ruby-core-\d.*\.gem$/
|
28
|
+
tagPrefix: sentry-ruby-v
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,97 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
- Implement sentry-trace propagation [#1446](https://github.com/getsentry/sentry-ruby/pull/1446)
|
8
|
+
|
9
|
+
The SDK will insert the `sentry-trace` to outgoing requests made with `Net::HTTP`. Its value would look like `d827317d25d5420aa3aa97a0257db998-57757614642bdff5-1`.
|
10
|
+
|
11
|
+
If the receiver service also uses Sentry and the SDK supports performance monitoring, its tracing event will be connected with the sender application's.
|
12
|
+
|
13
|
+
Example:
|
14
|
+
|
15
|
+
<img width="1283" alt="connect sentry trace" src="https://user-images.githubusercontent.com/5079556/118963250-d7b40980-b998-11eb-9de4-598d1b220137.png">
|
16
|
+
|
17
|
+
This feature is activated by default. But users can use the new `config.propagate_traces` config option to disable it.
|
18
|
+
|
19
|
+
### Bug Fixes
|
20
|
+
|
21
|
+
- Allow toggling background sending on the fly [#1447](https://github.com/getsentry/sentry-ruby/pull/1447)
|
22
|
+
|
23
|
+
## 4.4.2
|
24
|
+
|
25
|
+
- Fix NoMethodError when SDK's dsn is nil [#1433](https://github.com/getsentry/sentry-ruby/pull/1433)
|
26
|
+
- fix: Update protocol version to 7 [#1434](https://github.com/getsentry/sentry-ruby/pull/1434)
|
27
|
+
- Fixes [#867](https://github.com/getsentry/sentry-ruby/issues/867)
|
28
|
+
|
29
|
+
## 4.4.1
|
30
|
+
|
31
|
+
- Apply patches when initializing the SDK [#1432](https://github.com/getsentry/sentry-ruby/pull/1432)
|
32
|
+
|
33
|
+
## 4.4.0
|
34
|
+
|
35
|
+
### Features
|
36
|
+
|
37
|
+
#### Support category-based rate limiting [#1336](https://github.com/getsentry/sentry-ruby/pull/1336)
|
38
|
+
|
39
|
+
Sentry rate limits different types of events. And when rate limiting is enabled, it sends back a `429` response to the SDK. Currently, the SDK would then raise an error like this:
|
40
|
+
|
41
|
+
```
|
42
|
+
Unable to record event with remote Sentry server (Sentry::Error - the server responded with status 429
|
43
|
+
body: {"detail":"event rejected due to rate limit"}):
|
44
|
+
```
|
45
|
+
|
46
|
+
This change improves the SDK's handling on such responses by:
|
47
|
+
|
48
|
+
- Not treating them as errors, so you don't see the noise anymore.
|
49
|
+
- Halting event sending for a while according to the duration provided in the response. And warns you with a message like:
|
50
|
+
|
51
|
+
```
|
52
|
+
Envelope [event] not sent: Excluded by random sample
|
53
|
+
```
|
54
|
+
|
55
|
+
#### Record request span from Net::HTTP library [#1381](https://github.com/getsentry/sentry-ruby/pull/1381)
|
56
|
+
|
57
|
+
Now any outgoing requests will be recorded as a tracing span. Example:
|
58
|
+
|
59
|
+
<img width="60%" alt="net:http span example" src="https://user-images.githubusercontent.com/5079556/115838944-c1279a80-a44c-11eb-8c67-dfd92bf68bbd.png">
|
60
|
+
|
61
|
+
|
62
|
+
#### Record breadcrumb for Net::HTTP requests [#1394](https://github.com/getsentry/sentry-ruby/pull/1394)
|
63
|
+
|
64
|
+
With the new `http_logger` breadcrumbs logger:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
config.breadcrumbs_logger = [:http_logger]
|
68
|
+
```
|
69
|
+
|
70
|
+
The SDK now records a new `net.http` breadcrumb whenever the user makes a request with the `Net::HTTP` library.
|
71
|
+
|
72
|
+
<img width="60%" alt="net http breadcrumb" src="https://user-images.githubusercontent.com/5079556/114298326-5f7c3d80-9ae8-11eb-9108-222384a7f1a2.png">
|
73
|
+
|
74
|
+
#### Support config.debug configuration option [#1400](https://github.com/getsentry/sentry-ruby/pull/1400)
|
75
|
+
|
76
|
+
It'll determine whether the SDK should run in the debugging mode. Default is `false`. When set to true, SDK errors will be logged with backtrace.
|
77
|
+
|
78
|
+
#### Add the third tracing state [#1402](https://github.com/getsentry/sentry-ruby/pull/1402)
|
79
|
+
- `rate == 0` - Tracing enabled. Rejects all locally created transactions but respects sentry-trace.
|
80
|
+
- `1 > rate > 0` - Tracing enabled. Samples locally created transactions with the rate and respects sentry-trace.
|
81
|
+
- `rate < 0` or `rate > 1` - Tracing disabled.
|
82
|
+
|
83
|
+
### Refactorings
|
84
|
+
|
85
|
+
- Let Transaction constructor take an optional hub argument [#1384](https://github.com/getsentry/sentry-ruby/pull/1384)
|
86
|
+
- Introduce LoggingHelper [#1385](https://github.com/getsentry/sentry-ruby/pull/1385)
|
87
|
+
- Raise exception if a Transaction is initialized without a hub [#1391](https://github.com/getsentry/sentry-ruby/pull/1391)
|
88
|
+
- Make hub a required argument for Transaction constructor [#1401](https://github.com/getsentry/sentry-ruby/pull/1401)
|
89
|
+
|
90
|
+
### Bug Fixes
|
91
|
+
|
92
|
+
- Check `Scope#set_context`'s value argument [#1415](https://github.com/getsentry/sentry-ruby/pull/1415)
|
93
|
+
- Disable tracing if events are not allowed to be sent [#1421](https://github.com/getsentry/sentry-ruby/pull/1421)
|
94
|
+
|
3
95
|
## 4.3.2
|
4
96
|
|
5
97
|
- Correct type attribute's usages [#1354](https://github.com/getsentry/sentry-ruby/pull/1354)
|
data/Gemfile
CHANGED
data/lib/sentry-ruby.rb
CHANGED
@@ -6,6 +6,7 @@ require "sentry/version"
|
|
6
6
|
require "sentry/exceptions"
|
7
7
|
require "sentry/core_ext/object/deep_dup"
|
8
8
|
require "sentry/utils/argument_checking_helper"
|
9
|
+
require "sentry/utils/logging_helper"
|
9
10
|
require "sentry/configuration"
|
10
11
|
require "sentry/logger"
|
11
12
|
require "sentry/event"
|
@@ -31,6 +32,8 @@ module Sentry
|
|
31
32
|
|
32
33
|
LOGGER_PROGNAME = "sentry".freeze
|
33
34
|
|
35
|
+
SENTRY_TRACE_HEADER_NAME = "sentry-trace".freeze
|
36
|
+
|
34
37
|
THREAD_LOCAL = :sentry_hub
|
35
38
|
|
36
39
|
def self.sdk_meta
|
@@ -62,9 +65,26 @@ module Sentry
|
|
62
65
|
|
63
66
|
attr_accessor :background_worker
|
64
67
|
|
68
|
+
@@registered_patches = []
|
69
|
+
|
70
|
+
def register_patch(&block)
|
71
|
+
registered_patches << block
|
72
|
+
end
|
73
|
+
|
74
|
+
def apply_patches(config)
|
75
|
+
registered_patches.each do |patch|
|
76
|
+
patch.call(config)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def registered_patches
|
81
|
+
@@registered_patches
|
82
|
+
end
|
83
|
+
|
65
84
|
def init(&block)
|
66
85
|
config = Configuration.new
|
67
86
|
yield(config) if block_given?
|
87
|
+
apply_patches(config)
|
68
88
|
client = Client.new(config)
|
69
89
|
scope = Scope.new(max_breadcrumbs: config.max_breadcrumbs)
|
70
90
|
hub = Hub.new(client, scope)
|
@@ -188,3 +208,6 @@ module Sentry
|
|
188
208
|
end
|
189
209
|
end
|
190
210
|
end
|
211
|
+
|
212
|
+
# patches
|
213
|
+
require "sentry/net/http"
|
@@ -4,21 +4,24 @@ require "concurrent/configuration"
|
|
4
4
|
|
5
5
|
module Sentry
|
6
6
|
class BackgroundWorker
|
7
|
-
|
7
|
+
include LoggingHelper
|
8
|
+
|
9
|
+
attr_reader :max_queue, :number_of_threads, :logger
|
8
10
|
|
9
11
|
def initialize(configuration)
|
10
12
|
@max_queue = 30
|
11
13
|
@number_of_threads = configuration.background_worker_threads
|
14
|
+
@logger = configuration.logger
|
12
15
|
|
13
16
|
@executor =
|
14
17
|
if configuration.async
|
15
|
-
|
18
|
+
log_debug("config.async is set, BackgroundWorker is disabled")
|
16
19
|
Concurrent::ImmediateExecutor.new
|
17
20
|
elsif @number_of_threads == 0
|
18
|
-
|
21
|
+
log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
|
19
22
|
Concurrent::ImmediateExecutor.new
|
20
23
|
else
|
21
|
-
|
24
|
+
log_debug("initialized a background worker with #{@number_of_threads} threads")
|
22
25
|
|
23
26
|
Concurrent::ThreadPoolExecutor.new(
|
24
27
|
min_threads: 0,
|
data/lib/sentry/client.rb
CHANGED
@@ -2,6 +2,8 @@ require "sentry/transport"
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class Client
|
5
|
+
include LoggingHelper
|
6
|
+
|
5
7
|
attr_reader :transport, :configuration, :logger
|
6
8
|
|
7
9
|
def initialize(configuration)
|
@@ -28,7 +30,7 @@ module Sentry
|
|
28
30
|
|
29
31
|
if async_block = configuration.async
|
30
32
|
dispatch_async_event(async_block, event, hint)
|
31
|
-
elsif hint.fetch(:background, true)
|
33
|
+
elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
|
32
34
|
dispatch_background_event(event, hint)
|
33
35
|
else
|
34
36
|
send_event(event, hint)
|
@@ -36,7 +38,7 @@ module Sentry
|
|
36
38
|
|
37
39
|
event
|
38
40
|
rescue => e
|
39
|
-
|
41
|
+
log_error("Event capturing failed", e, debug: configuration.debug)
|
40
42
|
nil
|
41
43
|
end
|
42
44
|
|
@@ -76,7 +78,7 @@ module Sentry
|
|
76
78
|
event = configuration.before_send.call(event, hint)
|
77
79
|
|
78
80
|
if event.nil?
|
79
|
-
|
81
|
+
log_info("Discarded event because before_send returned nil")
|
80
82
|
return
|
81
83
|
end
|
82
84
|
end
|
@@ -86,11 +88,21 @@ module Sentry
|
|
86
88
|
event
|
87
89
|
rescue => e
|
88
90
|
loggable_event_type = (event_type || "event").capitalize
|
89
|
-
|
90
|
-
|
91
|
+
log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
|
92
|
+
|
93
|
+
event_info = Event.get_log_message(event.to_hash)
|
94
|
+
log_info("Unreported #{loggable_event_type}: #{event_info}")
|
91
95
|
raise
|
92
96
|
end
|
93
97
|
|
98
|
+
def generate_sentry_trace(span)
|
99
|
+
return unless configuration.propagate_traces
|
100
|
+
|
101
|
+
trace = span.to_sentry_trace
|
102
|
+
log_debug("[Tracing] Adding #{SENTRY_TRACE_HEADER_NAME} header to outgoing request: #{trace}")
|
103
|
+
trace
|
104
|
+
end
|
105
|
+
|
94
106
|
private
|
95
107
|
|
96
108
|
def dispatch_background_event(event, hint)
|
@@ -112,9 +124,8 @@ module Sentry
|
|
112
124
|
end
|
113
125
|
rescue => e
|
114
126
|
loggable_event_type = event_hash["type"] || "event"
|
115
|
-
|
127
|
+
log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
|
116
128
|
send_event(event, hint)
|
117
129
|
end
|
118
|
-
|
119
130
|
end
|
120
131
|
end
|
data/lib/sentry/configuration.rb
CHANGED
@@ -8,6 +8,7 @@ require "sentry/interfaces/stacktrace_builder"
|
|
8
8
|
|
9
9
|
module Sentry
|
10
10
|
class Configuration
|
11
|
+
include LoggingHelper
|
11
12
|
# Directories to be recognized as part of your app. e.g. if you
|
12
13
|
# have an `engines` dir at the root of your project, you may want
|
13
14
|
# to set this to something like /(app|config|engines|lib)/
|
@@ -71,6 +72,10 @@ module Sentry
|
|
71
72
|
# RACK_ENV by default.
|
72
73
|
attr_reader :environment
|
73
74
|
|
75
|
+
# Whether the SDK should run in the debugging mode. Default is false.
|
76
|
+
# If set to true, SDK errors will be logged with backtrace
|
77
|
+
attr_accessor :debug
|
78
|
+
|
74
79
|
# the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
|
75
80
|
attr_reader :dsn
|
76
81
|
|
@@ -101,6 +106,9 @@ module Sentry
|
|
101
106
|
# Set automatically for Rails.
|
102
107
|
attr_reader :project_root
|
103
108
|
|
109
|
+
# Insert sentry-trace to outgoing requests' headers
|
110
|
+
attr_accessor :propagate_traces
|
111
|
+
|
104
112
|
# Array of rack env parameters to be included in the event sent to sentry.
|
105
113
|
attr_accessor :rack_env_whitelist
|
106
114
|
|
@@ -167,13 +175,12 @@ module Sentry
|
|
167
175
|
LOG_PREFIX = "** [Sentry] ".freeze
|
168
176
|
MODULE_SEPARATOR = "::".freeze
|
169
177
|
|
170
|
-
AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
|
171
|
-
|
172
178
|
# Post initialization callbacks are called at the end of initialization process
|
173
179
|
# allowing extending the configuration of sentry-ruby by multiple extensions
|
174
180
|
@@post_initialization_callbacks = []
|
175
181
|
|
176
182
|
def initialize
|
183
|
+
self.debug = false
|
177
184
|
self.background_worker_threads = Concurrent.processor_count
|
178
185
|
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
|
179
186
|
self.breadcrumbs_logger = []
|
@@ -186,6 +193,7 @@ module Sentry
|
|
186
193
|
self.linecache = ::Sentry::LineCache.new
|
187
194
|
self.logger = ::Sentry::Logger.new(STDOUT)
|
188
195
|
self.project_root = Dir.pwd
|
196
|
+
self.propagate_traces = true
|
189
197
|
|
190
198
|
self.release = detect_release
|
191
199
|
self.sample_rate = 1.0
|
@@ -226,10 +234,6 @@ module Sentry
|
|
226
234
|
if logger.is_a?(Array)
|
227
235
|
logger
|
228
236
|
else
|
229
|
-
unless AVAILABLE_BREADCRUMBS_LOGGERS.include?(logger)
|
230
|
-
raise Sentry::Error, "Unsupported breadcrumbs logger. Supported loggers: #{AVAILABLE_BREADCRUMBS_LOGGERS}"
|
231
|
-
end
|
232
|
-
|
233
237
|
Array(logger)
|
234
238
|
end
|
235
239
|
|
@@ -278,10 +282,10 @@ module Sentry
|
|
278
282
|
def exception_class_allowed?(exc)
|
279
283
|
if exc.is_a?(Sentry::Error)
|
280
284
|
# Try to prevent error reporting loops
|
281
|
-
|
285
|
+
log_debug("Refusing to capture Sentry error: #{exc.inspect}")
|
282
286
|
false
|
283
287
|
elsif excluded_exception?(exc)
|
284
|
-
|
288
|
+
log_debug("User excluded error: #{exc.inspect}")
|
285
289
|
false
|
286
290
|
else
|
287
291
|
true
|
@@ -293,7 +297,7 @@ module Sentry
|
|
293
297
|
end
|
294
298
|
|
295
299
|
def tracing_enabled?
|
296
|
-
!!((@traces_sample_rate && @traces_sample_rate
|
300
|
+
!!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler) && sending_allowed?
|
297
301
|
end
|
298
302
|
|
299
303
|
def stacktrace_builder
|
@@ -314,7 +318,7 @@ module Sentry
|
|
314
318
|
detect_release_from_capistrano ||
|
315
319
|
detect_release_from_heroku
|
316
320
|
rescue => e
|
317
|
-
|
321
|
+
log_error("Error detecting release", e, debug: debug)
|
318
322
|
end
|
319
323
|
|
320
324
|
def excluded_exception?(incoming_exception)
|
@@ -349,7 +353,7 @@ module Sentry
|
|
349
353
|
def detect_release_from_heroku
|
350
354
|
return unless running_on_heroku?
|
351
355
|
return if ENV['CI']
|
352
|
-
|
356
|
+
log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
|
353
357
|
|
354
358
|
ENV['HEROKU_SLUG_COMMIT']
|
355
359
|
end
|
data/lib/sentry/hub.rb
CHANGED
@@ -21,6 +21,10 @@ module Sentry
|
|
21
21
|
current_layer&.client
|
22
22
|
end
|
23
23
|
|
24
|
+
def configuration
|
25
|
+
current_client.configuration
|
26
|
+
end
|
27
|
+
|
24
28
|
def current_scope
|
25
29
|
current_layer&.scope
|
26
30
|
end
|
@@ -69,10 +73,10 @@ module Sentry
|
|
69
73
|
@stack.pop
|
70
74
|
end
|
71
75
|
|
72
|
-
def start_transaction(transaction: nil,
|
76
|
+
def start_transaction(transaction: nil, custom_sampling_context: {}, **options)
|
73
77
|
return unless configuration.tracing_enabled?
|
74
78
|
|
75
|
-
transaction ||= Transaction.new(**options)
|
79
|
+
transaction ||= Transaction.new(**options.merge(hub: self))
|
76
80
|
|
77
81
|
sampling_context = {
|
78
82
|
transaction_context: transaction.to_hash,
|
@@ -81,7 +85,7 @@ module Sentry
|
|
81
85
|
|
82
86
|
sampling_context.merge!(custom_sampling_context)
|
83
87
|
|
84
|
-
transaction.set_initial_sample_decision(
|
88
|
+
transaction.set_initial_sample_decision(sampling_context: sampling_context)
|
85
89
|
transaction
|
86
90
|
end
|
87
91
|
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require "net/http"
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Net
|
5
|
+
module HTTP
|
6
|
+
OP_NAME = "net.http"
|
7
|
+
|
8
|
+
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
|
9
|
+
# Here's part of its definition. As you can see, it usually calls itself inside a #start block
|
10
|
+
#
|
11
|
+
# ```
|
12
|
+
# def request(req, body = nil, &block)
|
13
|
+
# unless started?
|
14
|
+
# start {
|
15
|
+
# req['connection'] ||= 'close'
|
16
|
+
# return request(req, body, &block) # <- request will be called for the second time from the first call
|
17
|
+
# }
|
18
|
+
# end
|
19
|
+
# # .....
|
20
|
+
# end
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# So when the entire flow looks like this:
|
24
|
+
#
|
25
|
+
# 1. #request is called.
|
26
|
+
# - But because the request hasn't started yet, it calls #start (which then calls #do_start)
|
27
|
+
# - At this moment @sentry_span is still nil, so #set_sentry_trace_header returns early
|
28
|
+
# 2. #do_start then creates a new Span and assigns it to @sentry_span
|
29
|
+
# 3. #request is called for the second time.
|
30
|
+
# - This time @sentry_span should present. So #set_sentry_trace_header will set the sentry-trace header on the request object
|
31
|
+
# 4. Once the request finished, it
|
32
|
+
# - Records a breadcrumb if http_logger is set
|
33
|
+
# - Finishes the Span inside @sentry_span and clears the instance variable
|
34
|
+
#
|
35
|
+
def request(req, body = nil, &block)
|
36
|
+
set_sentry_trace_header(req)
|
37
|
+
|
38
|
+
super.tap do |res|
|
39
|
+
record_sentry_breadcrumb(req, res)
|
40
|
+
record_sentry_span(req, res)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def do_start
|
45
|
+
super.tap do
|
46
|
+
start_sentry_span
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def do_finish
|
51
|
+
super.tap do
|
52
|
+
finish_sentry_span
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def set_sentry_trace_header(req)
|
59
|
+
return unless @sentry_span
|
60
|
+
|
61
|
+
trace = Sentry.get_current_client.generate_sentry_trace(@sentry_span)
|
62
|
+
req[SENTRY_TRACE_HEADER_NAME] = trace if trace
|
63
|
+
end
|
64
|
+
|
65
|
+
def record_sentry_breadcrumb(req, res)
|
66
|
+
if Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
67
|
+
return if from_sentry_sdk?
|
68
|
+
|
69
|
+
request_info = extract_request_info(req)
|
70
|
+
crumb = Sentry::Breadcrumb.new(
|
71
|
+
level: :info,
|
72
|
+
category: OP_NAME,
|
73
|
+
type: :info,
|
74
|
+
data: {
|
75
|
+
method: request_info[:method],
|
76
|
+
url: request_info[:url],
|
77
|
+
status: res.code.to_i
|
78
|
+
}
|
79
|
+
)
|
80
|
+
Sentry.add_breadcrumb(crumb)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def record_sentry_span(req, res)
|
85
|
+
if Sentry.initialized? && @sentry_span
|
86
|
+
request_info = extract_request_info(req)
|
87
|
+
@sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
88
|
+
@sentry_span.set_data(:status, res.code.to_i)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def start_sentry_span
|
93
|
+
if Sentry.initialized? && transaction = Sentry.get_current_scope.get_transaction
|
94
|
+
return if from_sentry_sdk?
|
95
|
+
return if transaction.sampled == false
|
96
|
+
|
97
|
+
child_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
|
98
|
+
@sentry_span = child_span
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def finish_sentry_span
|
103
|
+
if Sentry.initialized? && @sentry_span
|
104
|
+
@sentry_span.set_timestamp(Sentry.utc_now.to_f)
|
105
|
+
@sentry_span = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def from_sentry_sdk?
|
110
|
+
dsn = Sentry.configuration.dsn
|
111
|
+
dsn && dsn.host == self.address
|
112
|
+
end
|
113
|
+
|
114
|
+
def extract_request_info(req)
|
115
|
+
uri = req.uri
|
116
|
+
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
117
|
+
{ method: req.method, url: url }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Sentry.register_patch do
|
124
|
+
patch = Sentry::Net::HTTP
|
125
|
+
Net::HTTP.send(:prepend, patch) unless Net::HTTP.ancestors.include?(patch)
|
126
|
+
end
|
data/lib/sentry/scope.rb
CHANGED
data/lib/sentry/span.rb
CHANGED
@@ -21,7 +21,16 @@ module Sentry
|
|
21
21
|
attr_reader :trace_id, :span_id, :parent_span_id, :sampled, :start_timestamp, :timestamp, :description, :op, :status, :tags, :data
|
22
22
|
attr_accessor :span_recorder, :transaction
|
23
23
|
|
24
|
-
def initialize(
|
24
|
+
def initialize(
|
25
|
+
description: nil,
|
26
|
+
op: nil,
|
27
|
+
status: nil,
|
28
|
+
trace_id: nil,
|
29
|
+
parent_span_id: nil,
|
30
|
+
sampled: nil,
|
31
|
+
start_timestamp: nil,
|
32
|
+
timestamp: nil
|
33
|
+
)
|
25
34
|
@trace_id = trace_id || SecureRandom.uuid.delete("-")
|
26
35
|
@span_id = SecureRandom.hex(8)
|
27
36
|
@parent_span_id = parent_span_id
|
data/lib/sentry/transaction.rb
CHANGED
@@ -10,19 +10,24 @@ module Sentry
|
|
10
10
|
UNLABELD_NAME = "<unlabeled transaction>".freeze
|
11
11
|
MESSAGE_PREFIX = "[Tracing]"
|
12
12
|
|
13
|
-
|
13
|
+
include LoggingHelper
|
14
14
|
|
15
|
-
|
15
|
+
attr_reader :name, :parent_sampled, :hub, :configuration, :logger
|
16
|
+
|
17
|
+
def initialize(name: nil, parent_sampled: nil, hub:, **options)
|
16
18
|
super(**options)
|
17
19
|
|
18
20
|
@name = name
|
19
21
|
@parent_sampled = parent_sampled
|
20
22
|
@transaction = self
|
23
|
+
@hub = hub
|
24
|
+
@configuration = hub.configuration
|
25
|
+
@logger = configuration.logger
|
21
26
|
init_span_recorder
|
22
27
|
end
|
23
28
|
|
24
|
-
def self.from_sentry_trace(sentry_trace,
|
25
|
-
return unless configuration.tracing_enabled?
|
29
|
+
def self.from_sentry_trace(sentry_trace, hub: Sentry.get_current_hub, **options)
|
30
|
+
return unless hub.configuration.tracing_enabled?
|
26
31
|
return unless sentry_trace
|
27
32
|
|
28
33
|
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
@@ -36,7 +41,7 @@ module Sentry
|
|
36
41
|
sampled_flag != "0"
|
37
42
|
end
|
38
43
|
|
39
|
-
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: parent_sampled, **options)
|
44
|
+
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: parent_sampled, hub: hub, **options)
|
40
45
|
end
|
41
46
|
|
42
47
|
def to_hash
|
@@ -58,7 +63,7 @@ module Sentry
|
|
58
63
|
copy
|
59
64
|
end
|
60
65
|
|
61
|
-
def set_initial_sample_decision(sampling_context
|
66
|
+
def set_initial_sample_decision(sampling_context:)
|
62
67
|
unless configuration.tracing_enabled?
|
63
68
|
@sampled = false
|
64
69
|
return
|
@@ -78,17 +83,16 @@ module Sentry
|
|
78
83
|
end
|
79
84
|
|
80
85
|
transaction_description = generate_transaction_description
|
81
|
-
logger = configuration.logger
|
82
86
|
|
83
87
|
unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Numeric) && sample_rate >= 0.0 && sample_rate <= 1.0)
|
84
88
|
@sampled = false
|
85
|
-
|
89
|
+
log_warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
|
86
90
|
return
|
87
91
|
end
|
88
92
|
|
89
93
|
if sample_rate == 0.0 || sample_rate == false
|
90
94
|
@sampled = false
|
91
|
-
|
95
|
+
log_debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
|
92
96
|
return
|
93
97
|
end
|
94
98
|
|
@@ -99,15 +103,26 @@ module Sentry
|
|
99
103
|
end
|
100
104
|
|
101
105
|
if @sampled
|
102
|
-
|
106
|
+
log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
|
103
107
|
else
|
104
|
-
|
108
|
+
log_debug(
|
105
109
|
"#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
|
106
110
|
)
|
107
111
|
end
|
108
112
|
end
|
109
113
|
|
110
114
|
def finish(hub: nil)
|
115
|
+
if hub
|
116
|
+
log_warn(
|
117
|
+
<<~MSG
|
118
|
+
Specifying a different hub in `Transaction#finish` will be deprecated in version 5.0.
|
119
|
+
Please use `Hub#start_transaction` with the designated hub.
|
120
|
+
MSG
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
hub ||= @hub
|
125
|
+
|
111
126
|
super() # Span#finish doesn't take arguments
|
112
127
|
|
113
128
|
if @name.nil?
|
@@ -116,7 +131,6 @@ module Sentry
|
|
116
131
|
|
117
132
|
return unless @sampled || @parent_sampled
|
118
133
|
|
119
|
-
hub ||= Sentry.get_current_hub
|
120
134
|
event = hub.current_client.event_from_transaction(self)
|
121
135
|
hub.capture_event(event)
|
122
136
|
end
|
data/lib/sentry/transport.rb
CHANGED
@@ -3,15 +3,20 @@ require "base64"
|
|
3
3
|
|
4
4
|
module Sentry
|
5
5
|
class Transport
|
6
|
-
PROTOCOL_VERSION = '
|
6
|
+
PROTOCOL_VERSION = '7'
|
7
7
|
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
|
8
8
|
|
9
|
+
include LoggingHelper
|
10
|
+
|
9
11
|
attr_accessor :configuration
|
12
|
+
attr_reader :logger, :rate_limits
|
10
13
|
|
11
14
|
def initialize(configuration)
|
12
15
|
@configuration = configuration
|
16
|
+
@logger = configuration.logger
|
13
17
|
@transport_configuration = configuration.transport
|
14
18
|
@dsn = configuration.dsn
|
19
|
+
@rate_limits = {}
|
15
20
|
end
|
16
21
|
|
17
22
|
def send_data(data, options = {})
|
@@ -19,8 +24,18 @@ module Sentry
|
|
19
24
|
end
|
20
25
|
|
21
26
|
def send_event(event)
|
27
|
+
event_hash = event.to_hash
|
28
|
+
item_type = get_item_type(event_hash)
|
29
|
+
|
22
30
|
unless configuration.sending_allowed?
|
23
|
-
|
31
|
+
log_debug("Envelope [#{item_type}] not sent: #{configuration.error_messages}")
|
32
|
+
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
if is_rate_limited?(item_type)
|
37
|
+
log_info("Envelope [#{item_type}] not sent: rate limiting")
|
38
|
+
|
24
39
|
return
|
25
40
|
end
|
26
41
|
|
@@ -33,6 +48,35 @@ module Sentry
|
|
33
48
|
event
|
34
49
|
end
|
35
50
|
|
51
|
+
def is_rate_limited?(item_type)
|
52
|
+
# check category-specific limit
|
53
|
+
category_delay =
|
54
|
+
case item_type
|
55
|
+
when "transaction"
|
56
|
+
@rate_limits["transaction"]
|
57
|
+
else
|
58
|
+
@rate_limits["error"]
|
59
|
+
end
|
60
|
+
|
61
|
+
# check universal limit if not category limit
|
62
|
+
universal_delay = @rate_limits[nil]
|
63
|
+
|
64
|
+
delay =
|
65
|
+
if category_delay && universal_delay
|
66
|
+
if category_delay > universal_delay
|
67
|
+
category_delay
|
68
|
+
else
|
69
|
+
universal_delay
|
70
|
+
end
|
71
|
+
elsif category_delay
|
72
|
+
category_delay
|
73
|
+
else
|
74
|
+
universal_delay
|
75
|
+
end
|
76
|
+
|
77
|
+
!!delay && delay > Time.now
|
78
|
+
end
|
79
|
+
|
36
80
|
def generate_auth_header
|
37
81
|
now = Sentry.utc_now.to_i
|
38
82
|
fields = {
|
@@ -50,7 +94,7 @@ module Sentry
|
|
50
94
|
event_hash = event.to_hash
|
51
95
|
|
52
96
|
event_id = event_hash[:event_id] || event_hash["event_id"]
|
53
|
-
item_type = event_hash
|
97
|
+
item_type = get_item_type(event_hash)
|
54
98
|
|
55
99
|
envelope = <<~ENVELOPE
|
56
100
|
{"event_id":"#{event_id}","dsn":"#{configuration.dsn.to_s}","sdk":#{Sentry.sdk_meta.to_json},"sent_at":"#{Sentry.utc_now.iso8601}"}
|
@@ -58,10 +102,16 @@ module Sentry
|
|
58
102
|
#{JSON.generate(event_hash)}
|
59
103
|
ENVELOPE
|
60
104
|
|
61
|
-
|
105
|
+
log_info("Sending envelope [#{item_type}] #{event_id} to Sentry")
|
62
106
|
|
63
107
|
envelope
|
64
108
|
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def get_item_type(event_hash)
|
113
|
+
event_hash[:type] || event_hash["type"] || "event"
|
114
|
+
end
|
65
115
|
end
|
66
116
|
end
|
67
117
|
|
@@ -7,6 +7,10 @@ module Sentry
|
|
7
7
|
GZIP_THRESHOLD = 1024 * 30
|
8
8
|
CONTENT_TYPE = 'application/x-sentry-envelope'
|
9
9
|
|
10
|
+
DEFAULT_DELAY = 60
|
11
|
+
RETRY_AFTER_HEADER = "retry-after"
|
12
|
+
RATE_LIMIT_HEADER = "x-sentry-rate-limits"
|
13
|
+
|
10
14
|
attr_reader :conn, :adapter
|
11
15
|
|
12
16
|
def initialize(*args)
|
@@ -24,18 +28,26 @@ module Sentry
|
|
24
28
|
encoding = GZIP_ENCODING
|
25
29
|
end
|
26
30
|
|
27
|
-
conn.post @endpoint do |req|
|
31
|
+
response = conn.post @endpoint do |req|
|
28
32
|
req.headers['Content-Type'] = CONTENT_TYPE
|
29
33
|
req.headers['Content-Encoding'] = encoding
|
30
34
|
req.headers['X-Sentry-Auth'] = generate_auth_header
|
31
35
|
req.body = data
|
32
36
|
end
|
37
|
+
|
38
|
+
if has_rate_limited_header?(response.headers)
|
39
|
+
handle_rate_limited_response(response.headers)
|
40
|
+
end
|
33
41
|
rescue Faraday::Error => e
|
34
42
|
error_info = e.message
|
35
43
|
|
36
44
|
if e.response
|
37
|
-
|
38
|
-
|
45
|
+
if e.response[:status] == 429
|
46
|
+
handle_rate_limited_response(e.response[:headers])
|
47
|
+
else
|
48
|
+
error_info += "\nbody: #{e.response[:body]}"
|
49
|
+
error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error']
|
50
|
+
end
|
39
51
|
end
|
40
52
|
|
41
53
|
raise Sentry::ExternalError, error_info
|
@@ -43,6 +55,64 @@ module Sentry
|
|
43
55
|
|
44
56
|
private
|
45
57
|
|
58
|
+
def has_rate_limited_header?(headers)
|
59
|
+
headers[RETRY_AFTER_HEADER] || headers[RATE_LIMIT_HEADER]
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_rate_limited_response(headers)
|
63
|
+
rate_limits =
|
64
|
+
if rate_limits = headers[RATE_LIMIT_HEADER]
|
65
|
+
parse_rate_limit_header(rate_limits)
|
66
|
+
elsif retry_after = headers[RETRY_AFTER_HEADER]
|
67
|
+
# although Sentry doesn't send a date string back
|
68
|
+
# based on HTTP specification, this could be a date string (instead of an integer)
|
69
|
+
retry_after = retry_after.to_i
|
70
|
+
retry_after = DEFAULT_DELAY if retry_after == 0
|
71
|
+
|
72
|
+
{ nil => Time.now + retry_after }
|
73
|
+
else
|
74
|
+
{ nil => Time.now + DEFAULT_DELAY }
|
75
|
+
end
|
76
|
+
|
77
|
+
rate_limits.each do |category, limit|
|
78
|
+
if current_limit = @rate_limits[category]
|
79
|
+
if current_limit < limit
|
80
|
+
@rate_limits[category] = limit
|
81
|
+
end
|
82
|
+
else
|
83
|
+
@rate_limits[category] = limit
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_rate_limit_header(rate_limit_header)
|
89
|
+
time = Time.now
|
90
|
+
|
91
|
+
result = {}
|
92
|
+
|
93
|
+
limits = rate_limit_header.split(",")
|
94
|
+
limits.each do |limit|
|
95
|
+
next if limit.nil? || limit.empty?
|
96
|
+
|
97
|
+
begin
|
98
|
+
retry_after, categories = limit.strip.split(":").first(2)
|
99
|
+
retry_after = time + retry_after.to_i
|
100
|
+
categories = categories.split(";")
|
101
|
+
|
102
|
+
if categories.empty?
|
103
|
+
result[nil] = retry_after
|
104
|
+
else
|
105
|
+
categories.each do |category|
|
106
|
+
result[category] = retry_after
|
107
|
+
end
|
108
|
+
end
|
109
|
+
rescue StandardError
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
46
116
|
def should_compress?(data)
|
47
117
|
@transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
|
48
118
|
end
|
@@ -50,7 +120,7 @@ module Sentry
|
|
50
120
|
def set_conn
|
51
121
|
server = @dsn.server
|
52
122
|
|
53
|
-
|
123
|
+
log_debug("Sentry HTTP Transport connecting to #{server}")
|
54
124
|
|
55
125
|
Faraday.new(server, :ssl => ssl_configuration, :proxy => @transport_configuration.proxy) do |builder|
|
56
126
|
@transport_configuration.faraday_builder&.call(builder)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sentry
|
2
|
+
module LoggingHelper
|
3
|
+
def log_error(message, exception, debug: false)
|
4
|
+
message = "#{message}: #{exception.message}"
|
5
|
+
message += "\n#{exception.backtrace.join("\n")}" if debug
|
6
|
+
|
7
|
+
logger.error(LOGGER_PROGNAME) do
|
8
|
+
message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def log_info(message)
|
13
|
+
logger.info(LOGGER_PROGNAME) { message }
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_debug(message)
|
17
|
+
logger.debug(LOGGER_PROGNAME) { message }
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_warn(message)
|
21
|
+
logger.warn(LOGGER_PROGNAME) { message }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/sentry/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.5.0.pre.beta.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -83,6 +83,7 @@ files:
|
|
83
83
|
- lib/sentry/interfaces/threads.rb
|
84
84
|
- lib/sentry/linecache.rb
|
85
85
|
- lib/sentry/logger.rb
|
86
|
+
- lib/sentry/net/http.rb
|
86
87
|
- lib/sentry/rack.rb
|
87
88
|
- lib/sentry/rack/capture_exceptions.rb
|
88
89
|
- lib/sentry/rack/deprecations.rb
|
@@ -97,6 +98,7 @@ files:
|
|
97
98
|
- lib/sentry/transport/http_transport.rb
|
98
99
|
- lib/sentry/utils/argument_checking_helper.rb
|
99
100
|
- lib/sentry/utils/exception_cause_chain.rb
|
101
|
+
- lib/sentry/utils/logging_helper.rb
|
100
102
|
- lib/sentry/utils/real_ip.rb
|
101
103
|
- lib/sentry/utils/request_id.rb
|
102
104
|
- lib/sentry/version.rb
|
@@ -120,11 +122,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
122
|
version: '2.4'
|
121
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
124
|
requirements:
|
123
|
-
- - "
|
125
|
+
- - ">"
|
124
126
|
- !ruby/object:Gem::Version
|
125
|
-
version:
|
127
|
+
version: 1.3.1
|
126
128
|
requirements: []
|
127
|
-
rubygems_version: 3.
|
129
|
+
rubygems_version: 3.1.6
|
128
130
|
signing_key:
|
129
131
|
specification_version: 4
|
130
132
|
summary: A gem that provides a client interface for the Sentry error logger
|