sentry-ruby-core 4.1.6 → 4.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.craft.yml +3 -3
- data/CHANGELOG.md +171 -1
- data/Gemfile +6 -3
- data/README.md +39 -49
- data/lib/sentry-ruby.rb +30 -9
- data/lib/sentry/background_worker.rb +8 -4
- data/lib/sentry/breadcrumb.rb +7 -2
- data/lib/sentry/breadcrumb/sentry_logger.rb +2 -1
- data/lib/sentry/breadcrumb_buffer.rb +3 -2
- data/lib/sentry/client.rb +62 -28
- data/lib/sentry/configuration.rb +80 -17
- data/lib/sentry/event.rb +33 -45
- data/lib/sentry/exceptions.rb +7 -0
- data/lib/sentry/hub.rb +24 -4
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +19 -1
- data/lib/sentry/interfaces/request.rb +10 -10
- data/lib/sentry/interfaces/single_exception.rb +16 -4
- data/lib/sentry/interfaces/stacktrace.rb +9 -26
- data/lib/sentry/interfaces/stacktrace_builder.rb +50 -0
- data/lib/sentry/interfaces/threads.rb +32 -0
- data/lib/sentry/net/http.rb +126 -0
- data/lib/sentry/rack/capture_exceptions.rb +18 -14
- data/lib/sentry/rake.rb +1 -1
- data/lib/sentry/scope.rb +12 -6
- data/lib/sentry/span.rb +21 -4
- data/lib/sentry/transaction.rb +55 -43
- data/lib/sentry/transaction_event.rb +3 -1
- data/lib/sentry/transport.rb +58 -27
- data/lib/sentry/transport/configuration.rb +3 -1
- data/lib/sentry/transport/http_transport.rb +92 -6
- data/lib/sentry/utils/logging_helper.rb +24 -0
- data/lib/sentry/utils/real_ip.rb +13 -7
- data/lib/sentry/version.rb +1 -1
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +1 -1
- metadata +9 -4
@@ -1,23 +1,27 @@
|
|
1
1
|
require "concurrent/executor/thread_pool_executor"
|
2
2
|
require "concurrent/executor/immediate_executor"
|
3
|
+
require "concurrent/configuration"
|
3
4
|
|
4
5
|
module Sentry
|
5
6
|
class BackgroundWorker
|
6
|
-
|
7
|
+
include LoggingHelper
|
8
|
+
|
9
|
+
attr_reader :max_queue, :number_of_threads, :logger
|
7
10
|
|
8
11
|
def initialize(configuration)
|
9
12
|
@max_queue = 30
|
10
13
|
@number_of_threads = configuration.background_worker_threads
|
14
|
+
@logger = configuration.logger
|
11
15
|
|
12
16
|
@executor =
|
13
17
|
if configuration.async
|
14
|
-
|
18
|
+
log_debug("config.async is set, BackgroundWorker is disabled")
|
15
19
|
Concurrent::ImmediateExecutor.new
|
16
20
|
elsif @number_of_threads == 0
|
17
|
-
|
21
|
+
log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
|
18
22
|
Concurrent::ImmediateExecutor.new
|
19
23
|
else
|
20
|
-
|
24
|
+
log_debug("initialized a background worker with #{@number_of_threads} threads")
|
21
25
|
|
22
26
|
Concurrent::ThreadPoolExecutor.new(
|
23
27
|
min_threads: 0,
|
data/lib/sentry/breadcrumb.rb
CHANGED
@@ -2,15 +2,16 @@ module Sentry
|
|
2
2
|
class Breadcrumb
|
3
3
|
DATA_SERIALIZATION_ERROR_MESSAGE = "[data were removed due to serialization issues]"
|
4
4
|
|
5
|
-
attr_accessor :category, :data, :
|
5
|
+
attr_accessor :category, :data, :level, :timestamp, :type
|
6
|
+
attr_reader :message
|
6
7
|
|
7
8
|
def initialize(category: nil, data: nil, message: nil, timestamp: nil, level: nil, type: nil)
|
8
9
|
@category = category
|
9
10
|
@data = data || {}
|
10
11
|
@level = level
|
11
|
-
@message = message
|
12
12
|
@timestamp = timestamp || Sentry.utc_now.to_i
|
13
13
|
@type = type
|
14
|
+
self.message = message
|
14
15
|
end
|
15
16
|
|
16
17
|
def to_hash
|
@@ -24,6 +25,10 @@ module Sentry
|
|
24
25
|
}
|
25
26
|
end
|
26
27
|
|
28
|
+
def message=(msg)
|
29
|
+
@message = (msg || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
|
30
|
+
end
|
31
|
+
|
27
32
|
private
|
28
33
|
|
29
34
|
def serialized_data
|
@@ -14,6 +14,7 @@ module Sentry
|
|
14
14
|
def add(*args, &block)
|
15
15
|
super
|
16
16
|
add_breadcrumb(*args, &block)
|
17
|
+
nil
|
17
18
|
end
|
18
19
|
|
19
20
|
def add_breadcrumb(severity, message = nil, progname = nil)
|
@@ -49,7 +50,7 @@ module Sentry
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
|
-
return if ignored_logger?(progname) || message
|
53
|
+
return if ignored_logger?(progname) || message == ""
|
53
54
|
|
54
55
|
# some loggers will add leading/trailing space as they (incorrectly, mind you)
|
55
56
|
# think of logging as a shortcut to std{out,err}
|
@@ -2,12 +2,13 @@ require "sentry/breadcrumb"
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class BreadcrumbBuffer
|
5
|
+
DEFAULT_SIZE = 100
|
5
6
|
include Enumerable
|
6
7
|
|
7
8
|
attr_accessor :buffer
|
8
9
|
|
9
|
-
def initialize(size =
|
10
|
-
@buffer = Array.new(size)
|
10
|
+
def initialize(size = nil)
|
11
|
+
@buffer = Array.new(size || DEFAULT_SIZE)
|
11
12
|
end
|
12
13
|
|
13
14
|
def record(crumb)
|
data/lib/sentry/client.rb
CHANGED
@@ -2,10 +2,13 @@ require "sentry/transport"
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class Client
|
5
|
-
|
5
|
+
include LoggingHelper
|
6
|
+
|
7
|
+
attr_reader :transport, :configuration, :logger
|
6
8
|
|
7
9
|
def initialize(configuration)
|
8
10
|
@configuration = configuration
|
11
|
+
@logger = configuration.logger
|
9
12
|
|
10
13
|
if transport_class = configuration.transport.transport_class
|
11
14
|
@transport = transport_class.new(configuration)
|
@@ -26,32 +29,17 @@ module Sentry
|
|
26
29
|
scope.apply_to_event(event, hint)
|
27
30
|
|
28
31
|
if async_block = configuration.async
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
event_hash = event.to_json_compatible
|
33
|
-
|
34
|
-
if async_block.arity == 2
|
35
|
-
hint = JSON.parse(JSON.generate(hint))
|
36
|
-
async_block.call(event_hash, hint)
|
37
|
-
else
|
38
|
-
async_block.call(event_hash)
|
39
|
-
end
|
40
|
-
rescue => e
|
41
|
-
configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
|
42
|
-
send_event(event, hint)
|
43
|
-
end
|
32
|
+
dispatch_async_event(async_block, event, hint)
|
33
|
+
elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
|
34
|
+
dispatch_background_event(event, hint)
|
44
35
|
else
|
45
|
-
|
46
|
-
Sentry.background_worker.perform do
|
47
|
-
send_event(event, hint)
|
48
|
-
end
|
49
|
-
else
|
50
|
-
send_event(event, hint)
|
51
|
-
end
|
36
|
+
send_event(event, hint)
|
52
37
|
end
|
53
38
|
|
54
39
|
event
|
40
|
+
rescue => e
|
41
|
+
log_error("Event capturing failed", e, debug: configuration.debug)
|
42
|
+
nil
|
55
43
|
end
|
56
44
|
|
57
45
|
def event_from_exception(exception, hint = {})
|
@@ -60,12 +48,15 @@ module Sentry
|
|
60
48
|
|
61
49
|
Event.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
|
62
50
|
event.add_exception_interface(exception)
|
51
|
+
event.add_threads_interface(crashed: true)
|
63
52
|
end
|
64
53
|
end
|
65
54
|
|
66
55
|
def event_from_message(message, hint = {})
|
67
56
|
integration_meta = Sentry.integrations[hint[:integration]]
|
68
|
-
Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
|
57
|
+
event = Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
|
58
|
+
event.add_threads_interface(backtrace: caller)
|
59
|
+
event
|
69
60
|
end
|
70
61
|
|
71
62
|
def event_from_transaction(transaction)
|
@@ -82,16 +73,59 @@ module Sentry
|
|
82
73
|
|
83
74
|
def send_event(event, hint = nil)
|
84
75
|
event_type = event.is_a?(Event) ? event.type : event["type"]
|
85
|
-
event = configuration.before_send.call(event, hint) if configuration.before_send && event_type == "event"
|
86
76
|
|
87
|
-
if
|
88
|
-
configuration.
|
89
|
-
|
77
|
+
if event_type != TransactionEvent::TYPE && configuration.before_send
|
78
|
+
event = configuration.before_send.call(event, hint)
|
79
|
+
|
80
|
+
if event.nil?
|
81
|
+
log_info("Discarded event because before_send returned nil")
|
82
|
+
return
|
83
|
+
end
|
90
84
|
end
|
91
85
|
|
92
86
|
transport.send_event(event)
|
93
87
|
|
94
88
|
event
|
89
|
+
rescue => e
|
90
|
+
loggable_event_type = (event_type || "event").capitalize
|
91
|
+
log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
|
92
|
+
|
93
|
+
event_info = Event.get_log_message(event.to_hash)
|
94
|
+
log_info("Unreported #{loggable_event_type}: #{event_info}")
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
|
98
|
+
def generate_sentry_trace(span)
|
99
|
+
return unless configuration.propagate_traces
|
100
|
+
|
101
|
+
trace = span.to_sentry_trace
|
102
|
+
log_debug("[Tracing] Adding #{SENTRY_TRACE_HEADER_NAME} header to outgoing request: #{trace}")
|
103
|
+
trace
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def dispatch_background_event(event, hint)
|
109
|
+
Sentry.background_worker.perform do
|
110
|
+
send_event(event, hint)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def dispatch_async_event(async_block, event, hint)
|
115
|
+
# We have to convert to a JSON-like hash, because background job
|
116
|
+
# processors (esp ActiveJob) may not like weird types in the event hash
|
117
|
+
event_hash = event.to_json_compatible
|
118
|
+
|
119
|
+
if async_block.arity == 2
|
120
|
+
hint = JSON.parse(JSON.generate(hint))
|
121
|
+
async_block.call(event_hash, hint)
|
122
|
+
else
|
123
|
+
async_block.call(event_hash)
|
124
|
+
end
|
125
|
+
rescue => e
|
126
|
+
loggable_event_type = event_hash["type"] || "event"
|
127
|
+
log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
|
128
|
+
send_event(event, hint)
|
95
129
|
end
|
96
130
|
end
|
97
131
|
end
|
data/lib/sentry/configuration.rb
CHANGED
@@ -4,9 +4,11 @@ require "sentry/utils/exception_cause_chain"
|
|
4
4
|
require "sentry/dsn"
|
5
5
|
require "sentry/transport/configuration"
|
6
6
|
require "sentry/linecache"
|
7
|
+
require "sentry/interfaces/stacktrace_builder"
|
7
8
|
|
8
9
|
module Sentry
|
9
10
|
class Configuration
|
11
|
+
include LoggingHelper
|
10
12
|
# Directories to be recognized as part of your app. e.g. if you
|
11
13
|
# have an `engines` dir at the root of your project, you may want
|
12
14
|
# to set this to something like /(app|config|engines|lib)/
|
@@ -38,10 +40,19 @@ module Sentry
|
|
38
40
|
#
|
39
41
|
attr_accessor :backtrace_cleanup_callback
|
40
42
|
|
43
|
+
# Optional Proc, called before adding the breadcrumb to the current scope
|
44
|
+
# E.g.: lambda { |breadcrumb, hint| breadcrumb }
|
45
|
+
# E.g.: lambda { |breadcrumb, hint| nil }
|
46
|
+
# E.g.: lambda { |breadcrumb, hint|
|
47
|
+
# breadcrumb.message = 'a'
|
48
|
+
# breadcrumb
|
49
|
+
# }
|
50
|
+
attr_reader :before_breadcrumb
|
51
|
+
|
41
52
|
# Optional Proc, called before sending an event to the server/
|
42
|
-
# E.g.: lambda { |event| event }
|
43
|
-
# E.g.: lambda { |event| nil }
|
44
|
-
# E.g.: lambda { |event|
|
53
|
+
# E.g.: lambda { |event, hint| event }
|
54
|
+
# E.g.: lambda { |event, hint| nil }
|
55
|
+
# E.g.: lambda { |event, hint|
|
45
56
|
# event[:message] = 'a'
|
46
57
|
# event
|
47
58
|
# }
|
@@ -52,12 +63,19 @@ module Sentry
|
|
52
63
|
# - :active_support_logger
|
53
64
|
attr_reader :breadcrumbs_logger
|
54
65
|
|
66
|
+
# Max number of breadcrumbs a breadcrumb buffer can hold
|
67
|
+
attr_accessor :max_breadcrumbs
|
68
|
+
|
55
69
|
# Number of lines of code context to capture, or nil for none
|
56
70
|
attr_accessor :context_lines
|
57
71
|
|
58
72
|
# RACK_ENV by default.
|
59
73
|
attr_reader :environment
|
60
74
|
|
75
|
+
# Whether the SDK should run in the debugging mode. Default is false.
|
76
|
+
# If set to true, SDK errors will be logged with backtrace
|
77
|
+
attr_accessor :debug
|
78
|
+
|
61
79
|
# the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
|
62
80
|
attr_reader :dsn
|
63
81
|
|
@@ -88,6 +106,9 @@ module Sentry
|
|
88
106
|
# Set automatically for Rails.
|
89
107
|
attr_reader :project_root
|
90
108
|
|
109
|
+
# Insert sentry-trace to outgoing requests' headers
|
110
|
+
attr_accessor :propagate_traces
|
111
|
+
|
91
112
|
# Array of rack env parameters to be included in the event sent to sentry.
|
92
113
|
attr_accessor :rack_env_whitelist
|
93
114
|
|
@@ -109,6 +130,12 @@ module Sentry
|
|
109
130
|
# will not be sent to Sentry.
|
110
131
|
attr_accessor :send_default_pii
|
111
132
|
|
133
|
+
# Allow to skip Sentry emails within rake tasks
|
134
|
+
attr_accessor :skip_rake_integration
|
135
|
+
|
136
|
+
# IP ranges for trusted proxies that will be skipped when calculating IP address.
|
137
|
+
attr_accessor :trusted_proxies
|
138
|
+
|
112
139
|
attr_accessor :server_name
|
113
140
|
|
114
141
|
# Return a Transport::Configuration object for transport-related configurations.
|
@@ -151,25 +178,32 @@ module Sentry
|
|
151
178
|
LOG_PREFIX = "** [Sentry] ".freeze
|
152
179
|
MODULE_SEPARATOR = "::".freeze
|
153
180
|
|
154
|
-
|
181
|
+
# Post initialization callbacks are called at the end of initialization process
|
182
|
+
# allowing extending the configuration of sentry-ruby by multiple extensions
|
183
|
+
@@post_initialization_callbacks = []
|
155
184
|
|
156
185
|
def initialize
|
186
|
+
self.debug = false
|
157
187
|
self.background_worker_threads = Concurrent.processor_count
|
188
|
+
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
|
158
189
|
self.breadcrumbs_logger = []
|
159
190
|
self.context_lines = 3
|
160
191
|
self.environment = environment_from_env
|
161
192
|
self.enabled_environments = []
|
162
193
|
self.exclude_loggers = []
|
163
194
|
self.excluded_exceptions = IGNORE_DEFAULT.dup
|
164
|
-
self.inspect_exception_causes_for_exclusion =
|
195
|
+
self.inspect_exception_causes_for_exclusion = true
|
165
196
|
self.linecache = ::Sentry::LineCache.new
|
166
197
|
self.logger = ::Sentry::Logger.new(STDOUT)
|
167
198
|
self.project_root = Dir.pwd
|
199
|
+
self.propagate_traces = true
|
168
200
|
|
169
201
|
self.release = detect_release
|
170
202
|
self.sample_rate = 1.0
|
171
203
|
self.send_modules = true
|
172
204
|
self.send_default_pii = false
|
205
|
+
self.skip_rake_integration = false
|
206
|
+
self.trusted_proxies = []
|
173
207
|
self.dsn = ENV['SENTRY_DSN']
|
174
208
|
self.server_name = server_name_from_env
|
175
209
|
|
@@ -178,7 +212,8 @@ module Sentry
|
|
178
212
|
|
179
213
|
@transport = Transport::Configuration.new
|
180
214
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
181
|
-
|
215
|
+
|
216
|
+
run_post_initialization_callbacks
|
182
217
|
end
|
183
218
|
|
184
219
|
def dsn=(value)
|
@@ -203,10 +238,6 @@ module Sentry
|
|
203
238
|
if logger.is_a?(Array)
|
204
239
|
logger
|
205
240
|
else
|
206
|
-
unless AVAILABLE_BREADCRUMBS_LOGGERS.include?(logger)
|
207
|
-
raise Sentry::Error, "Unsupported breadcrumbs logger. Supported loggers: #{AVAILABLE_BREADCRUMBS_LOGGERS}"
|
208
|
-
end
|
209
|
-
|
210
241
|
Array(logger)
|
211
242
|
end
|
212
243
|
|
@@ -223,6 +254,14 @@ module Sentry
|
|
223
254
|
@before_send = value
|
224
255
|
end
|
225
256
|
|
257
|
+
def before_breadcrumb=(value)
|
258
|
+
unless value.nil? || value.respond_to?(:call)
|
259
|
+
raise ArgumentError, "before_breadcrumb must be callable (or nil to disable)"
|
260
|
+
end
|
261
|
+
|
262
|
+
@before_breadcrumb = value
|
263
|
+
end
|
264
|
+
|
226
265
|
def environment=(environment)
|
227
266
|
@environment = environment.to_s
|
228
267
|
end
|
@@ -247,10 +286,10 @@ module Sentry
|
|
247
286
|
def exception_class_allowed?(exc)
|
248
287
|
if exc.is_a?(Sentry::Error)
|
249
288
|
# Try to prevent error reporting loops
|
250
|
-
|
289
|
+
log_debug("Refusing to capture Sentry error: #{exc.inspect}")
|
251
290
|
false
|
252
291
|
elsif excluded_exception?(exc)
|
253
|
-
|
292
|
+
log_debug("User excluded error: #{exc.inspect}")
|
254
293
|
false
|
255
294
|
else
|
256
295
|
true
|
@@ -262,7 +301,17 @@ module Sentry
|
|
262
301
|
end
|
263
302
|
|
264
303
|
def tracing_enabled?
|
265
|
-
!!((@traces_sample_rate && @traces_sample_rate
|
304
|
+
!!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler) && sending_allowed?
|
305
|
+
end
|
306
|
+
|
307
|
+
def stacktrace_builder
|
308
|
+
@stacktrace_builder ||= StacktraceBuilder.new(
|
309
|
+
project_root: @project_root.to_s,
|
310
|
+
app_dirs_pattern: @app_dirs_pattern,
|
311
|
+
linecache: @linecache,
|
312
|
+
context_lines: @context_lines,
|
313
|
+
backtrace_cleanup_callback: @backtrace_cleanup_callback
|
314
|
+
)
|
266
315
|
end
|
267
316
|
|
268
317
|
private
|
@@ -273,7 +322,7 @@ module Sentry
|
|
273
322
|
detect_release_from_capistrano ||
|
274
323
|
detect_release_from_heroku
|
275
324
|
rescue => e
|
276
|
-
|
325
|
+
log_error("Error detecting release", e, debug: debug)
|
277
326
|
end
|
278
327
|
|
279
328
|
def excluded_exception?(incoming_exception)
|
@@ -308,7 +357,7 @@ module Sentry
|
|
308
357
|
def detect_release_from_heroku
|
309
358
|
return unless running_on_heroku?
|
310
359
|
return if ENV['CI']
|
311
|
-
|
360
|
+
log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
|
312
361
|
|
313
362
|
ENV['HEROKU_SLUG_COMMIT']
|
314
363
|
end
|
@@ -382,7 +431,21 @@ module Sentry
|
|
382
431
|
end
|
383
432
|
end
|
384
433
|
|
385
|
-
|
386
|
-
|
434
|
+
def run_post_initialization_callbacks
|
435
|
+
self.class.post_initialization_callbacks.each do |hook|
|
436
|
+
instance_eval(&hook)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# allow extensions to add their hooks to the Configuration class
|
441
|
+
def self.add_post_initialization_callback(&block)
|
442
|
+
self.post_initialization_callbacks << block
|
443
|
+
end
|
444
|
+
|
445
|
+
protected
|
446
|
+
|
447
|
+
def self.post_initialization_callbacks
|
448
|
+
@@post_initialization_callbacks
|
449
|
+
end
|
387
450
|
end
|
388
451
|
end
|