sentry-ruby-core 4.7.1 → 4.8.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -2
  3. data/LICENSE.txt +1 -1
  4. data/README.md +7 -7
  5. data/bin/console +5 -1
  6. data/lib/sentry/background_worker.rb +30 -2
  7. data/lib/sentry/breadcrumb/sentry_logger.rb +2 -0
  8. data/lib/sentry/breadcrumb.rb +2 -0
  9. data/lib/sentry/breadcrumb_buffer.rb +2 -0
  10. data/lib/sentry/client.rb +13 -1
  11. data/lib/sentry/configuration.rb +57 -77
  12. data/lib/sentry/core_ext/object/deep_dup.rb +2 -0
  13. data/lib/sentry/core_ext/object/duplicable.rb +1 -0
  14. data/lib/sentry/dsn.rb +2 -0
  15. data/lib/sentry/envelope.rb +25 -0
  16. data/lib/sentry/event.rb +5 -0
  17. data/lib/sentry/exceptions.rb +2 -0
  18. data/lib/sentry/hub.rb +13 -4
  19. data/lib/sentry/integrable.rb +2 -0
  20. data/lib/sentry/interface.rb +2 -0
  21. data/lib/sentry/interfaces/exception.rb +2 -0
  22. data/lib/sentry/interfaces/single_exception.rb +31 -0
  23. data/lib/sentry/interfaces/stacktrace.rb +10 -0
  24. data/lib/sentry/interfaces/stacktrace_builder.rb +2 -0
  25. data/lib/sentry/interfaces/threads.rb +2 -0
  26. data/lib/sentry/linecache.rb +2 -0
  27. data/lib/sentry/net/http.rb +2 -0
  28. data/lib/sentry/rack/capture_exceptions.rb +2 -0
  29. data/lib/sentry/rack.rb +2 -0
  30. data/lib/sentry/rake.rb +4 -4
  31. data/lib/sentry/release_detector.rb +38 -0
  32. data/lib/sentry/scope.rb +8 -4
  33. data/lib/sentry/span.rb +1 -0
  34. data/lib/sentry/transaction.rb +6 -1
  35. data/lib/sentry/transport/configuration.rb +2 -0
  36. data/lib/sentry/transport/dummy_transport.rb +2 -0
  37. data/lib/sentry/transport/http_transport.rb +6 -4
  38. data/lib/sentry/transport.rb +77 -19
  39. data/lib/sentry/utils/argument_checking_helper.rb +2 -0
  40. data/lib/sentry/utils/custom_inspection.rb +14 -0
  41. data/lib/sentry/utils/exception_cause_chain.rb +10 -10
  42. data/lib/sentry/utils/logging_helper.rb +2 -0
  43. data/lib/sentry/utils/real_ip.rb +2 -0
  44. data/lib/sentry/utils/request_id.rb +2 -0
  45. data/lib/sentry/version.rb +3 -1
  46. data/lib/sentry-ruby.rb +26 -0
  47. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b44d17f4c1d10102a398692daf891a9f0d506e9ad370ca7f942a4bca470ada00
4
- data.tar.gz: 0a365e84e9f0881331492d37ad05869c70e514afd8932e26a2af919b2eb0f98b
3
+ metadata.gz: c0d62d3133d461116b39ce7e822a3cc523c378417eafade8b3a904f76bce5289
4
+ data.tar.gz: 4ae280d6e7d88107e7f34f0794d266182d1c801407373023293c38db038aae50
5
5
  SHA512:
6
- metadata.gz: 0c2a2e18a8f3d03f805b8c578c2240e02baa0e70b28b2929c14280bf34f958bb9f23e7a68a9c82d3a5d36490f42a796e22baa6316e5423e619229580ee47b227
7
- data.tar.gz: 002762a3e85563716cc056fc86f0571d1198f6b1df219eec3782b57510b7e566f0be08a694d1f24b74f0146b2c01029922badd1c94f18a32bda0d830c672a92b
6
+ metadata.gz: 8027f22238a8012bb1aca95d528cb479e287a5b6b17269c9789056d6959f33cab558fac3166147b82b2bda7ee6ad87f4d484ad83634d41804bf198ea7fddd7b2
7
+ data.tar.gz: ea6681465bf4f538139b0ba6057ea34dec7cb848e77be48ca65c5d7ee5080c492f324dda7903f40d1db275d4bea4f3887048ebf144d7748d02be35314e5fb977
data/Gemfile CHANGED
@@ -10,10 +10,12 @@ gem "rspec", "~> 3.0"
10
10
  gem "rspec-retry"
11
11
  gem "webmock"
12
12
  gem "timecop"
13
- gem "codecov", "0.2.12"
13
+ gem 'simplecov'
14
+ gem "simplecov-cobertura", "~> 1.4"
15
+ gem "rexml"
14
16
 
15
17
  gem "object_tracer"
16
- gem "debug", github: "ruby/debug" if RUBY_VERSION.to_f >= 2.6
18
+ gem "debug", github: "ruby/debug", platform: :ruby if RUBY_VERSION.to_f >= 2.6
17
19
  gem "pry"
