sentry-ruby-core 4.7.3 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9ceb36eb4db5018e7b309949fa8ae468c07f0687c0227fd9f8b20e68e216776
4
- data.tar.gz: e214379b67e80cb89c7f70a36739bd2f067af6fa9227acf125349a2aef2c8ea4
3
+ metadata.gz: f675111daf93ba05db7de62dcfa1dd9268f6ba009265de08e0e4f118ffea0c62
4
+ data.tar.gz: 2ece2ccaf14be6cc5fa762b77a57cd63dcc3fe52a03987c8c4136a43ec62bbd1
5
5
  SHA512:
6
- metadata.gz: 2bfdb4d169ba1d9c896273401df8891b51f5fc016609154a1ec02bf064bf956bc4ce69ab85cafc4c82001ccaf475d86bc18d4064f1cdd8563ae03761a0245604
7
- data.tar.gz: 53ce1e1f6d4957a1c3ad327cc7e51d154da1da32c863ad21fd0ce3aca09273fa794c08b39ee7adfafd96e7afa30555b87a2f2013b345906a0218001d177d3bec
6
+ metadata.gz: d5d9405c41879a40ba0c0205fa08e38fa5b3ed243276181239be63f0baa0b739b37ac91ecb86abd76768c4c5a706f1f7b7e47d7eadd357f2116ec2ec9bd913d5
7
+ data.tar.gz: abb46869f2eba00f053096e5568829815b02520db31fe9c98bbfabea40b5dcbe6d438ca97287ca7f9b4fee9a4710b49dec73b9196a3650d3a811184a960a09a7
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/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__)
data/lib/sentry/client.rb CHANGED
@@ -26,17 +26,25 @@ module Sentry
26
26
  def capture_event(event, scope, hint = {})
27
27
  return unless configuration.sending_allowed?
28
28
 
29
+ unless event.is_a?(TransactionEvent) || configuration.sample_allowed?
30
+ transport.record_lost_event(:sample_rate, 'event')
31
+ return
32
+ end
33
+
34
+ event_type = event.is_a?(Event) ? event.type : event["type"]
29
35
  event = scope.apply_to_event(event, hint)
30
36
 
31
37
  if event.nil?
32
38
  log_info("Discarded event because one of the event processors returned nil")
39
+ transport.record_lost_event(:event_processor, event_type)
33
40
  return
34
41
  end
35
42
 
36
43
  if async_block = configuration.async
37
44
  dispatch_async_event(async_block, event, hint)
38
45
  elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
39
- dispatch_background_event(event, hint)
46
+ queued = dispatch_background_event(event, hint)
47
+ transport.record_lost_event(:queue_overflow, event_type) unless queued
40
48
  else
41
49
  send_event(event, hint)
42
50
  end
@@ -84,6 +92,7 @@ module Sentry
84
92
 
85
93
  if event.nil?
86
94
  log_info("Discarded event because before_send returned nil")
95
+ transport.record_lost_event(:before_send, 'event')
87
96
  return
88
97
  end
89
98
  end
@@ -97,6 +106,7 @@ module Sentry
97
106
 
98
107
  event_info = Event.get_log_message(event.to_hash)
99
108
  log_info("Unreported #{loggable_event_type}: #{event_info}")
109
+ transport.record_lost_event(:network_error, event_type)
100
110
  raise
101
111
  end
102
112
 
@@ -1,13 +1,16 @@
1
1
  require "concurrent/utility/processor_counter"
2
2
 
3
3
  require "sentry/utils/exception_cause_chain"
4
+ require 'sentry/utils/custom_inspection'
4
5
  require "sentry/dsn"
6
+ require "sentry/release_detector"
5
7
  require "sentry/transport/configuration"
6
8
  require "sentry/linecache"
7
9
  require "sentry/interfaces/stacktrace_builder"
8
10
 
9
11
  module Sentry
10
12
  class Configuration
13
+ include CustomInspection
11
14
  include LoggingHelper
12
15
  # Directories to be recognized as part of your app. e.g. if you
