sentry-ruby 0.2.0 → 4.1.1
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/CHANGELOG.md +70 -0
- data/Gemfile +1 -1
- data/README.md +88 -8
- data/lib/sentry-ruby.rb +36 -12
- data/lib/sentry/background_worker.rb +37 -0
- data/lib/sentry/breadcrumb.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +10 -12
- data/lib/sentry/breadcrumb_buffer.rb +2 -5
- data/lib/sentry/client.rb +14 -8
- data/lib/sentry/configuration.rb +25 -38
- data/lib/sentry/event.rb +10 -6
- data/lib/sentry/hub.rb +7 -2
- data/lib/sentry/interfaces/request.rb +1 -31
- data/lib/sentry/rack.rb +3 -3
- data/lib/sentry/rack/{capture_exception.rb → capture_exceptions.rb} +20 -11
- data/lib/sentry/rack/deprecations.rb +19 -0
- data/lib/sentry/rack/interface.rb +22 -0
- data/lib/sentry/rake.rb +17 -0
- data/lib/sentry/scope.rb +5 -5
- data/lib/sentry/span.rb +12 -26
- data/lib/sentry/transaction.rb +44 -0
- data/lib/sentry/transport.rb +9 -19
- data/lib/sentry/transport/http_transport.rb +3 -6
- data/lib/sentry/utils/request_id.rb +16 -0
- data/lib/sentry/version.rb +1 -1
- data/sentry-ruby.gemspec +1 -0
- metadata +28 -5
- data/lib/sentry/rack/tracing.rb +0 -39
- data/lib/sentry/transport/state.rb +0 -40
@@ -10,11 +10,8 @@ module Sentry
|
|
10
10
|
@buffer = Array.new(size)
|
11
11
|
end
|
12
12
|
|
13
|
-
def record(crumb
|
14
|
-
if block_given?
|
15
|
-
crumb = Breadcrumb.new if crumb.nil?
|
16
|
-
yield(crumb)
|
17
|
-
end
|
13
|
+
def record(crumb)
|
14
|
+
yield(crumb) if block_given?
|
18
15
|
@buffer.slice!(0)
|
19
16
|
@buffer << crumb
|
20
17
|
end
|
data/lib/sentry/client.rb
CHANGED
@@ -20,8 +20,10 @@ module Sentry
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def capture_event(event, scope)
|
24
|
-
|
23
|
+
def capture_event(event, scope, hint = {})
|
24
|
+
return unless configuration.sending_allowed?
|
25
|
+
|
26
|
+
scope.apply_to_event(event, hint)
|
25
27
|
|
26
28
|
if configuration.async?
|
27
29
|
begin
|
@@ -30,10 +32,16 @@ module Sentry
|
|
30
32
|
configuration.async.call(event.to_json_compatible)
|
31
33
|
rescue => e
|
32
34
|
configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
|
33
|
-
send_event(event)
|
35
|
+
send_event(event, hint)
|
34
36
|
end
|
35
37
|
else
|
36
|
-
|
38
|
+
if hint.fetch(:background, true)
|
39
|
+
Sentry.background_worker.perform do
|
40
|
+
send_event(event, hint)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
send_event(event, hint)
|
44
|
+
end
|
37
45
|
end
|
38
46
|
|
39
47
|
event
|
@@ -63,10 +71,8 @@ module Sentry
|
|
63
71
|
end
|
64
72
|
end
|
65
73
|
|
66
|
-
def send_event(event)
|
67
|
-
|
68
|
-
|
69
|
-
event = configuration.before_send.call(event) if configuration.before_send
|
74
|
+
def send_event(event, hint = nil)
|
75
|
+
event = configuration.before_send.call(event, hint) if configuration.before_send
|
70
76
|
if event.nil?
|
71
77
|
configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
|
72
78
|
return
|
data/lib/sentry/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "concurrent/utility/processor_counter"
|
2
|
+
|
1
3
|
require "sentry/utils/exception_cause_chain"
|
2
4
|
require "sentry/dsn"
|
3
5
|
require "sentry/transport/configuration"
|
@@ -15,6 +17,15 @@ module Sentry
|
|
15
17
|
attr_reader :async
|
16
18
|
alias async? async
|
17
19
|
|
20
|
+
# to send events in a non-blocking way, sentry-ruby has its own background worker
|
21
|
+
# by default, the worker holds a thread pool that has [the number of processors] threads
|
22
|
+
# but you can configure it with this configuration option
|
23
|
+
# E.g.: config.background_worker_threads = 5
|
24
|
+
#
|
25
|
+
# if you want to send events synchronously, set the value to 0
|
26
|
+
# E.g.: config.background_worker_threads = 0
|
27
|
+
attr_accessor :background_worker_threads
|
28
|
+
|
18
29
|
# a proc/lambda that takes an array of stack traces
|
19
30
|
# it'll be used to silence (reduce) backtrace of the exception
|
20
31
|
#
|
@@ -46,13 +57,13 @@ module Sentry
|
|
46
57
|
attr_accessor :context_lines
|
47
58
|
|
48
59
|
# RACK_ENV by default.
|
49
|
-
attr_reader :
|
60
|
+
attr_reader :environment
|
50
61
|
|
51
62
|
# the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
|
52
63
|
attr_reader :dsn
|
53
64
|
|
54
|
-
# Whitelist of
|
55
|
-
attr_accessor :
|
65
|
+
# Whitelist of enabled_environments that will send notifications to Sentry. Array of Strings.
|
66
|
+
attr_accessor :enabled_environments
|
56
67
|
|
57
68
|
# Logger 'progname's to exclude from breadcrumbs
|
58
69
|
attr_accessor :exclude_loggers
|
@@ -101,12 +112,6 @@ module Sentry
|
|
101
112
|
|
102
113
|
attr_accessor :server_name
|
103
114
|
|
104
|
-
# Provide a configurable callback to determine event capture.
|
105
|
-
# Note that the object passed into the block will be a String (messages) or
|
106
|
-
# an exception.
|
107
|
-
# e.g. lambda { |exc_or_msg| exc_or_msg.some_attr == false }
|
108
|
-
attr_reader :should_capture
|
109
|
-
|
110
115
|
# Return a Transport::Configuration object for transport-related configurations.
|
111
116
|
attr_reader :transport
|
112
117
|
|
@@ -152,10 +157,11 @@ module Sentry
|
|
152
157
|
|
153
158
|
def initialize
|
154
159
|
self.async = false
|
160
|
+
self.background_worker_threads = Concurrent.processor_count
|
155
161
|
self.breadcrumbs_logger = []
|
156
162
|
self.context_lines = 3
|
157
|
-
self.
|
158
|
-
self.
|
163
|
+
self.environment = environment_from_env
|
164
|
+
self.enabled_environments = []
|
159
165
|
self.exclude_loggers = []
|
160
166
|
self.excluded_exceptions = IGNORE_DEFAULT.dup
|
161
167
|
self.inspect_exception_causes_for_exclusion = false
|
@@ -169,7 +175,6 @@ module Sentry
|
|
169
175
|
self.send_default_pii = false
|
170
176
|
self.dsn = ENV['SENTRY_DSN']
|
171
177
|
self.server_name = server_name_from_env
|
172
|
-
self.should_capture = false
|
173
178
|
|
174
179
|
self.before_send = false
|
175
180
|
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
|
@@ -213,14 +218,6 @@ module Sentry
|
|
213
218
|
@breadcrumbs_logger = logger
|
214
219
|
end
|
215
220
|
|
216
|
-
def should_capture=(value)
|
217
|
-
unless value == false || value.respond_to?(:call)
|
218
|
-
raise ArgumentError, "should_capture must be callable (or false to disable)"
|
219
|
-
end
|
220
|
-
|
221
|
-
@should_capture = value
|
222
|
-
end
|
223
|
-
|
224
221
|
def before_send=(value)
|
225
222
|
unless value == false || value.respond_to?(:call)
|
226
223
|
raise ArgumentError, "before_send must be callable (or false to disable)"
|
@@ -229,20 +226,17 @@ module Sentry
|
|
229
226
|
@before_send = value
|
230
227
|
end
|
231
228
|
|
232
|
-
def
|
233
|
-
@
|
229
|
+
def environment=(environment)
|
230
|
+
@environment = environment.to_s
|
234
231
|
end
|
235
232
|
|
236
|
-
def
|
233
|
+
def sending_allowed?
|
237
234
|
@errors = []
|
238
235
|
|
239
236
|
valid? &&
|
240
|
-
|
241
|
-
capture_allowed_by_callback?(message_or_exc) &&
|
237
|
+
capture_in_environment? &&
|
242
238
|
sample_allowed?
|
243
239
|
end
|
244
|
-
# If we cannot capture, we cannot send.
|
245
|
-
alias sending_allowed? capture_allowed?
|
246
240
|
|
247
241
|
def error_messages
|
248
242
|
@errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
|
@@ -267,7 +261,7 @@ module Sentry
|
|
267
261
|
end
|
268
262
|
|
269
263
|
def enabled_in_current_env?
|
270
|
-
|
264
|
+
enabled_environments.empty? || enabled_environments.include?(environment)
|
271
265
|
end
|
272
266
|
|
273
267
|
def tracing_enabled?
|
@@ -353,17 +347,10 @@ module Sentry
|
|
353
347
|
ENV['SENTRY_RELEASE']
|
354
348
|
end
|
355
349
|
|
356
|
-
def
|
350
|
+
def capture_in_environment?
|
357
351
|
return true if enabled_in_current_env?
|
358
352
|
|
359
|
-
@errors << "Not configured to send/capture in environment '#{
|
360
|
-
false
|
361
|
-
end
|
362
|
-
|
363
|
-
def capture_allowed_by_callback?(message_or_exc)
|
364
|
-
return true if !should_capture || message_or_exc.nil? || should_capture.call(message_or_exc)
|
365
|
-
|
366
|
-
@errors << "should_capture returned false"
|
353
|
+
@errors << "Not configured to send/capture in environment '#{environment}'"
|
367
354
|
false
|
368
355
|
end
|
369
356
|
|
@@ -394,7 +381,7 @@ module Sentry
|
|
394
381
|
Socket.gethostbyname(hostname).first rescue server_name
|
395
382
|
end
|
396
383
|
|
397
|
-
def
|
384
|
+
def environment_from_env
|
398
385
|
ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default'
|
399
386
|
end
|
400
387
|
|
data/lib/sentry/event.rb
CHANGED
@@ -5,6 +5,7 @@ require 'securerandom'
|
|
5
5
|
require 'sentry/interface'
|
6
6
|
require 'sentry/backtrace'
|
7
7
|
require 'sentry/utils/real_ip'
|
8
|
+
require 'sentry/utils/request_id'
|
8
9
|
|
9
10
|
module Sentry
|
10
11
|
class Event
|
@@ -17,7 +18,7 @@ module Sentry
|
|
17
18
|
)
|
18
19
|
|
19
20
|
attr_accessor(*ATTRIBUTES)
|
20
|
-
attr_reader :configuration
|
21
|
+
attr_reader :configuration, :request, :exception, :stacktrace
|
21
22
|
|
22
23
|
def initialize(configuration:, message: nil)
|
23
24
|
# this needs to go first because some setters rely on configuration
|
@@ -37,7 +38,7 @@ module Sentry
|
|
37
38
|
@fingerprint = []
|
38
39
|
|
39
40
|
@server_name = configuration.server_name
|
40
|
-
@environment = configuration.
|
41
|
+
@environment = configuration.environment
|
41
42
|
@release = configuration.release
|
42
43
|
@modules = configuration.gem_specs if configuration.send_modules
|
43
44
|
|
@@ -74,7 +75,7 @@ module Sentry
|
|
74
75
|
end
|
75
76
|
|
76
77
|
def rack_env=(env)
|
77
|
-
unless
|
78
|
+
unless request || env.empty?
|
78
79
|
@request = Sentry::RequestInterface.new.tap do |int|
|
79
80
|
int.from_rack(env)
|
80
81
|
end
|
@@ -82,6 +83,9 @@ module Sentry
|
|
82
83
|
if configuration.send_default_pii && ip = calculate_real_ip_from_rack(env.dup)
|
83
84
|
user[:ip_address] = ip
|
84
85
|
end
|
86
|
+
if request_id = Utils::RequestId.read_from(env)
|
87
|
+
tags[:request_id] = request_id
|
88
|
+
end
|
85
89
|
end
|
86
90
|
end
|
87
91
|
|
@@ -92,9 +96,9 @@ module Sentry
|
|
92
96
|
def to_hash
|
93
97
|
data = serialize_attributes
|
94
98
|
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
|
95
|
-
data[:stacktrace] =
|
96
|
-
data[:request] =
|
97
|
-
data[:exception] =
|
99
|
+
data[:stacktrace] = stacktrace.to_hash if stacktrace
|
100
|
+
data[:request] = request.to_hash if request
|
101
|
+
data[:exception] = exception.to_hash if exception
|
98
102
|
|
99
103
|
data
|
100
104
|
end
|
data/lib/sentry/hub.rb
CHANGED
@@ -80,12 +80,16 @@ module Sentry
|
|
80
80
|
|
81
81
|
return unless event
|
82
82
|
|
83
|
+
options[:hint] ||= {}
|
84
|
+
options[:hint] = options[:hint].merge(exception: exception)
|
83
85
|
capture_event(event, **options, &block)
|
84
86
|
end
|
85
87
|
|
86
88
|
def capture_message(message, **options, &block)
|
87
89
|
return unless current_client
|
88
90
|
|
91
|
+
options[:hint] ||= {}
|
92
|
+
options[:hint] = options[:hint].merge(message: message)
|
89
93
|
event = current_client.event_from_message(message)
|
90
94
|
capture_event(event, **options, &block)
|
91
95
|
end
|
@@ -93,6 +97,7 @@ module Sentry
|
|
93
97
|
def capture_event(event, **options, &block)
|
94
98
|
return unless current_client
|
95
99
|
|
100
|
+
hint = options.delete(:hint) || {}
|
96
101
|
scope = current_scope.dup
|
97
102
|
|
98
103
|
if block
|
@@ -103,9 +108,9 @@ module Sentry
|
|
103
108
|
scope.update_from_options(**options)
|
104
109
|
end
|
105
110
|
|
106
|
-
event = current_client.capture_event(event, scope)
|
111
|
+
event = current_client.capture_event(event, scope, hint)
|
107
112
|
|
108
|
-
@last_event_id = event
|
113
|
+
@last_event_id = event&.event_id
|
109
114
|
event
|
110
115
|
end
|
111
116
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'rack'
|
2
|
-
|
3
1
|
module Sentry
|
4
2
|
class RequestInterface < Interface
|
5
3
|
REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
|
@@ -18,36 +16,8 @@ module Sentry
|
|
18
16
|
self.cookies = nil
|
19
17
|
end
|
20
18
|
|
21
|
-
def from_rack(env_hash)
|
22
|
-
req = ::Rack::Request.new(env_hash)
|
23
|
-
|
24
|
-
if Sentry.configuration.send_default_pii
|
25
|
-
self.data = read_data_from(req)
|
26
|
-
self.cookies = req.cookies
|
27
|
-
else
|
28
|
-
# need to completely wipe out ip addresses
|
29
|
-
IP_HEADERS.each { |h| env_hash.delete(h) }
|
30
|
-
end
|
31
|
-
|
32
|
-
self.url = req.scheme && req.url.split('?').first
|
33
|
-
self.method = req.request_method
|
34
|
-
self.query_string = req.query_string
|
35
|
-
|
36
|
-
self.headers = format_headers_for_sentry(env_hash)
|
37
|
-
self.env = format_env_for_sentry(env_hash)
|
38
|
-
end
|
39
|
-
|
40
19
|
private
|
41
20
|
|
42
|
-
# Request ID based on ActionDispatch::RequestId
|
43
|
-
def read_request_id_from(env_hash)
|
44
|
-
REQUEST_ID_HEADERS.each do |key|
|
45
|
-
request_id = env_hash[key]
|
46
|
-
return request_id if request_id
|
47
|
-
end
|
48
|
-
nil
|
49
|
-
end
|
50
|
-
|
51
21
|
# See Sentry server default limits at
|
52
22
|
# https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
|
53
23
|
def read_data_from(request)
|
@@ -67,7 +37,7 @@ module Sentry
|
|
67
37
|
begin
|
68
38
|
key = key.to_s # rack env can contain symbols
|
69
39
|
value = value.to_s
|
70
|
-
next memo['X-Request-Id'] ||=
|
40
|
+
next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env_hash) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
|
71
41
|
next unless key.upcase == key # Non-upper case stuff isn't either
|
72
42
|
|
73
43
|
# Rack adds in an incorrect HTTP_VERSION key, which causes downstream
|
data/lib/sentry/rack.rb
CHANGED
@@ -1,45 +1,54 @@
|
|
1
1
|
module Sentry
|
2
2
|
module Rack
|
3
|
-
class
|
3
|
+
class CaptureExceptions
|
4
4
|
def initialize(app)
|
5
5
|
@app = app
|
6
6
|
end
|
7
7
|
|
8
8
|
def call(env)
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
Sentry.clone_hub_to_current_thread
|
13
|
-
|
14
|
-
# it's essential for multi-process servers (e.g. unicorn)
|
9
|
+
return @app.call(env) unless Sentry.initialized?
|
10
|
+
|
11
|
+
# make sure the current thread has a clean hub
|
12
|
+
Sentry.clone_hub_to_current_thread
|
13
|
+
|
15
14
|
Sentry.with_scope do |scope|
|
16
|
-
# there could be some breadcrumbs already stored in the top-level scope
|
17
|
-
# and for request information, we don't need those breadcrumbs
|
18
15
|
scope.clear_breadcrumbs
|
19
|
-
env['sentry.client'] = Sentry.get_current_client
|
20
|
-
|
21
16
|
scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
|
22
17
|
scope.set_rack_env(env)
|
23
18
|
|
19
|
+
span = Sentry.start_transaction(name: scope.transaction_name, op: "rack.request")
|
20
|
+
scope.set_span(span)
|
21
|
+
|
24
22
|
begin
|
25
23
|
response = @app.call(env)
|
26
24
|
rescue Sentry::Error
|
25
|
+
finish_span(span, 500)
|
27
26
|
raise # Don't capture Sentry errors
|
28
27
|
rescue Exception => e
|
29
28
|
Sentry.capture_exception(e)
|
29
|
+
finish_span(span, 500)
|
30
30
|
raise
|
31
31
|
end
|
32
32
|
|
33
33
|
exception = collect_exception(env)
|
34
34
|
Sentry.capture_exception(exception) if exception
|
35
35
|
|
36
|
+
finish_span(span, response[0])
|
37
|
+
|
36
38
|
response
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
42
|
+
private
|
43
|
+
|
40
44
|
def collect_exception(env)
|
41
45
|
env['rack.exception'] || env['sinatra.error']
|
42
46
|
end
|
47
|
+
|
48
|
+
def finish_span(span, status_code)
|
49
|
+
span.set_http_status(status_code)
|
50
|
+
span.finish
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
45
54
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sentry
|
2
|
+
module Rack
|
3
|
+
class DeprecatedMiddleware
|
4
|
+
def initialize(_)
|
5
|
+
raise Sentry::Error.new <<~MSG
|
6
|
+
|
7
|
+
You're seeing this message because #{self.class} has been replaced by Sentry::Rack::CaptureExceptions.
|
8
|
+
Removing this middleware from your app and upgrading sentry-rails to 4.1.0+ should solve the issue.
|
9
|
+
MSG
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Tracing < DeprecatedMiddleware
|
14
|
+
end
|
15
|
+
|
16
|
+
class CaptureException < DeprecatedMiddleware
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sentry
|
2
|
+
class RequestInterface
|
3
|
+
def from_rack(env_hash)
|
4
|
+
req = ::Rack::Request.new(env_hash)
|
5
|
+
|
6
|
+
if Sentry.configuration.send_default_pii
|
7
|
+
self.data = read_data_from(req)
|
8
|
+
self.cookies = req.cookies
|
9
|
+
else
|
10
|
+
# need to completely wipe out ip addresses
|
11
|
+
IP_HEADERS.each { |h| env_hash.delete(h) }
|
12
|
+
end
|
13
|
+
|
14
|
+
self.url = req.scheme && req.url.split('?').first
|
15
|
+
self.method = req.request_method
|
16
|
+
self.query_string = req.query_string
|
17
|
+
|
18
|
+
self.headers = format_headers_for_sentry(env_hash)
|
19
|
+
self.env = format_env_for_sentry(env_hash)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|