sentry-ruby-core 4.7.1 → 4.8.1

Sign up to get free protection for your applications and to get access to all the features.
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