18
20
 
19
21
  gem "benchmark-ips"
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2020 getsentry
3
+ Copyright (c) 2020 Sentry
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -10,13 +10,13 @@ _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
- | current version | build | coverage | downloads | semver stability |
14
- | --- | ----- | -------- | --------- | ---------------- |
15
- | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-ruby%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![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://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-rails%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![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://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-sidekiq%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![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://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-delayed_job%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![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) |
19
- | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-resque%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![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-resque.svg)](https://rubygems.org/gems/sentry-resque/) | [![SemVer stability](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-resque&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-resque&package-manager=bundler&version-scheme=semver) |
13
+ | current version | build | coverage | downloads |
14
+ | --- | ----- | -------- | --------- |
15
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-ruby%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![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/) |
16
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-rails%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![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/) |
17
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-sidekiq%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![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/) |
18
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-delayed_job%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![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/) |
19
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-resque%20Test/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![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-resque.svg)](https://rubygems.org/gems/sentry-resque/) |
20
20
 
21
21
 
22
22
 
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "sentry/ruby"
4
+ require "sentry-ruby"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,9 @@ require "sentry/ruby"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
+ # Sentry.init do |config|
14
+ # config.dsn = 'https://2fb45f003d054a7ea47feb45898f7649@o447951.ingest.sentry.io/5434472'
15
+ # end
16
+
13
17
  require "irb"
14
18
  IRB.start(__FILE__)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "concurrent/executor/thread_pool_executor"
2
4
  require "concurrent/executor/immediate_executor"
3
5
  require "concurrent/configuration"
@@ -7,11 +9,15 @@ module Sentry
7
9
  include LoggingHelper
8
10
 
9
11
  attr_reader :max_queue, :number_of_threads, :logger
12
+ attr_accessor :shutdown_timeout
10
13
 
11
14
  def initialize(configuration)
12
15
  @max_queue = 30
16
+ @shutdown_timeout = 1
13
17
  @number_of_threads = configuration.background_worker_threads
14
18
  @logger = configuration.logger
19
+ @debug = configuration.debug
20
+ @shutdown_callback = nil
15
21
 
16
22
  @executor =
17
23
  if configuration.async
@@ -23,19 +29,41 @@ module Sentry
23
29
  else
24
30
  log_debug("initialized a background worker with #{@number_of_threads} threads")
25
31
 
26
- Concurrent::ThreadPoolExecutor.new(
32
+ executor = Concurrent::ThreadPoolExecutor.new(
27
33
  min_threads: 0,
28
34
  max_threads: @number_of_threads,
29
35
  max_queue: @max_queue,
30
36
  fallback_policy: :discard
31
37
  )
38
+
39
+ @shutdown_callback = proc do
40
+ executor.shutdown
41
+ executor.wait_for_termination(@shutdown_timeout)
42
+ end
43
+
44
+ executor
32
45
  end
33
46
  end
34
47
 
48
+ # if you want to monkey-patch this method, please override `_perform` instead
35
49
  def perform(&block)
36
50
  @executor.post do
37
- block.call
51
+ begin
52
+ _perform(&block)
53
+ rescue Exception => e
54
+ log_error("exception happened in background worker", e, debug: @debug)
55
+ end
38
56
  end
39
57
  end
58
+
59
+ def shutdown
60
+ @shutdown_callback&.call
61
+ end
62
+
63
+ private
64
+
65
+ def _perform(&block)
66
+ block.call
67
+ end
40
68
  end
41
69
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
 
3
5
  module Sentry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class Breadcrumb
3
5
  DATA_SERIALIZATION_ERROR_MESSAGE = "[data were removed due to serialization issues]"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sentry/breadcrumb"
2
4
 
3
5
  module Sentry
data/lib/sentry/client.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sentry/transport"
2
4
 
3
5
  module Sentry
@@ -26,17 +28,25 @@ module Sentry
26
28
  def capture_event(event, scope, hint = {})
27
29
  return unless configuration.sending_allowed?
28
30
 
31
+ unless event.is_a?(TransactionEvent) || configuration.sample_allowed?
32
+ transport.record_lost_event(:sample_rate, 'event')
33
+ return
34
+ end
35
+
36
+ event_type = event.is_a?(Event) ? event.type : event["type"]
29
37
  event = scope.apply_to_event(event, hint)
30
38
 
31
39
  if event.nil?
32
40
  log_info("Discarded event because one of the event processors returned nil")
41
+ transport.record_lost_event(:event_processor, event_type)
33
42
  return
34
43
  end
35
44
 
36
45
  if async_block = configuration.async
37
46
  dispatch_async_event(async_block, event, hint)
38
47
  elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
39
- dispatch_background_event(event, hint)
48
+ queued = dispatch_background_event(event, hint)
49
+ transport.record_lost_event(:queue_overflow, event_type) unless queued
40
50
  else
41
51
  send_event(event, hint)
42
52
  end
@@ -84,6 +94,7 @@ module Sentry
84
94
 
85
95
  if event.nil?
86
96
  log_info("Discarded event because before_send returned nil")
97
+ transport.record_lost_event(:before_send, 'event')
87
98
  return
88
99
  end
89
100
  end
@@ -97,6 +108,7 @@ module Sentry
97
108
 
98
109
  event_info = Event.get_log_message(event.to_hash)
99
110
  log_info("Unreported #{loggable_event_type}: #{event_info}")
111
+ transport.record_lost_event(:network_error, event_type)
100
112
  raise
101
113
  end
102
114
 
@@ -1,13 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "concurrent/utility/processor_counter"
2
4
 
3
5
  require "sentry/utils/exception_cause_chain"
6
+ require 'sentry/utils/custom_inspection'
4
7
  require "sentry/dsn"
8
+ require "sentry/release_detector"
5
9
  require "sentry/transport/configuration"
6
10
  require "sentry/linecache"
7
11
  require "sentry/interfaces/stacktrace_builder"
8
12
 
9
13
  module Sentry
10
14
  class Configuration
15
+ include CustomInspection
11
16
  include LoggingHelper
12
17
  # Directories to be recognized as part of your app. e.g. if you
13
18
  # have an `engines` dir at the root of your project, you may want
@@ -63,6 +68,9 @@ module Sentry
63
68
  # - :active_support_logger
64
69
  attr_reader :breadcrumbs_logger
65
70
 
71
+ # Whether to capture local variables from the raised exception's frame. Default is false.
72
+ attr_accessor :capture_exception_frame_locals
73
+
66
74
  # Max number of breadcrumbs a breadcrumb buffer can hold
67
75
  attr_accessor :max_breadcrumbs
68
76
 
@@ -104,7 +112,7 @@ module Sentry
104
112
 
105
113
  # Project directory root for in_app detection. Could be Rails root, etc.
106
114
  # Set automatically for Rails.
107
- attr_reader :project_root
115
+ attr_accessor :project_root
108
116
 
109
117
  # Insert sentry-trace to outgoing requests' headers
110
118
  attr_accessor :propagate_traces
@@ -154,6 +162,10 @@ module Sentry
154
162
  # ```
155
163
  attr_accessor :traces_sampler
156
164
 
165
+ # Send diagnostic client reports about dropped events, true by default
166
+ # tries to attach to an existing envelope max once every 30s
167
+ attr_accessor :send_client_reports
168
+
157
169
  # these are not config options
158
170
  attr_reader :errors, :gem_specs
159
171
 
@@ -177,17 +189,21 @@ module Sentry
177
189
 
178
190
  LOG_PREFIX = "** [Sentry] ".freeze
179
191
  MODULE_SEPARATOR = "::".freeze
192
+ SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
180
193
 
181
194
  # Post initialization callbacks are called at the end of initialization process
182
195
  # allowing extending the configuration of sentry-ruby by multiple extensions
183
196
  @@post_initialization_callbacks = []
184
197
 
185
198
  def initialize
199
+ self.app_dirs_pattern = nil
186
200
  self.debug = false
187
201
  self.background_worker_threads = Concurrent.processor_count
202
+ self.backtrace_cleanup_callback = nil
188
203
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
189
204
  self.breadcrumbs_logger = []
190
205
  self.context_lines = 3
206
+ self.capture_exception_frame_locals = false
191
207
  self.environment = environment_from_env
192
208
  self.enabled_environments = []
193
209
  self.exclude_loggers = []
@@ -202,12 +218,15 @@ module Sentry
202
218
  self.send_modules = true
203
219
  self.send_default_pii = false
204
220
  self.skip_rake_integration = false
221
+ self.send_client_reports = true
205
222
  self.trusted_proxies = []
206
223
  self.dsn = ENV['SENTRY_DSN']
207
224
  self.server_name = server_name_from_env
208
225
 
209
- self.before_send = false
226
+ self.before_send = nil
210
227
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
228
+ self.traces_sample_rate = nil
229
+ self.traces_sampler = nil
211
230
 
212
231
  @transport = Transport::Configuration.new
213
232
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
@@ -216,18 +235,13 @@ module Sentry
216
235
  end
217
236
 
218
237
  def dsn=(value)
219
- return if value.nil? || value.empty?
220
-
221
- @dsn = DSN.new(value)
238
+ @dsn = init_dsn(value)
222
239
  end
223
240
 
224
241
  alias server= dsn=
225
242
 
226
-
227
243
  def async=(value)
228
- if value && !value.respond_to?(:call)
229
- raise(ArgumentError, "async must be callable")
230
- end
244
+ check_callable!("async", value)
231
245
 
232
246
  @async = value
233
247
  end
@@ -246,17 +260,13 @@ module Sentry
246
260
  end
247
261
 
248
262
  def before_send=(value)
249
- unless value == false || value.respond_to?(:call)
250
- raise ArgumentError, "before_send must be callable (or false to disable)"
251
- end
263
+ check_callable!("before_send", value)
252
264
 
253
265
  @before_send = value
254
266
  end
255
267
 
256
268
  def before_breadcrumb=(value)
257
- unless value.nil? || value.respond_to?(:call)
258
- raise ArgumentError, "before_breadcrumb must be callable (or nil to disable)"
259
- end
269
+ check_callable!("before_breadcrumb", value)
260
270
 
261
271
  @before_breadcrumb = value
262
272
  end
@@ -268,9 +278,13 @@ module Sentry
268
278
  def sending_allowed?
269
279
  @errors = []
270
280
 
271
- valid? &&
272
- capture_in_environment? &&
273
- sample_allowed?
281
+ valid? && capture_in_environment?
282
+ end
283
+
284
+ def sample_allowed?
285
+ return true if sample_rate == 1.0
286
+
287
+ Random.rand < sample_rate
274
288
  end
275
289
 
276
290
  def error_messages
@@ -278,10 +292,6 @@ module Sentry
278
292
  @errors.join(", ")
279
293
  end
280
294
 
281
- def project_root=(root_dir)
282
- @project_root = root_dir
283
- end
284
-
285
295
  def exception_class_allowed?(exc)
286
296
  if exc.is_a?(Sentry::Error)
287
297
  # Try to prevent error reporting loops
@@ -316,10 +326,11 @@ module Sentry
316
326
  def detect_release
317
327
  return unless sending_allowed?
318
328
 
319
- self.release ||= detect_release_from_env ||
320
- detect_release_from_git ||
321
- detect_release_from_capistrano ||
322
- detect_release_from_heroku
329
+ self.release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
330
+
331
+ if running_on_heroku? && release.nil?
332
+ log_warn(HEROKU_DYNO_METADATA_MESSAGE)
333
+ end
323
334
  rescue => e
324
335
  log_error("Error detecting release", e, debug: debug)
325
336
  end
@@ -335,6 +346,18 @@ module Sentry
335
346
 
336
347
  private
337
348
 
349
+ def check_callable!(name, value)
350
+ unless value == nil || value.respond_to?(:call)
351
+ raise ArgumentError, "#{name} must be callable (or nil to disable)"
352
+ end
353
+ end
354
+
355
+ def init_dsn(dsn_string)
356
+ return if dsn_string.nil? || dsn_string.empty?
357
+
358
+ DSN.new(dsn_string)
359
+ end
360
+
338
361
  def excluded_exception?(incoming_exception)
339
362
  excluded_exception_classes.any? do |excluded_exception|
340
363
  matches_exception?(excluded_exception, incoming_exception)
@@ -364,37 +387,6 @@ module Sentry
364
387
  nil
365
388
  end
366
389
 
367
- def detect_release_from_heroku
368
- return unless running_on_heroku?
369
- return if ENV['CI']
370
- log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
371
-
372
- ENV['HEROKU_SLUG_COMMIT']
373
- end
374
-
375
- def running_on_heroku?
376
- File.directory?("/etc/heroku")
377
- end
378
-
379
- def detect_release_from_capistrano
380
- revision_file = File.join(project_root, 'REVISION')
381
- revision_log = File.join(project_root, '..', 'revisions.log')
382
-
383
- if File.exist?(revision_file)
384
- File.read(revision_file).strip
385
- elsif File.exist?(revision_log)
386
- File.open(revision_log).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
387
- end
388
- end
389
-
390
- def detect_release_from_git
391
- Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
392
- end
393
-
394
- def detect_release_from_env
395
- ENV['SENTRY_RELEASE']
396
- end
397
-
398
390
  def capture_in_environment?
399
391
  return true if enabled_in_current_env?
400
392
 
@@ -411,36 +403,24 @@ module Sentry
411
403
  end
412
404
  end
413
405
 
414
- def sample_allowed?
415
- return true if sample_rate == 1.0
416
-
417
- if Random.rand >= sample_rate
418
- @errors << "Excluded by random sample"
419
- false
420
- else
421
- true
422
- end
423
- end
424
-
425
- # Try to resolve the hostname to an FQDN, but fall back to whatever
426
- # the load name is.
427
- def resolve_hostname
428
- Socket.gethostname ||
429
- Socket.gethostbyname(hostname).first rescue server_name
430
- end
431
-
432
406
  def environment_from_env
433
- ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default'
407
+ ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
434
408
  end
435
409
 
436
410
  def server_name_from_env
437
411
  if running_on_heroku?
438
412
  ENV['DYNO']
439
413
  else
440
- resolve_hostname
414
+ # Try to resolve the hostname to an FQDN, but fall back to whatever
415
+ # the load name is.
416
+ Socket.gethostname || Socket.gethostbyname(hostname).first rescue server_name
441
417
  end
442
418
  end
443
419
 
420
+ def running_on_heroku?
421
+ File.directory?("/etc/heroku") && !ENV["CI"]
422
+ end
423
+
444
424
  def run_post_initialization_callbacks
445
425
  self.class.post_initialization_callbacks.each do |hook|
446
426
  instance_eval(&hook)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  return if Object.method_defined?(:deep_dup)
2
4
 
3
5
  require 'sentry/core_ext/object/duplicable'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  return if Object.method_defined?(:duplicable?)
3
4
 
4
5
  #########################################
data/lib/sentry/dsn.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "uri"
2
4
 
3
5
  module Sentry
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class Envelope
5
+ def initialize(headers)
6
+ @headers = headers
7
+ @items = []
8
+ end
9
+
10
+ def add_item(headers, payload)
11
+ @items << [headers, payload]
12
+ end
13
+
14
+ def to_s
15
+ payload = @items.map do |item_headers, item_payload|
16
+ <<~ENVELOPE
17
+ #{JSON.generate(item_headers)}
18
+ #{JSON.generate(item_payload)}
19
+ ENVELOPE
20
+ end.join("\n")
21
+
22
+ "#{JSON.generate(@headers)}\n#{payload}"
23
+ end
24
+ end
25
+ end
data/lib/sentry/event.rb CHANGED
@@ -6,6 +6,7 @@ require 'sentry/interface'
6
6
  require 'sentry/backtrace'
7
7
  require 'sentry/utils/real_ip'
8
8
  require 'sentry/utils/request_id'
9
+ require 'sentry/utils/custom_inspection'
9
10
 
10
11
  module Sentry
11
12
  class Event
@@ -21,6 +22,10 @@ module Sentry
21
22
 
22
23
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
23
24
 
25
+ SKIP_INSPECTION_ATTRIBUTES = [:@configuration, :@modules, :@backtrace]
26
+
27
+ include CustomInspection
28
+
24
29
  attr_writer(*WRITER_ATTRIBUTES)
25
30
  attr_reader(*SERIALIZEABLE_ATTRIBUTES)
26
31
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class Error < StandardError
3
5
  end
data/lib/sentry/hub.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sentry/scope"
2
4
  require "sentry/client"
3
5
 
@@ -90,10 +92,10 @@ module Sentry
90
92
  end
91
93
 
92
94
  def capture_exception(exception, **options, &block)
93
- return unless current_client
94
-
95
95
  check_argument_type!(exception, ::Exception)
96
96
 
97
+ return unless current_client
98
+
97
99
  options[:hint] ||= {}
98
100
  options[:hint][:exception] = exception
99
101
  event = current_client.event_from_exception(exception, options[:hint])
@@ -104,6 +106,8 @@ module Sentry
104
106
  end
105
107
 
106
108
  def capture_message(message, **options, &block)
109
+ check_argument_type!(message, ::String)
110
+
107
111
  return unless current_client
108
112
 
109
113
  options[:hint] ||= {}
@@ -114,10 +118,10 @@ module Sentry
114
118
  end
115
119
 
116
120
  def capture_event(event, **options, &block)
117
- return unless current_client
118
-
119
121
  check_argument_type!(event, Sentry::Event)
120
122
 
123
+ return unless current_client
124
+
121
125
  hint = options.delete(:hint) || {}
122
126
  scope = current_scope.dup
123
127
 
@@ -131,6 +135,11 @@ module Sentry
131
135
 
132
136
  event = current_client.capture_event(event, scope, hint)
133
137
 
138
+
139
+ if event && configuration.debug
140
+ configuration.log_debug(event.to_json_compatible)
141
+ end
142
+
134
143
  @last_event_id = event&.event_id
135
144
  event
136
145
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Integrable
3
5
  def register_integration(name:, version:)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class Interface
3
5
  def self.inherited(klass)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class ExceptionInterface < Interface
3
5
  def initialize(values:)
@@ -1,5 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/utils/exception_cause_chain"
4
+
1
5
  module Sentry
2
6
  class SingleExceptionInterface < Interface
7
+ include CustomInspection
8
+
9
+ SKIP_INSPECTION_ATTRIBUTES = [:@stacktrace]
10
+ PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]".freeze
11
+ OMISSION_MARK = "...".freeze
12
+ MAX_LOCAL_BYTES = 1024
13
+
3
14
  attr_reader :type, :value, :module, :thread_id, :stacktrace
4
15
 
5
16
  def initialize(exception:, stacktrace: nil)
@@ -20,6 +31,26 @@ module Sentry
20
31
  # also see `StacktraceBuilder.build`.
21
32
  def self.build_with_stacktrace(exception:, stacktrace_builder:)
22
33
  stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
34
+
35
+ if locals = exception.instance_variable_get(:@sentry_locals)
36
+ locals.each do |k, v|
37
+ locals[k] =
38
+ begin
39
+ v = v.inspect unless v.is_a?(String)
40
+
41
+ if v.length >= MAX_LOCAL_BYTES
42
+ v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
43
+ end
44
+
45
+ v
46
+ rescue StandardError
47
+ PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
48
+ end
49
+ end
50
+
51
+ stacktrace.frames.last.vars = locals
52
+ end
53
+
23
54
  new(exception: exception, stacktrace: stacktrace)
24
55
  end
25
56
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class StacktraceInterface
3
5
  attr_reader :frames
@@ -10,6 +12,10 @@ module Sentry
10
12
  { frames: @frames.map(&:to_hash) }
11
13
  end
12
14
 
15
+ def inspect
16
+ @frames.map(&:to_s)
17
+ end
18
+
13
19
  private
14
20
 
15
21
  # Not actually an interface, but I want to use the same style
@@ -28,6 +34,10 @@ module Sentry
28
34
  @filename = compute_filename
29
35
  end
30
36
 
37
+ def to_s
38
+ "#{@filename}:#{@lineno}"
39
+ end
40
+
31
41
  def compute_filename
32
42
  return if abs_path.nil?
33
43
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class StacktraceBuilder
3
5
  attr_reader :project_root, :app_dirs_pattern, :linecache, :context_lines, :backtrace_cleanup_callback
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class ThreadsInterface
3
5
  def initialize(crashed: false, stacktrace: nil)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class LineCache
3
5
  def initialize
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "net/http"
2
4
 
3
5
  module Sentry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Rack
3
5
  class CaptureExceptions
data/lib/sentry/rack.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
 
3
5
  require 'sentry/rack/capture_exceptions'
data/lib/sentry/rake.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rake"
2
4
  require "rake/task"
3
5
 
@@ -5,7 +7,7 @@ module Sentry
5
7
  module Rake
6
8
  module Application
7
9
  def display_error_message(ex)
8
- Sentry.capture_exception(ex, hint: { background: false }) do |scope|
10
+ Sentry.capture_exception(ex) do |scope|
9
11
  task_name = top_level_tasks.join(' ')
10
12
  scope.set_transaction_name(task_name)
11
13
  scope.set_tag("rake_task", task_name)
@@ -19,9 +21,7 @@ module Sentry
19
21
  def execute(args=nil)
20
22
  return super unless Sentry.initialized? && Sentry.get_current_hub
21
23
 
22
- Sentry.get_current_hub.with_background_worker_disabled do
23
- super
24
- end
24
+ super
25
25
  end
26
26
  end
27
27
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class ReleaseDetector
5
+ class << self
6
+ def detect_release(project_root:, running_on_heroku:)
7
+ detect_release_from_env ||
8
+ detect_release_from_git ||
9
+ detect_release_from_capistrano(project_root) ||
10
+ detect_release_from_heroku(running_on_heroku)
11
+ end
12
+
13
+ def detect_release_from_heroku(running_on_heroku)
14
+ return unless running_on_heroku
15
+ ENV['HEROKU_SLUG_COMMIT']
16
+ end
17
+
18
+ def detect_release_from_capistrano(project_root)
19
+ revision_file = File.join(project_root, 'REVISION')
20
+ revision_log = File.join(project_root, '..', 'revisions.log')
21
+
22
+ if File.exist?(revision_file)
23
+ File.read(revision_file).strip
24
+ elsif File.exist?(revision_log)
25
+ File.open(revision_log).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
26
+ end
27
+ end
28
+
29
+ def detect_release_from_git
30
+ Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
31
+ end
32
+
33
+ def detect_release_from_env
34
+ ENV['SENTRY_RELEASE']
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/sentry/scope.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sentry/breadcrumb_buffer"
2
4
  require "etc"
3
5
 
@@ -112,7 +114,7 @@ module Sentry
112
114
  end
113
115
 
114
116
  def set_extra(key, value)
115
- @extra.merge!(key => value)
117
+ set_extras(key => value)
116
118
  end
117
119
 
118
120
  def set_tags(tags_hash)
@@ -121,17 +123,19 @@ module Sentry
121
123
  end
122
124
 
123
125
  def set_tag(key, value)
124
- @tags.merge!(key => value)
126
+ set_tags(key => value)
125
127
  end
126
128
 
127
129
  def set_contexts(contexts_hash)
128
130
  check_argument_type!(contexts_hash, Hash)
129
- @contexts.merge!(contexts_hash)
131
+ @contexts.merge!(contexts_hash) do |key, old, new|
132
+ new.merge(old)
133
+ end
130
134
  end
131
135
 
132
136
  def set_context(key, value)
133
137
  check_argument_type!(value, Hash)
134
- @contexts.merge!(key => value)
138
+ set_contexts(key => value)
135
139
  end
136
140
 
137
141
  def set_level(level)
data/lib/sentry/span.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "securerandom"
3
4
 
4
5
  module Sentry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class Transaction < Span
3
5
  SENTRY_TRACE_REGEXP = Regexp.new(
@@ -129,7 +131,10 @@ module Sentry
129
131
  @name = UNLABELD_NAME
130
132
  end
131
133
 
132
- return unless @sampled || @parent_sampled
134
+ unless @sampled || @parent_sampled
135
+ hub.current_client.transport.record_lost_event(:sample_rate, 'transaction')
136
+ return
137
+ end
133
138
 
134
139
  event = hub.current_client.event_from_transaction(self)
135
140
  hub.capture_event(event)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class Transport
3
5
  class Configuration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class DummyTransport < Transport
3
5
  attr_accessor :events
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'zlib'
3
5
 
@@ -139,10 +141,10 @@ module Sentry
139
141
  end
140
142
 
141
143
  def ssl_configuration
142
- (@transport_configuration.ssl || {}).merge(
143
- :verify => @transport_configuration.ssl_verification,
144
- :ca_file => @transport_configuration.ssl_ca_file
145
- )
144
+ {
145
+ verify: @transport_configuration.ssl_verification,
146
+ ca_file: @transport_configuration.ssl_ca_file
147
+ }.merge(@transport_configuration.ssl || {})
146
148
  end
147
149
  end
148
150
  end
@@ -1,22 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "base64"
5
+ require "sentry/envelope"
3
6
 
4
7
  module Sentry
5
8
  class Transport
6
9
  PROTOCOL_VERSION = '7'
7
10
  USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
11
+ CLIENT_REPORT_INTERVAL = 30
12
+
13
+ # https://develop.sentry.dev/sdk/client-reports/#envelope-item-payload
14
+ CLIENT_REPORT_REASONS = [
15
+ :ratelimit_backoff,
16
+ :queue_overflow,
17
+ :cache_overflow, # NA
18
+ :network_error,
19
+ :sample_rate,
20
+ :before_send,
21
+ :event_processor
22
+ ]
8
23
 
9
24
  include LoggingHelper
10
25
 
11
- attr_accessor :configuration
12
- attr_reader :logger, :rate_limits
26
+ attr_reader :logger, :rate_limits, :discarded_events, :last_client_report_sent
13
27
 
14
28
  def initialize(configuration)
15
- @configuration = configuration
16
29
  @logger = configuration.logger
17
30
  @transport_configuration = configuration.transport
18
31
  @dsn = configuration.dsn
19
32
  @rate_limits = {}
33
+ @send_client_reports = configuration.send_client_reports
34
+
35
+ if @send_client_reports
36
+ @discarded_events = Hash.new(0)
37
+ @last_client_report_sent = Time.now
38
+ end
20
39
  end
21
40
 
22
41
  def send_data(data, options = {})
@@ -27,14 +46,9 @@ module Sentry
27
46
  event_hash = event.to_hash
28
47
  item_type = get_item_type(event_hash)
29
48
 
30
- unless configuration.sending_allowed?
31
- log_debug("Envelope [#{item_type}] not sent: #{configuration.error_messages}")
32
-
33
- return
34
- end
35
-
36
49
  if is_rate_limited?(item_type)
37
50
  log_info("Envelope [#{item_type}] not sent: rate limiting")
51
+ record_lost_event(:ratelimit_backoff, item_type)
38
52
 
39
53
  return
40
54
  end
@@ -91,20 +105,38 @@ module Sentry
91
105
 
92
106
  def encode(event)
93
107
  # Convert to hash
94
- event_hash = event.to_hash
108
+ event_payload = event.to_hash
109
+ event_id = event_payload[:event_id] || event_payload["event_id"]
110
+ item_type = get_item_type(event_payload)
111
+
112
+ envelope = Envelope.new(
113
+ {
114
+ event_id: event_id,
115
+ dsn: @dsn.to_s,
116
+ sdk: Sentry.sdk_meta,
117
+ sent_at: Sentry.utc_now.iso8601
118
+ }
119
+ )
120
+
121
+ envelope.add_item(
122
+ { type: item_type, content_type: 'application/json' },
123
+ event_payload
124
+ )
125
+
126
+ client_report_headers, client_report_payload = fetch_pending_client_report
127
+ envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
95
128
 
96
- event_id = event_hash[:event_id] || event_hash["event_id"]
97
- item_type = get_item_type(event_hash)
129
+ log_info("Sending envelope [#{item_type}] #{event_id} to Sentry")
98
130
 
99
- envelope = <<~ENVELOPE
100
- {"event_id":"#{event_id}","dsn":"#{configuration.dsn.to_s}","sdk":#{Sentry.sdk_meta.to_json},"sent_at":"#{Sentry.utc_now.iso8601}"}
101
- {"type":"#{item_type}","content_type":"application/json"}
102
- #{JSON.generate(event_hash)}
103
- ENVELOPE
131
+ envelope.to_s
132
+ end
104
133
 
105
- log_info("Sending envelope [#{item_type}] #{event_id} to Sentry")
134
+ def record_lost_event(reason, item_type)
135
+ return unless @send_client_reports
136
+ return unless CLIENT_REPORT_REASONS.include?(reason)
106
137
 
107
- envelope
138
+ item_type ||= 'event'
139
+ @discarded_events[[reason, item_type]] += 1
108
140
  end
109
141
 
110
142
  private
@@ -112,6 +144,32 @@ module Sentry
112
144
  def get_item_type(event_hash)
113
145
  event_hash[:type] || event_hash["type"] || "event"
114
146
  end
147
+
148
+ def fetch_pending_client_report
149
+ return nil unless @send_client_reports
150
+ return nil if @last_client_report_sent > Time.now - CLIENT_REPORT_INTERVAL
151
+ return nil if @discarded_events.empty?
152
+
153
+ discarded_events_hash = @discarded_events.map do |key, val|
154
+ reason, type = key
155
+
156
+ # 'event' has to be mapped to 'error'
157
+ category = type == 'transaction' ? 'transaction' : 'error'
158
+
159
+ { reason: reason, category: category, quantity: val }
160
+ end
161
+
162
+ item_header = { type: 'client_report' }
163
+ item_payload = {
164
+ timestamp: Sentry.utc_now.iso8601,
165
+ discarded_events: discarded_events_hash
166
+ }
167
+
168
+ @discarded_events = Hash.new(0)
169
+ @last_client_report_sent = Time.now
170
+
171
+ [item_header, item_payload]
172
+ end
115
173
  end
116
174
  end
117
175
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module ArgumentCheckingHelper
3
5
  private
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module CustomInspection
5
+ def inspect
6
+ attr_strings = (instance_variables - self.class::SKIP_INSPECTION_ATTRIBUTES).each_with_object([]) do |attr, result|
7
+ value = instance_variable_get(attr)
8
+ result << "#{attr}=#{value.inspect}" if value
9
+ end
10
+
11
+ "#<#{self.class.name} #{attr_strings.join(", ")}>"
12
+ end
13
+ end
14
+ end
@@ -1,19 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Utils
3
5
  module ExceptionCauseChain
4
6
  def self.exception_to_array(exception)
5
- if exception.respond_to?(:cause) && exception.cause
6
- exceptions = [exception]
7
- while exception.cause
8
- exception = exception.cause
9
- break if exceptions.any? { |e| e.object_id == exception.object_id }
7
+ exceptions = [exception]
8
+
9
+ while exception.cause
10
+ exception = exception.cause
11
+ break if exceptions.any? { |e| e.object_id == exception.object_id }
10
12
 
11
- exceptions << exception
12
- end
13
- exceptions
14
- else
15
- [exception]
13
+ exceptions << exception
16
14
  end
15
+
16
+ exceptions
17
17
  end
18
18
  end
19
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module LoggingHelper
3
5
  def log_error(message, exception, debug: false)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ipaddr'
2
4
 
3
5
  # Based on ActionDispatch::RemoteIp. All security-related precautions from that
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Utils
3
5
  module RequestId
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
- VERSION = "4.7.1"
4
+ VERSION = "4.8.1"
3
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "English"
2
4
  require "forwardable"
3
5
  require "time"
@@ -37,6 +39,22 @@ module Sentry
37
39
  THREAD_LOCAL = :sentry_hub
38
40
 
39
41
  class << self
42
+ def exception_locals_tp
43
+ @exception_locals_tp ||= TracePoint.new(:raise) do |tp|
44
+ exception = tp.raised_exception
45
+
46
+ # don't collect locals again if the exception is re-raised
47
+ next if exception.instance_variable_get(:@sentry_locals)
48
+ next unless tp.binding
49
+
50
+ locals = tp.binding.local_variables.each_with_object({}) do |local, result|
51
+ result[local] = tp.binding.local_variable_get(local)
52
+ end
53
+
54
+ exception.instance_variable_set(:@sentry_locals, locals)
55
+ end
56
+ end
57
+
40
58
  attr_accessor :background_worker
41
59
 
42
60
  ##### Patch Registration #####
@@ -88,6 +106,14 @@ module Sentry
88
106
  Thread.current.thread_variable_set(THREAD_LOCAL, hub)
89
107
  @main_hub = hub
90
108
  @background_worker = Sentry::BackgroundWorker.new(config)
109
+
110
+ if config.capture_exception_frame_locals
111
+ exception_locals_tp.enable
112
+ end
113
+
114
+ at_exit do
115
+ @background_worker.shutdown
116
+ end
91
117
  end
92
118
 
93
119
  # Returns an uri for security policy reporting that's generated from the given DSN
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.7.1
4
+ version: 4.8.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-09-01 00:00:00.000000000 Z
11
+ date: 2021-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -68,6 +68,7 @@ files:
68
68
  - lib/sentry/core_ext/object/deep_dup.rb
69
69
  - lib/sentry/core_ext/object/duplicable.rb
70
70
  - lib/sentry/dsn.rb
71
+ - lib/sentry/envelope.rb
71
72
  - lib/sentry/event.rb
72
73
  - lib/sentry/exceptions.rb
73
74
  - lib/sentry/hub.rb
@@ -85,6 +86,7 @@ files:
85
86
  - lib/sentry/rack.rb
86
87
  - lib/sentry/rack/capture_exceptions.rb
87
88
  - lib/sentry/rake.rb
89
+ - lib/sentry/release_detector.rb
88
90
  - lib/sentry/scope.rb
89
91
  - lib/sentry/span.rb
90
92
  - lib/sentry/transaction.rb
@@ -94,6 +96,7 @@ files:
94
96
  - lib/sentry/transport/dummy_transport.rb
95
97
  - lib/sentry/transport/http_transport.rb
96
98
  - lib/sentry/utils/argument_checking_helper.rb
99
+ - lib/sentry/utils/custom_inspection.rb
97
100
  - lib/sentry/utils/exception_cause_chain.rb
98
101
  - lib/sentry/utils/logging_helper.rb
99
102
  - lib/sentry/utils/real_ip.rb