13
16
  # have an `engines` dir at the root of your project, you may want
@@ -63,6 +66,9 @@ module Sentry
63
66
  # - :active_support_logger
64
67
  attr_reader :breadcrumbs_logger
65
68
 
69
+ # Whether to capture local variables from the raised exception's frame. Default is false.
70
+ attr_accessor :capture_exception_frame_locals
71
+
66
72
  # Max number of breadcrumbs a breadcrumb buffer can hold
67
73
  attr_accessor :max_breadcrumbs
68
74
 
@@ -104,7 +110,7 @@ module Sentry
104
110
 
105
111
  # Project directory root for in_app detection. Could be Rails root, etc.
106
112
  # Set automatically for Rails.
107
- attr_reader :project_root
113
+ attr_accessor :project_root
108
114
 
109
115
  # Insert sentry-trace to outgoing requests' headers
110
116
  attr_accessor :propagate_traces
@@ -154,6 +160,10 @@ module Sentry
154
160
  # ```
155
161
  attr_accessor :traces_sampler
156
162
 
163
+ # Send diagnostic client reports about dropped events, true by default
164
+ # tries to attach to an existing envelope max once every 30s
165
+ attr_accessor :send_client_reports
166
+
157
167
  # these are not config options
158
168
  attr_reader :errors, :gem_specs
159
169
 
@@ -177,17 +187,21 @@ module Sentry
177
187
 
178
188
  LOG_PREFIX = "** [Sentry] ".freeze
179
189
  MODULE_SEPARATOR = "::".freeze
190
+ SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
180
191
 
181
192
  # Post initialization callbacks are called at the end of initialization process
182
193
  # allowing extending the configuration of sentry-ruby by multiple extensions
183
194
  @@post_initialization_callbacks = []
184
195
 
185
196
  def initialize
197
+ self.app_dirs_pattern = nil
186
198
  self.debug = false
187
199
  self.background_worker_threads = Concurrent.processor_count
200
+ self.backtrace_cleanup_callback = nil
188
201
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
189
202
  self.breadcrumbs_logger = []
190
203
  self.context_lines = 3
204
+ self.capture_exception_frame_locals = false
191
205
  self.environment = environment_from_env
192
206
  self.enabled_environments = []
193
207
  self.exclude_loggers = []
@@ -202,12 +216,15 @@ module Sentry
202
216
  self.send_modules = true
203
217
  self.send_default_pii = false
204
218
  self.skip_rake_integration = false
219
+ self.send_client_reports = true
205
220
  self.trusted_proxies = []
206
221
  self.dsn = ENV['SENTRY_DSN']
207
222
  self.server_name = server_name_from_env
208
223
 
209
- self.before_send = false
224
+ self.before_send = nil
210
225
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
226
+ self.traces_sample_rate = nil
227
+ self.traces_sampler = nil
211
228
 
212
229
  @transport = Transport::Configuration.new
213
230
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
@@ -216,18 +233,13 @@ module Sentry
216
233
  end
217
234
 
218
235
  def dsn=(value)
219
- return if value.nil? || value.empty?
220
-
221
- @dsn = DSN.new(value)
236
+ @dsn = init_dsn(value)
222
237
  end
223
238
 
224
239
  alias server= dsn=
225
240
 
226
-
227
241
  def async=(value)
228
- if value && !value.respond_to?(:call)
229
- raise(ArgumentError, "async must be callable")
230
- end
242
+ check_callable!("async", value)
231
243
 
232
244
  @async = value
233
245
  end
@@ -246,17 +258,13 @@ module Sentry
246
258
  end
247
259
 
248
260
  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
261
+ check_callable!("before_send", value)
252
262
 
253
263
  @before_send = value
254
264
  end
255
265
 
256
266
  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
267
+ check_callable!("before_breadcrumb", value)
260
268
 
261
269
  @before_breadcrumb = value
262
270
  end
@@ -268,9 +276,13 @@ module Sentry
268
276
  def sending_allowed?
