sentry-ruby-core 4.7.0 → 4.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +4 -2
- data/LICENSE.txt +1 -1
- data/README.md +7 -7
- data/bin/console +5 -1
- data/lib/sentry/client.rb +11 -1
- data/lib/sentry/configuration.rb +55 -77
- data/lib/sentry/event.rb +5 -0
- data/lib/sentry/hub.rb +11 -4
- 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/LICENSE.txt
CHANGED
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
|
-
| [![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/) |
|
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
|
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,36 +401,24 @@ 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
|
-
ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || '
|
405
|
+
ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
434
406
|
end
|
435
407
|
|
436
408
|
def server_name_from_env
|
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
@@ -90,10 +90,10 @@ module Sentry
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def capture_exception(exception, **options, &block)
|
93
|
-
return unless current_client
|
94
|
-
|
95
93
|
check_argument_type!(exception, ::Exception)
|
96
94
|
|
95
|
+
return unless current_client
|
96
|
+
|
97
97
|
options[:hint] ||= {}
|
98
98
|
options[:hint][:exception] = exception
|
99
99
|
event = current_client.event_from_exception(exception, options[:hint])
|
@@ -104,6 +104,8 @@ module Sentry
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def capture_message(message, **options, &block)
|
107
|
+
check_argument_type!(message, ::String)
|
108
|
+
|
107
109
|
return unless current_client
|
108
110
|
|
109
111
|
options[:hint] ||= {}
|
@@ -114,10 +116,10 @@ module Sentry
|
|
114
116
|
end
|
115
117
|
|
116
118
|
def capture_event(event, **options, &block)
|
117
|
-
return unless current_client
|
118
|
-
|
119
119
|
check_argument_type!(event, Sentry::Event)
|
120
120
|
|
121
|
+
return unless current_client
|
122
|
+
|
121
123
|
hint = options.delete(:hint) || {}
|
122
124
|
scope = current_scope.dup
|
123
125
|
|
@@ -131,6 +133,11 @@ module Sentry
|
|
131
133
|
|
132
134
|
event = current_client.capture_event(event, scope, hint)
|
133
135
|
|
136
|
+
|
137
|
+
if event && configuration.debug
|
138
|
+
configuration.log_debug(event.to_json_compatible)
|
139
|
+
end
|
140
|
+
|
134
141
|
@last_event_id = event&.event_id
|
135
142
|
event
|
136
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
|
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
|