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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66ef212c06425a29b56b64935c1fb662633fa80fb9f58b9680b1d95220ee8b5d
4
- data.tar.gz: 18e211d0c93d18717795b23290c2cb7da503b0dcd1690c1c8b51861c3a37ef98
3
+ metadata.gz: 55322eb3e1391f625b292b0827c37a6e3c44ea4aacc6b9cc35a77f3feb2522a0
4
+ data.tar.gz: 4d07834ec0038f98f990141b2997a5f6e0a00db33183d928d129dd67fa1324cd
5
5
  SHA512:
6
- metadata.gz: e334875a26a04e45a78c37e7bb0014604cd2b400b89eb01faa3ae21adc1f9072e46227ce880c1406148e32d6a125504dc08ffab5e1ac61c0b3befbd31e5352ea
7
- data.tar.gz: 4c01fb8d489a3d143a738180f3f68ba62d3882267ecf17b82c3cf0e509ab36a8b458699d022d5d55699baacaccd2acc3bfede0d332ae5b50776128ac5c164a2a
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
@@ -8,7 +8,11 @@ gem "i18n", "<= 1.8.7"
8
8
 
9
9
  gem "rake", "~> 12.0"
10
10
  gem "rspec", "~> 3.0"
11
+ gem "rspec-retry"
12
+ gem "webmock"
13
+ gem "timecop"
11
14
  gem "codecov", "0.2.12"
15
+ gem "tapping_device"
12
16
 
13
17
  gem "pry"
