sentry-ruby 0.2.0 → 4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|