269
277
  @errors = []
270
278
 
271
- valid? &&
272
- capture_in_environment? &&
273
- sample_allowed?
279
+ valid? && capture_in_environment?
280
+ end
281
+
282
+ def sample_allowed?
283
+ return true if sample_rate == 1.0
284
+
285
+ Random.rand < sample_rate
274
286
  end
275
287
 
276
288
  def error_messages
@@ -278,10 +290,6 @@ module Sentry
278
290
  @errors.join(", ")
279
291
  end
280
292
 
281
- def project_root=(root_dir)
282
- @project_root = root_dir
283
- end
284
-
285
293
  def exception_class_allowed?(exc)
286
294
  if exc.is_a?(Sentry::Error)
287
295
  # Try to prevent error reporting loops
@@ -316,10 +324,11 @@ module Sentry
316
324
  def detect_release
317
325
  return unless sending_allowed?
318
326
 
319
- self.release ||= detect_release_from_env ||
320
- detect_release_from_git ||
321
- detect_release_from_capistrano ||
322
- detect_release_from_heroku
327
+ self.release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
328
+
329
+ if running_on_heroku? && release.nil?
330
+ log_warn(HEROKU_DYNO_METADATA_MESSAGE)
331
+ end
323
332
  rescue => e
324
333
  log_error("Error detecting release", e, debug: debug)
325
334
  end
@@ -335,6 +344,18 @@ module Sentry
335
344
 
336
345
  private
337
346
 
347
+ def check_callable!(name, value)
348
+ unless value == nil || value.respond_to?(:call)
349
+ raise ArgumentError, "#{name} must be callable (or nil to disable)"
350
+ end
351
+ end
352
+
353
+ def init_dsn(dsn_string)
354
+ return if dsn_string.nil? || dsn_string.empty?
355
+
356
+ DSN.new(dsn_string)
357
+ end
358
+
338
359
  def excluded_exception?(incoming_exception)
339
360
  excluded_exception_classes.any? do |excluded_exception|
340
361
  matches_exception?(excluded_exception, incoming_exception)
@@ -364,37 +385,6 @@ module Sentry
364
385
  nil
365
386
  end
366
387
 
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
388
  def capture_in_environment?
399
389
  return true if enabled_in_current_env?
400
390
 
@@ -411,24 +401,6 @@ module Sentry
411
401
  end
412
402
  end
413
403
 
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
404
  def environment_from_env
433
405
  ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
434
406
  end
@@ -437,10 +409,16 @@ module Sentry
437
409
  if running_on_heroku?
438
410
  ENV['DYNO']
439
411
  else
440
- resolve_hostname
412
+ # Try to resolve the hostname to an FQDN, but fall back to whatever
413
+ # the load name is.
414
+ Socket.gethostname || Socket.gethostbyname(hostname).first rescue server_name
441
415
  end
442
416
  end
443
417
 
418
+ def running_on_heroku?
419
+ File.directory?("/etc/heroku") && !ENV["CI"]
420
+ end
421
+
444
422
  def run_post_initialization_callbacks
445
423
  self.class.post_initialization_callbacks.each do |hook|
446
424
  instance_eval(&hook)
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
 
data/lib/sentry/hub.rb CHANGED
@@ -133,6 +133,11 @@ module Sentry
133
133
 
134
134
  event = current_client.capture_event(event, scope, hint)
135
135
 
136
+
137
+ if event && configuration.debug
138
+ configuration.log_debug(event.to_json_compatible)
139
+ end
140
+
136
141
  @last_event_id = event&.event_id
137
142
  event
138
143
  end
@@ -1,5 +1,14 @@
1
+ require "sentry/utils/exception_cause_chain"
2
+
1
3
  module Sentry
2
4
  class SingleExceptionInterface < Interface
5
+ include CustomInspection
6
+
7
+ SKIP_INSPECTION_ATTRIBUTES = [:@stacktrace]
8
+ PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]".freeze
9
+ OMISSION_MARK = "...".freeze
10
+ MAX_LOCAL_BYTES = 1024
11
+
3
12
  attr_reader :type, :value, :module, :thread_id, :stacktrace
