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 +4 -4
- data/Gemfile +4 -2
- data/README.md +7 -7
- data/bin/console +5 -1
- data/lib/sentry/client.rb +11 -1
- data/lib/sentry/configuration.rb +54 -76
- data/lib/sentry/event.rb +5 -0
- data/lib/sentry/hub.rb +5 -0
- data/lib/sentry/interfaces/single_exception.rb +29 -0
- data/lib/sentry/interfaces/stacktrace.rb +8 -0
- data/lib/sentry/release_detector.rb +36 -0
- data/lib/sentry/transaction.rb +4 -1
- data/lib/sentry/transport.rb +78 -15
- data/lib/sentry/utils/custom_inspection.rb +12 -0
- data/lib/sentry/utils/exception_cause_chain.rb +8 -10
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +20 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f675111daf93ba05db7de62dcfa1dd9268f6ba009265de08e0e4f118ffea0c62
|
4
|
+
data.tar.gz: 2ece2ccaf14be6cc5fa762b77a57cd63dcc3fe52a03987c8c4136a43ec62bbd1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 |
|
14
|
-
| --- | ----- | -------- | --------- |
|
15
|
-
| [](https://rubygems.org/gems/sentry-ruby) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-ruby/) |
|
16
|
-
| [](https://rubygems.org/gems/sentry-rails) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-rails/) |
|
17
|
-
| [](https://rubygems.org/gems/sentry-sidekiq) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-sidekiq/) |
|
18
|
-
| [](https://rubygems.org/gems/sentry-delayed_job) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-delayed_job/) |
|
19
|
-
| [](https://rubygems.org/gems/sentry-resque) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-resque/) |
|
13
|
+
| current version | build | coverage | downloads |
|
14
|
+
| --- | ----- | -------- | --------- |
|
15
|
+
| [](https://rubygems.org/gems/sentry-ruby) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-ruby/) |
|
16
|
+
| [](https://rubygems.org/gems/sentry-rails) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-rails/) |
|
17
|
+
| [](https://rubygems.org/gems/sentry-sidekiq) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-sidekiq/) |
|
18
|
+
| [](https://rubygems.org/gems/sentry-delayed_job) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-delayed_job/) |
|
19
|
+
| [](https://rubygems.org/gems/sentry-resque) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](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
|
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
|
|
data/lib/sentry/configuration.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
273
|
-
|
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 ||=
|
320
|
-
|
321
|
-
|
322
|
-
|
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
|
-
|
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
@@ -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
|
data/lib/sentry/transaction.rb
CHANGED
@@ -129,7 +129,10 @@ module Sentry
|
|
129
129
|
@name = UNLABELD_NAME
|
130
130
|
end
|
131
131
|
|
132
|
-
|
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)
|
data/lib/sentry/transport.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
105
|
+
event_payload = event.to_hash
|
95
106
|
|
96
|
-
event_id =
|
97
|
-
item_type = get_item_type(
|
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
|
-
|
101
|
-
|
102
|
-
#{JSON.generate(
|
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
|
-
|
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
|
-
|
12
|
-
|
13
|
-
exceptions
|
14
|
-
|
15
|
-
|
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
|
data/lib/sentry/version.rb
CHANGED
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.
|
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-
|
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
|