14
18
  gem "rack" unless ENV["WITHOUT_RACK"] == "1"
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
- **The old `sentry-raven` client has entered maintenance mode and was moved to [here](https://github.com/getsentry/sentry-ruby/tree/master/sentry-raven).**
15
-
16
- ---
13
+ | current version | build | coverage | downloads | semver stability |
14
+ | --- | ----- | -------- | --------- | ---------------- |
15
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://github.com/getsentry/sentry-ruby/blob/master/sentry-ruby/CHANGELOG.md) | ![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-ruby%20Test/badge.svg) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-ruby.svg)](https://rubygems.org/gems/sentry-ruby/) | [![SemVer stability](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver) |
16
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://github.com/getsentry/sentry-ruby/blob/master/sentry-rails/CHANGELOG.md) | ![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-rails%20Test/badge.svg) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails/) | [![SemVer stability](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-rails&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-rails&package-manager=bundler&version-scheme=semver) |
17
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://github.com/getsentry/sentry-ruby/blob/master/sentry-sidekiq/CHANGELOG.md) | ![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-sidekiq%20Test/badge.svg) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-sidekiq.svg)](https://rubygems.org/gems/sentry-sidekiq/) | [![SemVer stability](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-sidekiq&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-sidekiq&package-manager=bundler&version-scheme=semver) |
18
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://github.com/getsentry/sentry-ruby/blob/master/sentry-delayed_job/CHANGELOG.md) | ![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-delayed_job%20Test/badge.svg) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-delayed_job.svg)](https://rubygems.org/gems/sentry-delayed_job/) | [![SemVer stability](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-delayed_job&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-delayed_job&package-manager=bundler&version-scheme=semver) |
17
19
 
18
20
 
19
- [![Gem Version](https://img.shields.io/gem/v/sentry-ruby.svg)](https://rubygems.org/gems/sentry-ruby)
20
- ![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-ruby%20Test/badge.svg)
21
- [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master)
22
- [![Gem](https://img.shields.io/gem/dt/sentry-ruby.svg)](https://rubygems.org/gems/sentry-ruby/)
23
- [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-ruby&package-manager=bundler&version-scheme=semver)
24
21
 
25
22
 
26
- [Documentation](https://docs.sentry.io/platforms/ruby/) | [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues) | [Forum](https://forum.sentry.io/) | IRC: irc.freenode.net, #sentry
23
+ ## Migrate From sentry-raven
27
24
 
28
- The official Ruby-language client and integration layer for the [Sentry](https://github.com/getsentry/sentry) error reporting API.
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#L118) 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)
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
- ## More Information
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
+ * [![Ruby docs](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=ruby%20docs)](https://docs.sentry.io/platforms/ruby/)
247
+ * [![Forum](https://img.shields.io/badge/forum-sentry-green.svg)](https://forum.sentry.io/c/sdks)
248
+ * [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/PXa5Apfe7K)
249
+ * [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](https://stackoverflow.com/questions/tagged/sentry)
250
+ * [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](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[THREAD_LOCAL] = hub
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
- get_current_scope.breadcrumbs.record(breadcrumb)
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[THREAD_LOCAL] || clone_hub_to_current_thread
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[THREAD_LOCAL] = get_main_hub.clone
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
- attr_reader :max_queue, :number_of_threads
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
- configuration.logger.debug(LOGGER_PROGNAME) { "config.async is set, BackgroundWorker is disabled" }
18
+ log_debug("config.async is set, BackgroundWorker is disabled")
16
19
  Concurrent::ImmediateExecutor.new
17
20
  elsif @number_of_threads == 0
18
- configuration.logger.debug(LOGGER_PROGNAME) { "config.background_worker_threads is set to 0, all events will be sent synchronously" }
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
- configuration.logger.debug(LOGGER_PROGNAME) { "initialized a background worker with #{@number_of_threads} threads" }
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.empty?
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
- logger.error(LOGGER_PROGNAME) { "Event capturing failed: #{e.message}" }
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 == "event" && configuration.before_send
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
- logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
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
- logger.error(LOGGER_PROGNAME) { "#{event_type.capitalize} sending failed: #{e.message}" }
89
- logger.error(LOGGER_PROGNAME) { "Unreported #{event_type.capitalize}: #{Event.get_log_message(event.to_hash)}" }
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
- event_type = event_hash["type"]
114
- logger.error(LOGGER_PROGNAME) { "Async #{event_type} sending failed: #{e.message}" }
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
@@ -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
- logger.debug(LOGGER_PROGNAME) { "Refusing to capture Sentry error: #{exc.inspect}" }
281
+ log_debug("Refusing to capture Sentry error: #{exc.inspect}")
282
282
  false
283
283
  elsif excluded_exception?(exc)
284
- logger.debug(LOGGER_PROGNAME) { "User excluded error: #{exc.inspect}" }
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 > 0.0) || @traces_sampler)
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
- logger.error(LOGGER_PROGNAME) { "Error detecting release: #{e.message}" }
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
- logger.warn(LOGGER_PROGNAME) { HEROKU_DYNO_METADATA_MESSAGE } && return unless ENV['HEROKU_SLUG_COMMIT']
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
@@ -100,7 +100,6 @@ module Sentry
100
100
  end
101
101
 
102
102
  def type
103
- "event"
104
103
  end
105
104
 
106
105
  def to_hash
@@ -2,6 +2,6 @@ module Sentry
2
2
  class Error < StandardError
3
3
  end
4
4
 
5
- class ExternalError < StandardError
5
+ class ExternalError < Error
6
6
  end
7
7
  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,11 +73,19 @@ module Sentry
69
73
  @stack.pop
70
74
  end
71
75
 
72
- def start_transaction(transaction: nil, configuration: Sentry.configuration, **options)
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
- transaction.set_initial_sample_decision(configuration: current_client.configuration)
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 = contexts_hash
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
- # transaction will always be the first in the span_recorder
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(description: nil, op: nil, status: nil, trace_id: nil, parent_span_id: nil, sampled: nil, start_timestamp: nil, timestamp: nil)
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)
@@ -10,37 +10,38 @@ module Sentry
10
10
  UNLABELD_NAME = "<unlabeled transaction>".freeze
11
11
  MESSAGE_PREFIX = "[Tracing]"
12
12
 
13
- attr_reader :name, :parent_sampled
13
+ include LoggingHelper
14
14
 
15
- def initialize(name: nil, parent_sampled: nil, **options)
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
- set_span_recorder
21
- end
22
-
23
- def set_span_recorder
24
- @span_recorder = SpanRecorder.new(1000)
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, configuration: Sentry.configuration, **options)
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
- sampled =
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: sampled, sampled: sampled, **options)
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.set_span_recorder
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: {}, configuration: Sentry.configuration)
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
- if traces_sampler.is_a?(Proc)
91
- sampling_context = sampling_context.merge(
92
- parent_sampled: @parent_sampled,
93
- transaction_context: self.to_hash
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
- sample_rate = traces_sampler.call(sampling_context)
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
- logger.warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
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
- logger.debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
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
- logger.debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
106
+ log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
119
107
  else
120
- logger.debug(
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
- "transaction"
22
+ TYPE
21
23
  end
22
24
 
23
25
  def to_hash
@@ -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
- configuration.logger.debug(LOGGER_PROGNAME) { "Event not sent: #{configuration.error_messages}" }
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 = prepare_encoded_event(event)
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(event_hash)
49
- event_id = event_hash[:event_id] || event_hash['event_id']
50
- event_type = event_hash[:type] || event_hash['type']
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":"#{event_type}","content_type":"application/json"}
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 prepare_encoded_event(event)
64
- # Convert to hash
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
- error_info += "\nbody: #{e.response[:body]}"
38
- error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error']
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
- configuration.logger.debug(LOGGER_PROGNAME) { "Sentry HTTP Transport connecting to #{server}" }
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
@@ -1,3 +1,3 @@
1
1
  module Sentry
2
- VERSION = "4.3.0"
2
+ VERSION = "4.4.1"
3
3
  end
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.3.0
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-03-12 00:00:00.000000000 Z
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