4
13
 
5
14
  def initialize(exception:, stacktrace: nil)
@@ -20,6 +29,26 @@ module Sentry
20
29
  # also see `StacktraceBuilder.build`.
21
30
  def self.build_with_stacktrace(exception:, stacktrace_builder:)
22
31
  stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
32
+
33
+ if locals = exception.instance_variable_get(:@sentry_locals)
34
+ locals.each do |k, v|
35
+ locals[k] =
36
+ begin
37
+ v = v.inspect unless v.is_a?(String)
38
+
39
+ if v.length >= MAX_LOCAL_BYTES
40
+ v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
41
+ end
42
+
43
+ v
44
+ rescue StandardError
45
+ PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
46
+ end
47
+ end
48
+
49
+ stacktrace.frames.last.vars = locals
50
+ end
51
+
23
52
  new(exception: exception, stacktrace: stacktrace)
24
53
  end
25
54
  end
@@ -10,6 +10,10 @@ module Sentry
10
10
  { frames: @frames.map(&:to_hash) }
11
11
  end
12
12
 
13
+ def inspect
14
+ @frames.map(&:to_s)
15
+ end
16
+
13
17
  private
14
18
 
15
19
  # Not actually an interface, but I want to use the same style
@@ -28,6 +32,10 @@ module Sentry
28
32
  @filename = compute_filename
29
33
  end
30
34
 
35
+ def to_s
36
+ "#{@filename}:#{@lineno}"
37
+ end
38
+
31
39
  def compute_filename
32
40
  return if abs_path.nil?
33
41
 
@@ -0,0 +1,36 @@
1
+ module Sentry
2
+ class ReleaseDetector
3
+ class << self
4
+ def detect_release(project_root:, running_on_heroku:)
5
+ detect_release_from_env ||
6
+ detect_release_from_git ||
7
+ detect_release_from_capistrano(project_root) ||
8
+ detect_release_from_heroku(running_on_heroku)
9
+ end
10
+
11
+ def detect_release_from_heroku(running_on_heroku)
12
+ return unless running_on_heroku
13
+ ENV['HEROKU_SLUG_COMMIT']
14
+ end
15
+
16
+ def detect_release_from_capistrano(project_root)
17
+ revision_file = File.join(project_root, 'REVISION')
18
+ revision_log = File.join(project_root, '..', 'revisions.log')
19
+
20
+ if File.exist?(revision_file)
21
+ File.read(revision_file).strip
22
+ elsif File.exist?(revision_log)
23
+ File.open(revision_log).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
24
+ end
25
+ end
26
+
27
+ def detect_release_from_git
28
+ Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
29
+ end
30
+
31
+ def detect_release_from_env
32
+ ENV['SENTRY_RELEASE']
33
+ end
34
+ end
35
+ end
36
+ end
@@ -129,7 +129,10 @@ module Sentry
129
129
  @name = UNLABELD_NAME
130
130
  end
131
131
 
132
- return unless @sampled || @parent_sampled
132
+ unless @sampled || @parent_sampled
133
+ hub.current_client.transport.record_lost_event(:sample_rate, 'transaction')
134
+ return
135
+ end
133
136
 
134
137
  event = hub.current_client.event_from_transaction(self)
135
138
  hub.capture_event(event)
@@ -5,18 +5,34 @@ module Sentry
5
5
  class Transport
6
6
  PROTOCOL_VERSION = '7'
7
7
  USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
8
+ CLIENT_REPORT_INTERVAL = 30
9
+
10
+ # https://develop.sentry.dev/sdk/client-reports/#envelope-item-payload
11
+ CLIENT_REPORT_REASONS = [
12
+ :ratelimit_backoff,
13
+ :queue_overflow,
14
+ :cache_overflow, # NA
15
+ :network_error,
16
+ :sample_rate,
17
+ :before_send,
18
+ :event_processor
19
+ ]
8
20
 
