sentry-ruby-core 4.3.2 → 4.4.0.pre.beta.0
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 +4 -3
- data/CHANGELOG.md +61 -0
- data/Gemfile +3 -0
- data/lib/sentry-ruby.rb +2 -0
- data/lib/sentry/background_worker.rb +7 -4
- data/lib/sentry/client.rb +9 -6
- data/lib/sentry/configuration.rb +11 -11
- data/lib/sentry/hub.rb +7 -3
- data/lib/sentry/net/http.rb +87 -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 +53 -3
- 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 +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb17ca62057f29a255a7d725bd574775d840e987cbc3c067a9294b63311d26aa
|
4
|
+
data.tar.gz: 9268967f80f8406bbcf3bf2b0dc150567b3adc16d1c4b38fd8ef5df5a1559737
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d57f757d5d7c36c24bf296fbb673c8afe20fb3fadc89c1a1f4046856188286f9db9c7fdd2b6c3d49e921a302f179f1c4acef4ac737b802cc0640e979eab0de8
|
7
|
+
data.tar.gz: e5900027bce05c31a9bd10ea6fb2f660deee1494e37545a0637ea89767353add86b39fc4dde07de0c4ac9fcadb2f2d2bc8441308d4f89396e5c5de124ed7f445
|
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,7 @@ 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
|
29
|
+
changelog: sentry-ruby/CHANGELOG.md
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,66 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 4.4.0-beta.0
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
#### Support category-based rate limiting [#1336](https://github.com/getsentry/sentry-ruby/pull/1336)
|
8
|
+
|
9
|
+
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:
|
10
|
+
|
11
|
+
```
|
12
|
+
Unable to record event with remote Sentry server (Sentry::Error - the server responded with status 429
|
13
|
+
body: {"detail":"event rejected due to rate limit"}):
|
14
|
+
```
|
15
|
+
|
16
|
+
This change improves the SDK's handling on such responses by:
|
17
|
+
|
18
|
+
- Not treating them as errors, so you don't see the noise anymore.
|
19
|
+
- Halting event sending for a while according to the duration provided in the response. And warns you with a message like:
|
20
|
+
|
21
|
+
```
|
22
|
+
Envelope [event] not sent: Excluded by random sample
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Record request span from Net::HTTP library [#1381](https://github.com/getsentry/sentry-ruby/pull/1381)
|
26
|
+
|
27
|
+
Now any outgoing requests will be recorded as a tracing span. Example:
|
28
|
+
|
29
|
+
<img width="60%" alt="net:http span example" src="https://user-images.githubusercontent.com/5079556/115838944-c1279a80-a44c-11eb-8c67-dfd92bf68bbd.png">
|
30
|
+
|
31
|
+
|
32
|
+
#### Record breadcrumb for Net::HTTP requests [#1394](https://github.com/getsentry/sentry-ruby/pull/1394)
|
33
|
+
|
34
|
+
With the new `http_logger` breadcrumbs logger:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
config.breadcrumbs_logger = [:http_logger]
|
38
|
+
```
|
39
|
+
|
40
|
+
The SDK now records a new `net.http` breadcrumb whenever the user makes a request with the `Net::HTTP` library.
|
41
|
+
|
42
|
+
<img width="60%" alt="net http breadcrumb" src="https://user-images.githubusercontent.com/5079556/114298326-5f7c3d80-9ae8-11eb-9108-222384a7f1a2.png">
|
43
|
+
|
44
|
+
#### Support config.debug configuration option [#1400](https://github.com/getsentry/sentry-ruby/pull/1400)
|
45
|
+
|
46
|
+
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.
|
47
|
+
|
48
|
+
#### Add the third tracing state [#1402](https://github.com/getsentry/sentry-ruby/pull/1402)
|
49
|
+
- `rate == 0` - Tracing enabled. Rejects all locally created transactions but respects sentry-trace.
|
50
|
+
- `1 > rate > 0` - Tracing enabled. Samples locally created transactions with the rate and respects sentry-trace.
|
51
|
+
- `rate < 0` or `rate > 1` - Tracing disabled.
|
52
|
+
|
53
|
+
### Refactorings
|
54
|
+
|
55
|
+
- Let Transaction constructor take an optional hub argument [#1384](https://github.com/getsentry/sentry-ruby/pull/1384)
|
56
|
+
- Introduce LoggingHelper [#1385](https://github.com/getsentry/sentry-ruby/pull/1385)
|
57
|
+
- Raise exception if a Transaction is initialized without a hub [#1391](https://github.com/getsentry/sentry-ruby/pull/1391)
|
58
|
+
- Make hub a required argument for Transaction constructor [#1401](https://github.com/getsentry/sentry-ruby/pull/1401)
|
59
|
+
|
60
|
+
### Bug Fixes
|
61
|
+
|
62
|
+
- Check `Scope#set_context`'s value argument [#1415](https://github.com/getsentry/sentry-ruby/pull/1415)
|
63
|
+
|
3
64
|
## 4.3.2
|
4
65
|
|
5
66
|
- 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,8 @@ 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"
|
10
|
+
require "sentry/net/http"
|
9
11
|
require "sentry/configuration"
|
10
12
|
require "sentry/logger"
|
11
13
|
require "sentry/event"
|
@@ -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)
|
@@ -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,8 +88,10 @@ 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
|
|
@@ -112,9 +116,8 @@ module Sentry
|
|
112
116
|
end
|
113
117
|
rescue => e
|
114
118
|
loggable_event_type = event_hash["type"] || "event"
|
115
|
-
|
119
|
+
log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
|
116
120
|
send_event(event, hint)
|
117
121
|
end
|
118
|
-
|
119
122
|
end
|
120
123
|
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
|
|
@@ -167,13 +172,12 @@ module Sentry
|
|
167
172
|
LOG_PREFIX = "** [Sentry] ".freeze
|
168
173
|
MODULE_SEPARATOR = "::".freeze
|
169
174
|
|
170
|
-
AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
|
171
|
-
|
172
175
|
# Post initialization callbacks are called at the end of initialization process
|
173
176
|
# allowing extending the configuration of sentry-ruby by multiple extensions
|
174
177
|
@@post_initialization_callbacks = []
|
175
178
|
|
176
179
|
def initialize
|
180
|
+
self.debug = false
|
177
181
|
self.background_worker_threads = Concurrent.processor_count
|
178
182
|
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
|
179
183
|
self.breadcrumbs_logger = []
|
@@ -226,10 +230,6 @@ module Sentry
|
|
226
230
|
if logger.is_a?(Array)
|
227
231
|
logger
|
228
232
|
else
|
229
|
-
unless AVAILABLE_BREADCRUMBS_LOGGERS.include?(logger)
|
230
|
-
raise Sentry::Error, "Unsupported breadcrumbs logger. Supported loggers: #{AVAILABLE_BREADCRUMBS_LOGGERS}"
|
231
|
-
end
|
232
|
-
|
233
233
|
Array(logger)
|
234
234
|
end
|
235
235
|
|
@@ -278,10 +278,10 @@ module Sentry
|
|
278
278
|
def exception_class_allowed?(exc)
|
279
279
|
if exc.is_a?(Sentry::Error)
|
280
280
|
# Try to prevent error reporting loops
|
281
|
-
|
281
|
+
log_debug("Refusing to capture Sentry error: #{exc.inspect}")
|
282
282
|
false
|
283
283
|
elsif excluded_exception?(exc)
|
284
|
-
|
284
|
+
log_debug("User excluded error: #{exc.inspect}")
|
285
285
|
false
|
286
286
|
else
|
287
287
|
true
|
@@ -293,7 +293,7 @@ module Sentry
|
|
293
293
|
end
|
294
294
|
|
295
295
|
def tracing_enabled?
|
296
|
-
!!((@traces_sample_rate && @traces_sample_rate
|
296
|
+
!!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler)
|
297
297
|
end
|
298
298
|
|
299
299
|
def stacktrace_builder
|
@@ -314,7 +314,7 @@ module Sentry
|
|
314
314
|
detect_release_from_capistrano ||
|
315
315
|
detect_release_from_heroku
|
316
316
|
rescue => e
|
317
|
-
|
317
|
+
log_error("Error detecting release", e, debug: debug)
|
318
318
|
end
|
319
319
|
|
320
320
|
def excluded_exception?(incoming_exception)
|
@@ -349,7 +349,7 @@ module Sentry
|
|
349
349
|
def detect_release_from_heroku
|
350
350
|
return unless running_on_heroku?
|
351
351
|
return if ENV['CI']
|
352
|
-
|
352
|
+
log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
|
353
353
|
|
354
354
|
ENV['HEROKU_SLUG_COMMIT']
|
355
355
|
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,87 @@
|
|
1
|
+
require "net/http"
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Net
|
5
|
+
module HTTP
|
6
|
+
OP_NAME = "net.http"
|
7
|
+
|
8
|
+
def request(req, body = nil, &block)
|
9
|
+
super.tap do |res|
|
10
|
+
record_sentry_breadcrumb(req, res)
|
11
|
+
record_sentry_span(req, res)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def do_start
|
16
|
+
super.tap do
|
17
|
+
start_sentry_span
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def do_finish
|
22
|
+
super.tap do
|
23
|
+
finish_sentry_span
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def record_sentry_breadcrumb(req, res)
|
30
|
+
if Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
31
|
+
return if from_sentry_sdk?
|
32
|
+
|
33
|
+
request_info = extract_request_info(req)
|
34
|
+
crumb = Sentry::Breadcrumb.new(
|
35
|
+
level: :info,
|
36
|
+
category: OP_NAME,
|
37
|
+
type: :info,
|
38
|
+
data: {
|
39
|
+
method: request_info[:method],
|
40
|
+
url: request_info[:url],
|
41
|
+
status: res.code.to_i
|
42
|
+
}
|
43
|
+
)
|
44
|
+
Sentry.add_breadcrumb(crumb)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def record_sentry_span(req, res)
|
49
|
+
if Sentry.initialized? && @sentry_span
|
50
|
+
request_info = extract_request_info(req)
|
51
|
+
@sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
52
|
+
@sentry_span.set_data(:status, res.code.to_i)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def start_sentry_span
|
57
|
+
if Sentry.initialized? && transaction = Sentry.get_current_scope.get_transaction
|
58
|
+
return if from_sentry_sdk?
|
59
|
+
return if transaction.sampled == false
|
60
|
+
|
61
|
+
child_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
|
62
|
+
@sentry_span = child_span
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def finish_sentry_span
|
67
|
+
if Sentry.initialized? && @sentry_span
|
68
|
+
@sentry_span.set_timestamp(Sentry.utc_now.to_f)
|
69
|
+
@sentry_span = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def from_sentry_sdk?
|
74
|
+
dsn_host = Sentry.configuration.dsn.host
|
75
|
+
dsn_host == self.address
|
76
|
+
end
|
77
|
+
|
78
|
+
def extract_request_info(req)
|
79
|
+
uri = req.uri
|
80
|
+
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
81
|
+
{ method: req.method, url: url }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
Net::HTTP.send(:prepend, Sentry::Net::HTTP)
|
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
@@ -6,12 +6,17 @@ module Sentry
|
|
6
6
|
PROTOCOL_VERSION = '5'
|
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.4.0.pre.beta.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: 2021-04-
|
11
|
+
date: 2021-04-23 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,9 +122,9 @@ 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
129
|
rubygems_version: 3.0.3.1
|
128
130
|
signing_key:
|