sentry-ruby-core 4.3.0 → 4.4.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 +4 -3
- data/CHANGELOG.md +84 -0
- data/Gemfile +4 -0
- data/README.md +16 -22
- data/lib/sentry-ruby.rb +26 -5
- data/lib/sentry/background_worker.rb +7 -4
- data/lib/sentry/breadcrumb/sentry_logger.rb +1 -1
- data/lib/sentry/client.rb +12 -8
- data/lib/sentry/configuration.rb +11 -11
- data/lib/sentry/event.rb +0 -1
- data/lib/sentry/exceptions.rb +1 -1
- data/lib/sentry/hub.rb +15 -3
- data/lib/sentry/net/http.rb +90 -0
- data/lib/sentry/scope.rb +3 -3
- data/lib/sentry/span.rb +21 -4
- data/lib/sentry/transaction.rb +46 -41
- data/lib/sentry/transaction_event.rb +3 -1
- data/lib/sentry/transport.rb +57 -14
- 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 +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55322eb3e1391f625b292b0827c37a6e3c44ea4aacc6b9cc35a77f3feb2522a0
|
4
|
+
data.tar.gz: 4d07834ec0038f98f990141b2997a5f6e0a00db33183d928d129dd67fa1324cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 220d28a57647a0c91d28502fe94913d87fcb5f81f7ae474da274ee5855350d911adb1c5b0786b0acbced0308bc37baf911203bda1d8b03348386e6e7f882db9e
|
7
|
+
data.tar.gz: 443749298c01be72f32bbbb82759c3d8ffd765e72bb0134405685798e7d92584742f8402b1dd2bf88d5b079dc48d24690b9951fa3f88cbafad1d0aa01b2cb9c7
|
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,89 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 4.4.1
|
4
|
+
|
5
|
+
- Apply patches when initializing the SDK [#1432](https://github.com/getsentry/sentry-ruby/pull/1432)
|
6
|
+
|
7
|
+
## 4.4.0
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
#### Support category-based rate limiting [#1336](https://github.com/getsentry/sentry-ruby/pull/1336)
|
12
|
+
|
13
|
+
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:
|
14
|
+
|
15
|
+
```
|
16
|
+
Unable to record event with remote Sentry server (Sentry::Error - the server responded with status 429
|
17
|
+
body: {"detail":"event rejected due to rate limit"}):
|
18
|
+
```
|
19
|
+
|
20
|
+
This change improves the SDK's handling on such responses by:
|
21
|
+
|
22
|
+
- Not treating them as errors, so you don't see the noise anymore.
|
23
|
+
- Halting event sending for a while according to the duration provided in the response. And warns you with a message like:
|
24
|
+
|
25
|
+
```
|
26
|
+
Envelope [event] not sent: Excluded by random sample
|
27
|
+
```
|
28
|
+
|
29
|
+
#### Record request span from Net::HTTP library [#1381](https://github.com/getsentry/sentry-ruby/pull/1381)
|
30
|
+
|
31
|
+
Now any outgoing requests will be recorded as a tracing span. Example:
|
32
|
+
|
33
|
+
<img width="60%" alt="net:http span example" src="https://user-images.githubusercontent.com/5079556/115838944-c1279a80-a44c-11eb-8c67-dfd92bf68bbd.png">
|
34
|
+
|
35
|
+
|
36
|
+
#### Record breadcrumb for Net::HTTP requests [#1394](https://github.com/getsentry/sentry-ruby/pull/1394)
|
37
|
+
|
38
|
+
With the new `http_logger` breadcrumbs logger:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
config.breadcrumbs_logger = [:http_logger]
|
42
|
+
```
|
43
|
+
|
44
|
+
The SDK now records a new `net.http` breadcrumb whenever the user makes a request with the `Net::HTTP` library.
|
45
|
+
|
46
|
+
<img width="60%" alt="net http breadcrumb" src="https://user-images.githubusercontent.com/5079556/114298326-5f7c3d80-9ae8-11eb-9108-222384a7f1a2.png">
|
47
|
+
|
48
|
+
#### Support config.debug configuration option [#1400](https://github.com/getsentry/sentry-ruby/pull/1400)
|
49
|
+
|
50
|
+
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.
|
51
|
+
|
52
|
+
#### Add the third tracing state [#1402](https://github.com/getsentry/sentry-ruby/pull/1402)
|
53
|
+
- `rate == 0` - Tracing enabled. Rejects all locally created transactions but respects sentry-trace.
|
54
|
+
- `1 > rate > 0` - Tracing enabled. Samples locally created transactions with the rate and respects sentry-trace.
|
55
|
+
- `rate < 0` or `rate > 1` - Tracing disabled.
|
56
|
+
|
57
|
+
### Refactorings
|
58
|
+
|
59
|
+
- Let Transaction constructor take an optional hub argument [#1384](https://github.com/getsentry/sentry-ruby/pull/1384)
|
60
|
+
- Introduce LoggingHelper [#1385](https://github.com/getsentry/sentry-ruby/pull/1385)
|
61
|
+
- Raise exception if a Transaction is initialized without a hub [#1391](https://github.com/getsentry/sentry-ruby/pull/1391)
|
62
|
+
- Make hub a required argument for Transaction constructor [#1401](https://github.com/getsentry/sentry-ruby/pull/1401)
|
63
|
+
|
64
|
+
### Bug Fixes
|
65
|
+
|
66
|
+
- Check `Scope#set_context`'s value argument [#1415](https://github.com/getsentry/sentry-ruby/pull/1415)
|
67
|
+
- Disable tracing if events are not allowed to be sent [#1421](https://github.com/getsentry/sentry-ruby/pull/1421)
|
68
|
+
|
69
|
+
## 4.3.2
|
70
|
+
|
71
|
+
- Correct type attribute's usages [#1354](https://github.com/getsentry/sentry-ruby/pull/1354)
|
72
|
+
- Fix sampling decision precedence [#1335](https://github.com/getsentry/sentry-ruby/pull/1335)
|
73
|
+
- Fix set_contexts [#1375](https://github.com/getsentry/sentry-ruby/pull/1375)
|
74
|
+
- Use thread variable instead of fiber variable to store the hub [#1380](https://github.com/getsentry/sentry-ruby/pull/1380)
|
75
|
+
- Fixes [#1374](https://github.com/getsentry/sentry-ruby/issues/1374)
|
76
|
+
- Fix Span/Transaction's nesting issue [#1382](https://github.com/getsentry/sentry-ruby/pull/1382)
|
77
|
+
- Fixes [#1372](https://github.com/getsentry/sentry-ruby/issues/1372)
|
78
|
+
|
79
|
+
## 4.3.1
|
80
|
+
|
81
|
+
- Add Sentry.set_context helper [#1337](https://github.com/getsentry/sentry-ruby/pull/1337)
|
82
|
+
- Fix handle the case where the logger messages is not of String type [#1341](https://github.com/getsentry/sentry-ruby/pull/1341)
|
83
|
+
- Don't report Sentry::ExternalError to Sentry [#1353](https://github.com/getsentry/sentry-ruby/pull/1353)
|
84
|
+
- Sentry.add_breadcrumb should call Hub#add_breadcrumb [#1358](https://github.com/getsentry/sentry-ruby/pull/1358)
|
85
|
+
- Fixes [#1357](https://github.com/getsentry/sentry-ruby/issues/1357)
|
86
|
+
|
3
87
|
## 4.3.0
|
4
88
|
|
5
89
|
### Features
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,23 +10,21 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to he
|
|
10
10
|
Sentry SDK for Ruby
|
11
11
|
===========
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
| current version | build | coverage | downloads | semver stability |
|
14
|
+
| --- | ----- | -------- | --------- | ---------------- |
|
15
|
+
| [](https://github.com/getsentry/sentry-ruby/blob/master/sentry-ruby/CHANGELOG.md) |  | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-ruby/) | [](https://dependabot.com/compatibility-score.html?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver) |
|
16
|
+
| [](https://github.com/getsentry/sentry-ruby/blob/master/sentry-rails/CHANGELOG.md) |  | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-rails/) | [](https://dependabot.com/compatibility-score.html?dependency-name=sentry-rails&package-manager=bundler&version-scheme=semver) |
|
17
|
+
| [](https://github.com/getsentry/sentry-ruby/blob/master/sentry-sidekiq/CHANGELOG.md) |  | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-sidekiq/) | [](https://dependabot.com/compatibility-score.html?dependency-name=sentry-sidekiq&package-manager=bundler&version-scheme=semver) |
|
18
|
+
| [](https://github.com/getsentry/sentry-ruby/blob/master/sentry-delayed_job/CHANGELOG.md) |  | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-delayed_job/) | [](https://dependabot.com/compatibility-score.html?dependency-name=sentry-delayed_job&package-manager=bundler&version-scheme=semver) |
|
17
19
|
|
18
20
|
|
19
|
-
[](https://rubygems.org/gems/sentry-ruby)
|
20
|
-

|
21
|
-
[](https://codecov.io/gh/getsentry/sentry-ruby/branch/master)
|
22
|
-
[](https://rubygems.org/gems/sentry-ruby/)
|
23
|
-
[](https://dependabot.com/compatibility-score.html?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver)
|
24
21
|
|
25
22
|
|
26
|
-
|
23
|
+
## Migrate From sentry-raven
|
27
24
|
|
28
|
-
The
|
25
|
+
**The old `sentry-raven` client has entered maintenance mode and was moved to [here](https://github.com/getsentry/sentry-ruby/tree/master/sentry-raven).**
|
29
26
|
|
27
|
+
If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You can find the benefits of migrating and how to do it in our [migration guide](https://docs.sentry.io/platforms/ruby/migration/).
|
30
28
|
|
31
29
|
## Requirements
|
32
30
|
|
@@ -34,10 +32,6 @@ We test on Ruby 2.4, 2.5, 2.6, 2.7, and 3.0 at the latest patchlevel/teeny versi
|
|
34
32
|
|
35
33
|
If you use self-hosted Sentry, please also make sure its version is above `20.6.0`.
|
36
34
|
|
37
|
-
## Migrate From sentry-raven
|
38
|
-
|
39
|
-
If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You can find the benefits of migrating and how to do it in our [migration guide](https://docs.sentry.io/platforms/ruby/migration/).
|
40
|
-
|
41
35
|
## Getting Started
|
42
36
|
|
43
37
|
### Install
|
@@ -72,7 +66,7 @@ end
|
|
72
66
|
|
73
67
|
### Sentry doesn't report some kinds of data by default
|
74
68
|
|
75
|
-
**Sentry ignores some exceptions by default** - most of these are related to 404s parameter parsing errors. [For a complete list, see the `IGNORE_DEFAULT` constant](https://github.com/getsentry/sentry-ruby/blob/master/sentry-ruby/lib/sentry/configuration.rb#
|
69
|
+
**Sentry ignores some exceptions by default** - most of these are related to 404s parameter parsing errors. [For a complete list, see the `IGNORE_DEFAULT` constant](https://github.com/getsentry/sentry-ruby/blob/master/sentry-ruby/lib/sentry/configuration.rb#L151) and the integration gems' `IGNORE_DEFAULT`, like [`sentry-rails`'s](https://github.com/getsentry/sentry-ruby/blob/master/sentry-rails/lib/sentry/rails/configuration.rb#L12)
|
76
70
|
|
77
71
|
Sentry doesn't send personally identifiable information (pii) by default, such as request body, user ip or cookies. If you want those information to be sent, you can use the `send_default_pii` config option:
|
78
72
|
|
@@ -247,10 +241,10 @@ Of course, you can always assign the information on a per-event basis:
|
|
247
241
|
Sentry.capture_exception(exception, tags: {foo: "bar"})
|
248
242
|
```
|
249
243
|
|
250
|
-
##
|
251
|
-
|
252
|
-
* [Documentation](https://docs.sentry.io/platforms/ruby/)
|
253
|
-
* [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues)
|
254
|
-
* [Forum](https://forum.sentry.io/)
|
255
|
-
- [Discord](https://discord.gg/ez5KZN7)
|
244
|
+
## Resources
|
256
245
|
|
246
|
+
* [](https://docs.sentry.io/platforms/ruby/)
|
247
|
+
* [](https://forum.sentry.io/c/sdks)
|
248
|
+
* [](https://discord.gg/PXa5Apfe7K)
|
249
|
+
* [](https://stackoverflow.com/questions/tagged/sentry)
|
250
|
+
* [](https://twitter.com/intent/follow?screen_name=getsentry)
|
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"
|
@@ -58,17 +59,34 @@ module Sentry
|
|
58
59
|
extend Forwardable
|
59
60
|
|
60
61
|
def_delegators :get_current_client, :configuration, :send_event
|
61
|
-
def_delegators :get_current_scope, :set_tags, :set_extras, :set_user
|
62
|
+
def_delegators :get_current_scope, :set_tags, :set_extras, :set_user, :set_context
|
62
63
|
|
63
64
|
attr_accessor :background_worker
|
64
65
|
|
66
|
+
@@registered_patches = []
|
67
|
+
|
68
|
+
def register_patch(&block)
|
69
|
+
registered_patches << block
|
70
|
+
end
|
71
|
+
|
72
|
+
def apply_patches(config)
|
73
|
+
registered_patches.each do |patch|
|
74
|
+
patch.call(config)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def registered_patches
|
79
|
+
@@registered_patches
|
80
|
+
end
|
81
|
+
|
65
82
|
def init(&block)
|
66
83
|
config = Configuration.new
|
67
84
|
yield(config) if block_given?
|
85
|
+
apply_patches(config)
|
68
86
|
client = Client.new(config)
|
69
87
|
scope = Scope.new(max_breadcrumbs: config.max_breadcrumbs)
|
70
88
|
hub = Hub.new(client, scope)
|
71
|
-
Thread.current
|
89
|
+
Thread.current.thread_variable_set(THREAD_LOCAL, hub)
|
72
90
|
@main_hub = hub
|
73
91
|
@background_worker = Sentry::BackgroundWorker.new(config)
|
74
92
|
end
|
@@ -80,7 +98,7 @@ module Sentry
|
|
80
98
|
|
81
99
|
# Takes an instance of Sentry::Breadcrumb and stores it to the current active scope.
|
82
100
|
def add_breadcrumb(breadcrumb)
|
83
|
-
|
101
|
+
get_current_hub&.add_breadcrumb(breadcrumb)
|
84
102
|
end
|
85
103
|
|
86
104
|
# Returns the current active hub.
|
@@ -92,7 +110,7 @@ module Sentry
|
|
92
110
|
# ideally, we should do this proactively whenever a new thread is created
|
93
111
|
# but it's impossible for the SDK to keep track every new thread
|
94
112
|
# so we need to use this rather passive way to make sure the app doesn't crash
|
95
|
-
Thread.current
|
113
|
+
Thread.current.thread_variable_get(THREAD_LOCAL) || clone_hub_to_current_thread
|
96
114
|
end
|
97
115
|
|
98
116
|
# Returns the current active client.
|
@@ -107,7 +125,7 @@ module Sentry
|
|
107
125
|
|
108
126
|
# Clones the main thread's active hub and stores it to the current thread.
|
109
127
|
def clone_hub_to_current_thread
|
110
|
-
Thread.current
|
128
|
+
Thread.current.thread_variable_set(THREAD_LOCAL, get_main_hub.clone)
|
111
129
|
end
|
112
130
|
|
113
131
|
# Takes a block and yields the current active scope.
|
@@ -188,3 +206,6 @@ module Sentry
|
|
188
206
|
end
|
189
207
|
end
|
190
208
|
end
|
209
|
+
|
210
|
+
# patches
|
211
|
+
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,
|
@@ -50,7 +50,7 @@ module Sentry
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
return if ignored_logger?(progname) || message
|
53
|
+
return if ignored_logger?(progname) || message == ""
|
54
54
|
|
55
55
|
# some loggers will add leading/trailing space as they (incorrectly, mind you)
|
56
56
|
# think of logging as a shortcut to std{out,err}
|
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
|
|
@@ -72,11 +74,11 @@ module Sentry
|
|
72
74
|
def send_event(event, hint = nil)
|
73
75
|
event_type = event.is_a?(Event) ? event.type : event["type"]
|
74
76
|
|
75
|
-
if event_type
|
77
|
+
if event_type != TransactionEvent::TYPE && configuration.before_send
|
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
|
@@ -85,8 +87,11 @@ module Sentry
|
|
85
87
|
|
86
88
|
event
|
87
89
|
rescue => e
|
88
|
-
|
89
|
-
|
90
|
+
loggable_event_type = (event_type || "event").capitalize
|
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}")
|
90
95
|
raise
|
91
96
|
end
|
92
97
|
|
@@ -110,10 +115,9 @@ module Sentry
|
|
110
115
|
async_block.call(event_hash)
|
111
116
|
end
|
112
117
|
rescue => e
|
113
|
-
|
114
|
-
|
118
|
+
loggable_event_type = event_hash["type"] || "event"
|
119
|
+
log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
|
115
120
|
send_event(event, hint)
|
116
121
|
end
|
117
|
-
|
118
122
|
end
|
119
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) && sending_allowed?
|
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/event.rb
CHANGED
data/lib/sentry/exceptions.rb
CHANGED
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,11 +73,19 @@ 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)
|
76
|
-
|
79
|
+
transaction ||= Transaction.new(**options.merge(hub: self))
|
80
|
+
|
81
|
+
sampling_context = {
|
82
|
+
transaction_context: transaction.to_hash,
|
83
|
+
parent_sampled: transaction.parent_sampled
|
84
|
+
}
|
85
|
+
|
86
|
+
sampling_context.merge!(custom_sampling_context)
|
87
|
+
|
88
|
+
transaction.set_initial_sample_decision(sampling_context: sampling_context)
|
77
89
|
transaction
|
78
90
|
end
|
79
91
|
|
@@ -0,0 +1,90 @@
|
|
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
|
+
Sentry.register_patch do
|
88
|
+
patch = Sentry::Net::HTTP
|
89
|
+
Net::HTTP.send(:prepend, patch) unless Net::HTTP.ancestors.include?(patch)
|
90
|
+
end
|
data/lib/sentry/scope.rb
CHANGED
@@ -126,10 +126,11 @@ module Sentry
|
|
126
126
|
|
127
127
|
def set_contexts(contexts_hash)
|
128
128
|
check_argument_type!(contexts_hash, Hash)
|
129
|
-
@contexts
|
129
|
+
@contexts.merge!(contexts_hash)
|
130
130
|
end
|
131
131
|
|
132
132
|
def set_context(key, value)
|
133
|
+
check_argument_type!(value, Hash)
|
133
134
|
@contexts.merge!(key => value)
|
134
135
|
end
|
135
136
|
|
@@ -146,8 +147,7 @@ module Sentry
|
|
146
147
|
end
|
147
148
|
|
148
149
|
def get_transaction
|
149
|
-
|
150
|
-
span.span_recorder.spans.first if span
|
150
|
+
span.transaction if span
|
151
151
|
end
|
152
152
|
|
153
153
|
def get_span
|
data/lib/sentry/span.rb
CHANGED
@@ -19,9 +19,18 @@ module Sentry
|
|
19
19
|
|
20
20
|
|
21
21
|
attr_reader :trace_id, :span_id, :parent_span_id, :sampled, :start_timestamp, :timestamp, :description, :op, :status, :tags, :data
|
22
|
-
attr_accessor :span_recorder
|
23
|
-
|
24
|
-
def initialize(
|
22
|
+
attr_accessor :span_recorder, :transaction
|
23
|
+
|
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
|
@@ -78,7 +87,15 @@ module Sentry
|
|
78
87
|
|
79
88
|
def start_child(**options)
|
80
89
|
options = options.dup.merge(trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
|
81
|
-
Span.new(**options)
|
90
|
+
new_span = Span.new(**options)
|
91
|
+
new_span.transaction = transaction
|
92
|
+
new_span.span_recorder = span_recorder
|
93
|
+
|
94
|
+
if span_recorder
|
95
|
+
span_recorder.add(new_span)
|
96
|
+
end
|
97
|
+
|
98
|
+
new_span
|
82
99
|
end
|
83
100
|
|
84
101
|
def with_child_span(**options, &block)
|
data/lib/sentry/transaction.rb
CHANGED
@@ -10,37 +10,38 @@ 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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@span_recorder.add(self)
|
22
|
+
@transaction = self
|
23
|
+
@hub = hub
|
24
|
+
@configuration = hub.configuration
|
25
|
+
@logger = configuration.logger
|
26
|
+
init_span_recorder
|
26
27
|
end
|
27
28
|
|
28
|
-
def self.from_sentry_trace(sentry_trace,
|
29
|
-
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?
|
30
31
|
return unless sentry_trace
|
31
32
|
|
32
33
|
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
33
34
|
return if match.nil?
|
34
35
|
trace_id, parent_span_id, sampled_flag = match[1..3]
|
35
36
|
|
36
|
-
|
37
|
+
parent_sampled =
|
37
38
|
if sampled_flag.nil?
|
38
39
|
nil
|
39
40
|
else
|
40
41
|
sampled_flag != "0"
|
41
42
|
end
|
42
43
|
|
43
|
-
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled:
|
44
|
+
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: parent_sampled, hub: hub, **options)
|
44
45
|
end
|
45
46
|
|
46
47
|
def to_hash
|
@@ -49,20 +50,9 @@ module Sentry
|
|
49
50
|
hash
|
50
51
|
end
|
51
52
|
|
52
|
-
def start_child(**options)
|
53
|
-
child_span = super
|
54
|
-
child_span.span_recorder = @span_recorder
|
55
|
-
|
56
|
-
if @sampled
|
57
|
-
@span_recorder.add(child_span)
|
58
|
-
end
|
59
|
-
|
60
|
-
child_span
|
61
|
-
end
|
62
|
-
|
63
53
|
def deep_dup
|
64
54
|
copy = super
|
65
|
-
copy.
|
55
|
+
copy.init_span_recorder(@span_recorder.max_length)
|
66
56
|
|
67
57
|
@span_recorder.spans.each do |span|
|
68
58
|
# span_recorder's first span is the current span, which should not be added to the copy's spans
|
@@ -73,7 +63,7 @@ module Sentry
|
|
73
63
|
copy
|
74
64
|
end
|
75
65
|
|
76
|
-
def set_initial_sample_decision(sampling_context:
|
66
|
+
def set_initial_sample_decision(sampling_context:)
|
77
67
|
unless configuration.tracing_enabled?
|
78
68
|
@sampled = false
|
79
69
|
return
|
@@ -81,30 +71,28 @@ module Sentry
|
|
81
71
|
|
82
72
|
return unless @sampled.nil?
|
83
73
|
|
84
|
-
transaction_description = generate_transaction_description
|
85
|
-
|
86
|
-
logger = configuration.logger
|
87
|
-
sample_rate = configuration.traces_sample_rate
|
88
74
|
traces_sampler = configuration.traces_sampler
|
89
75
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
76
|
+
sample_rate =
|
77
|
+
if traces_sampler.is_a?(Proc)
|
78
|
+
traces_sampler.call(sampling_context)
|
79
|
+
elsif !sampling_context[:parent_sampled].nil?
|
80
|
+
sampling_context[:parent_sampled]
|
81
|
+
else
|
82
|
+
configuration.traces_sample_rate
|
83
|
+
end
|
95
84
|
|
96
|
-
|
97
|
-
end
|
85
|
+
transaction_description = generate_transaction_description
|
98
86
|
|
99
87
|
unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Numeric) && sample_rate >= 0.0 && sample_rate <= 1.0)
|
100
88
|
@sampled = false
|
101
|
-
|
89
|
+
log_warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
|
102
90
|
return
|
103
91
|
end
|
104
92
|
|
105
93
|
if sample_rate == 0.0 || sample_rate == false
|
106
94
|
@sampled = false
|
107
|
-
|
95
|
+
log_debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
|
108
96
|
return
|
109
97
|
end
|
110
98
|
|
@@ -115,15 +103,26 @@ module Sentry
|
|
115
103
|
end
|
116
104
|
|
117
105
|
if @sampled
|
118
|
-
|
106
|
+
log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
|
119
107
|
else
|
120
|
-
|
108
|
+
log_debug(
|
121
109
|
"#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
|
122
110
|
)
|
123
111
|
end
|
124
112
|
end
|
125
113
|
|
126
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
|
+
|
127
126
|
super() # Span#finish doesn't take arguments
|
128
127
|
|
129
128
|
if @name.nil?
|
@@ -132,11 +131,17 @@ module Sentry
|
|
132
131
|
|
133
132
|
return unless @sampled || @parent_sampled
|
134
133
|
|
135
|
-
hub ||= Sentry.get_current_hub
|
136
134
|
event = hub.current_client.event_from_transaction(self)
|
137
135
|
hub.capture_event(event)
|
138
136
|
end
|
139
137
|
|
138
|
+
protected
|
139
|
+
|
140
|
+
def init_span_recorder(limit = 1000)
|
141
|
+
@span_recorder = SpanRecorder.new(limit)
|
142
|
+
@span_recorder.add(self)
|
143
|
+
end
|
144
|
+
|
140
145
|
private
|
141
146
|
|
142
147
|
def generate_transaction_description
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class TransactionEvent < Event
|
5
|
+
TYPE = "transaction"
|
6
|
+
|
5
7
|
ATTRIBUTES = %i(
|
6
8
|
event_id level timestamp start_timestamp
|
7
9
|
release environment server_name modules
|
@@ -17,7 +19,7 @@ module Sentry
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def type
|
20
|
-
|
22
|
+
TYPE
|
21
23
|
end
|
22
24
|
|
23
25
|
def to_hash
|
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,12 +24,22 @@ 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
|
|
27
|
-
encoded_data =
|
42
|
+
encoded_data = encode(event)
|
28
43
|
|
29
44
|
return nil unless encoded_data
|
30
45
|
|
@@ -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 = {
|
@@ -45,29 +89,28 @@ module Sentry
|
|
45
89
|
'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
|
46
90
|
end
|
47
91
|
|
48
|
-
def encode(
|
49
|
-
|
50
|
-
|
92
|
+
def encode(event)
|
93
|
+
# Convert to hash
|
94
|
+
event_hash = event.to_hash
|
95
|
+
|
96
|
+
event_id = event_hash[:event_id] || event_hash["event_id"]
|
97
|
+
item_type = get_item_type(event_hash)
|
51
98
|
|
52
99
|
envelope = <<~ENVELOPE
|
53
100
|
{"event_id":"#{event_id}","dsn":"#{configuration.dsn.to_s}","sdk":#{Sentry.sdk_meta.to_json},"sent_at":"#{Sentry.utc_now.iso8601}"}
|
54
|
-
{"type":"#{
|
101
|
+
{"type":"#{item_type}","content_type":"application/json"}
|
55
102
|
#{JSON.generate(event_hash)}
|
56
103
|
ENVELOPE
|
57
104
|
|
105
|
+
log_info("Sending envelope [#{item_type}] #{event_id} to Sentry")
|
106
|
+
|
58
107
|
envelope
|
59
108
|
end
|
60
109
|
|
61
110
|
private
|
62
111
|
|
63
|
-
def
|
64
|
-
|
65
|
-
event_hash = event.to_hash
|
66
|
-
|
67
|
-
event_id = event_hash[:event_id] || event_hash["event_id"]
|
68
|
-
event_type = event_hash[:type] || event_hash["type"]
|
69
|
-
configuration.logger.info(LOGGER_PROGNAME) { "Sending #{event_type} #{event_id} to Sentry" }
|
70
|
-
encode(event_hash)
|
112
|
+
def get_item_type(event_hash)
|
113
|
+
event_hash[:type] || event_hash["type"] || "event"
|
71
114
|
end
|
72
115
|
end
|
73
116
|
end
|
@@ -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.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-05 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
|
@@ -124,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
126
|
- !ruby/object:Gem::Version
|
125
127
|
version: '0'
|
126
128
|
requirements: []
|
127
|
-
rubygems_version: 3.0.3
|
129
|
+
rubygems_version: 3.0.3.1
|
128
130
|
signing_key:
|
129
131
|
specification_version: 4
|
130
132
|
summary: A gem that provides a client interface for the Sentry error logger
|