9
21
  include LoggingHelper
10
22
 
11
- attr_accessor :configuration
12
- attr_reader :logger, :rate_limits
23
+ attr_reader :logger, :rate_limits, :discarded_events, :last_client_report_sent
13
24
 
14
25
  def initialize(configuration)
15
- @configuration = configuration
16
26
  @logger = configuration.logger
17
27
  @transport_configuration = configuration.transport
18
28
  @dsn = configuration.dsn
19
29
  @rate_limits = {}
30
+ @send_client_reports = configuration.send_client_reports
31
+
32
+ if @send_client_reports
33
+ @discarded_events = Hash.new(0)
34
+ @last_client_report_sent = Time.now
35
+ end
20
36
  end
21
37
 
22
38
  def send_data(data, options = {})
@@ -27,14 +43,9 @@ module Sentry
27
43
  event_hash = event.to_hash
28
44
  item_type = get_item_type(event_hash)
29
45
 
30
- unless configuration.sending_allowed?
31
- log_debug("Envelope [#{item_type}] not sent: #{configuration.error_messages}")
32
-
33
- return
34
- end
35
-
36
46
  if is_rate_limited?(item_type)
37
47
  log_info("Envelope [#{item_type}] not sent: rate limiting")
48
+ record_lost_event(:ratelimit_backoff, item_type)
38
49
 
39
50
  return
40
51
  end
@@ -91,27 +102,79 @@ module Sentry
91
102
 
92
103
  def encode(event)
93
104
  # Convert to hash
94
- event_hash = event.to_hash
105
+ event_payload = event.to_hash
95
106
 
96
- event_id = event_hash[:event_id] || event_hash["event_id"]
97
- item_type = get_item_type(event_hash)
107
+ event_id = event_payload[:event_id] || event_payload["event_id"]
108
+ item_type = get_item_type(event_payload)
109
+
110
+ envelope_header = {
111
+ event_id: event_id,
112
+ dsn: @dsn.to_s,
113
+ sdk: Sentry.sdk_meta,
114
+ sent_at: Sentry.utc_now.iso8601
115
+ }
116
+
117
+ event_header = { type: item_type, content_type: 'application/json' }
98
118
 
99
119
  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)}
120
+ #{JSON.generate(envelope_header)}
121
+ #{JSON.generate(event_header)}
122
+ #{JSON.generate(event_payload)}
103
123
  ENVELOPE
104
124
 
125
+ client_report = fetch_pending_client_report
126
+ envelope << client_report if client_report
127
+
105
128
  log_info("Sending envelope [#{item_type}] #{event_id} to Sentry")
106
129
 
107
130
  envelope
108
131
  end
109
132
 
133
+ def record_lost_event(reason, item_type)
134
+ return unless @send_client_reports
135
+ return unless CLIENT_REPORT_REASONS.include?(reason)
136
+
137
+ item_type ||= 'event'
138
+ @discarded_events[[reason, item_type]] += 1
139
+ end
140
+
110
141
  private
111
142
 
112
143
  def get_item_type(event_hash)
113
144
  event_hash[:type] || event_hash["type"] || "event"
114
145
  end
146
+
147
+ def fetch_pending_client_report
148
+ return nil unless @send_client_reports
149
+ return nil if @last_client_report_sent > Time.now - CLIENT_REPORT_INTERVAL
150
+ return nil if @discarded_events.empty?
151
+
152
+ discarded_events_hash = @discarded_events.map do |key, val|
153
+ reason, type = key
154
+
155
+ # 'event' has to be mapped to 'error'
156
+ category = type == 'transaction' ? 'transaction' : 'error'
157
+
158
+ { reason: reason, category: category, quantity: val }
159
+ end
160
+
161
+ item_header = { type: 'client_report' }
162
+
163
+ item_payload = {
164
+ timestamp: Sentry.utc_now.iso8601,
165
+ discarded_events: discarded_events_hash
166
+ }
167
+
168
+ client_report_item = <<~CLIENT_REPORT_ITEM
169
+ #{JSON.generate(item_header)}
170
+ #{JSON.generate(item_payload)}
171
+ CLIENT_REPORT_ITEM
172
+
173
+ @discarded_events = Hash.new(0)
174
+ @last_client_report_sent = Time.now
175
+
176
+ client_report_item
177
+ end
115
178
  end
116
179
  end
117
180
 
@@ -0,0 +1,12 @@
1
+ module Sentry
2
+ module CustomInspection
3
+ def inspect
4
+ attr_strings = (instance_variables - self.class::SKIP_INSPECTION_ATTRIBUTES).each_with_object([]) do |attr, result|
5
+ value = instance_variable_get(attr)
6
+ result << "#{attr}=#{value.inspect}" if value
7
+ end
8
+
9
+ "#<#{self.class.name} #{attr_strings.join(", ")}>"
10
+ end
11
+ end
12
+ end
@@ -2,18 +2,16 @@ module Sentry
2
2
  module Utils
3
3
  module ExceptionCauseChain
4
4
  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 }
5
+ exceptions = [exception]
10
6
 
11
- exceptions << exception
12
- end
13
- exceptions
14
- else
15
- [exception]
7
+ while exception.cause
8
+ exception = exception.cause
9
+ break if exceptions.any? { |e| e.object_id == exception.object_id }
10
+
11
+ exceptions << exception
16
12
  end
13
+
14
+ exceptions
17
15
  end
18
16
  end
19
17
  end
@@ -1,3 +1,3 @@
1
1
  module Sentry
2
- VERSION = "4.7.3"
2
+ VERSION = "4.8.0"
3
3
  end
data/lib/sentry-ruby.rb CHANGED
@@ -37,6 +37,22 @@ module Sentry
37
37
  THREAD_LOCAL = :sentry_hub
38
38
 
39
39
  class << self
40
+ def exception_locals_tp
41
+ @exception_locals_tp ||= TracePoint.new(:raise) do |tp|
42
+ exception = tp.raised_exception
43
+
44
+ # don't collect locals again if the exception is re-raised
45
+ next if exception.instance_variable_get(:@sentry_locals)
46
+ next unless tp.binding
47
+
48
+ locals = tp.binding.local_variables.each_with_object({}) do |local, result|
49
+ result[local] = tp.binding.local_variable_get(local)
50
+ end
51
+
52
+ exception.instance_variable_set(:@sentry_locals, locals)
53
+ end
54
+ end
55
+
40
56
  attr_accessor :background_worker
41
57
 
42
58
  ##### Patch Registration #####
@@ -88,6 +104,10 @@ module Sentry
88
104
  Thread.current.thread_variable_set(THREAD_LOCAL, hub)
89
105
  @main_hub = hub
90
106
  @background_worker = Sentry::BackgroundWorker.new(config)
107
+
108
+ if config.capture_exception_frame_locals
109
+ exception_locals_tp.enable
110
+ end
91
111
  end
92
112
 
93
113
  # 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.3
4
+ version: 4.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-22 00:00:00.000000000 Z
11
+ date: 2021-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -85,6 +85,7 @@ files:
85
85
  - lib/sentry/rack.rb
86
86
  - lib/sentry/rack/capture_exceptions.rb
87
87
  - lib/sentry/rake.rb
88
+ - lib/sentry/release_detector.rb
88
89
  - lib/sentry/scope.rb
89
90
  - lib/sentry/span.rb
90
91
  - lib/sentry/transaction.rb
@@ -94,6 +95,7 @@ files:
94
95
  - lib/sentry/transport/dummy_transport.rb
95
96
  - lib/sentry/transport/http_transport.rb
96
97
  - lib/sentry/utils/argument_checking_helper.rb
98
+ - lib/sentry/utils/custom_inspection.rb
97
99
  - lib/sentry/utils/exception_cause_chain.rb
98
100
  - lib/sentry/utils/logging_helper.rb
99
101
  - lib/sentry/utils/real_ip.rb