sentry-ruby-core 4.2.1 → 4.4.0.pre.beta.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.craft.yml +4 -3
- data/CHANGELOG.md +116 -0
- data/Gemfile +4 -0
- data/README.md +39 -55
- data/lib/sentry-ruby.rb +9 -9
- data/lib/sentry/background_worker.rb +8 -4
- data/lib/sentry/breadcrumb/sentry_logger.rb +2 -1
- data/lib/sentry/breadcrumb_buffer.rb +3 -2
- data/lib/sentry/client.rb +50 -27
- data/lib/sentry/configuration.rb +27 -11
- data/lib/sentry/event.rb +28 -47
- data/lib/sentry/exceptions.rb +7 -0
- data/lib/sentry/hub.rb +17 -3
- 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 +9 -3
- data/lib/sentry/net/http.rb +87 -0
- data/lib/sentry/rack/capture_exceptions.rb +18 -11
- data/lib/sentry/scope.rb +12 -6
- data/lib/sentry/span.rb +21 -4
- data/lib/sentry/transaction.rb +53 -42
- data/lib/sentry/transaction_event.rb +3 -1
- data/lib/sentry/transport.rb +57 -26
- 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 +1 -1
- data/lib/sentry/version.rb +1 -1
- metadata +9 -5
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 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 = {})
|
@@ -85,16 +73,51 @@ module Sentry
|
|
85
73
|
|
86
74
|
def send_event(event, hint = nil)
|
87
75
|
event_type = event.is_a?(Event) ? event.type : event["type"]
|
88
|
-
event = configuration.before_send.call(event, hint) if configuration.before_send && event_type == "event"
|
89
76
|
|
90
|
-
if
|
91
|
-
configuration.
|
92
|
-
|
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
|
93
84
|
end
|
94
85
|
|
95
86
|
transport.send_event(event)
|
96
87
|
|
97
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
|
+
private
|
99
|
+
|
100
|
+
def dispatch_background_event(event, hint)
|
101
|
+
Sentry.background_worker.perform do
|
102
|
+
send_event(event, hint)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def dispatch_async_event(async_block, event, hint)
|
107
|
+
# We have to convert to a JSON-like hash, because background job
|
108
|
+
# processors (esp ActiveJob) may not like weird types in the event hash
|
109
|
+
event_hash = event.to_json_compatible
|
110
|
+
|
111
|
+
if async_block.arity == 2
|
112
|
+
hint = JSON.parse(JSON.generate(hint))
|
113
|
+
async_block.call(event_hash, hint)
|
114
|
+
else
|
115
|
+
async_block.call(event_hash)
|
116
|
+
end
|
117
|
+
rescue => e
|
118
|
+
loggable_event_type = event_hash["type"] || "event"
|
119
|
+
log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
|
120
|
+
send_event(event, hint)
|
98
121
|
end
|
99
122
|
end
|
100
123
|
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)/
|
@@ -61,12 +63,19 @@ module Sentry
|
|
61
63
|
# - :active_support_logger
|
62
64
|
attr_reader :breadcrumbs_logger
|
63
65
|
|
66
|
+
# Max number of breadcrumbs a breadcrumb buffer can hold
|
67
|
+
attr_accessor :max_breadcrumbs
|
68
|
+
|
64
69
|
# Number of lines of code context to capture, or nil for none
|
65
70
|
attr_accessor :context_lines
|
66
71
|
|
67
72
|
# RACK_ENV by default.
|
68
73
|
attr_reader :environment
|
69
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
|
+
|
70
79
|
# the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
|
71
80
|
attr_reader :dsn
|
72
81
|
|
@@ -163,14 +172,14 @@ module Sentry
|
|
163
172
|
LOG_PREFIX = "** [Sentry] ".freeze
|
164
173
|
MODULE_SEPARATOR = "::".freeze
|
165
174
|
|
166
|
-
AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
|
167
|
-
|
168
175
|
# Post initialization callbacks are called at the end of initialization process
|
169
176
|
# allowing extending the configuration of sentry-ruby by multiple extensions
|
170
177
|
@@post_initialization_callbacks = []
|
171
178
|
|
172
179
|
def initialize
|
180
|
+
self.debug = false
|
173
181
|
self.background_worker_threads = Concurrent.processor_count
|
182
|
+
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
|
174
183
|
self.breadcrumbs_logger = []
|
175
184
|
self.context_lines = 3
|
176
185
|
self.environment = environment_from_env
|
@@ -195,6 +204,7 @@ module Sentry
|
|
195
204
|
|
196
205
|
@transport = Transport::Configuration.new
|
197
206
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
207
|
+
|
198
208
|
run_post_initialization_callbacks
|
199
209
|
end
|
200
210
|
|
@@ -220,10 +230,6 @@ module Sentry
|
|
220
230
|
if logger.is_a?(Array)
|
221
231
|
logger
|
222
232
|
else
|
223
|
-
unless AVAILABLE_BREADCRUMBS_LOGGERS.include?(logger)
|
224
|
-
raise Sentry::Error, "Unsupported breadcrumbs logger. Supported loggers: #{AVAILABLE_BREADCRUMBS_LOGGERS}"
|
225
|
-
end
|
226
|
-
|
227
233
|
Array(logger)
|
228
234
|
end
|
229
235
|
|
@@ -272,10 +278,10 @@ module Sentry
|
|
272
278
|
def exception_class_allowed?(exc)
|
273
279
|
if exc.is_a?(Sentry::Error)
|
274
280
|
# Try to prevent error reporting loops
|
275
|
-
|
281
|
+
log_debug("Refusing to capture Sentry error: #{exc.inspect}")
|
276
282
|
false
|
277
283
|
elsif excluded_exception?(exc)
|
278
|
-
|
284
|
+
log_debug("User excluded error: #{exc.inspect}")
|
279
285
|
false
|
280
286
|
else
|
281
287
|
true
|
@@ -287,7 +293,17 @@ module Sentry
|
|
287
293
|
end
|
288
294
|
|
289
295
|
def tracing_enabled?
|
290
|
-
!!((@traces_sample_rate && @traces_sample_rate
|
296
|
+
!!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler)
|
297
|
+
end
|
298
|
+
|
299
|
+
def stacktrace_builder
|
300
|
+
@stacktrace_builder ||= StacktraceBuilder.new(
|
301
|
+
project_root: @project_root.to_s,
|
302
|
+
app_dirs_pattern: @app_dirs_pattern,
|
303
|
+
linecache: @linecache,
|
304
|
+
context_lines: @context_lines,
|
305
|
+
backtrace_cleanup_callback: @backtrace_cleanup_callback
|
306
|
+
)
|
291
307
|
end
|
292
308
|
|
293
309
|
private
|
@@ -298,7 +314,7 @@ module Sentry
|
|
298
314
|
detect_release_from_capistrano ||
|
299
315
|
detect_release_from_heroku
|
300
316
|
rescue => e
|
301
|
-
|
317
|
+
log_error("Error detecting release", e, debug: debug)
|
302
318
|
end
|
303
319
|
|
304
320
|
def excluded_exception?(incoming_exception)
|
@@ -333,7 +349,7 @@ module Sentry
|
|
333
349
|
def detect_release_from_heroku
|
334
350
|
return unless running_on_heroku?
|
335
351
|
return if ENV['CI']
|
336
|
-
|
352
|
+
log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
|
337
353
|
|
338
354
|
ENV['HEROKU_SLUG_COMMIT']
|
339
355
|
end
|
data/lib/sentry/event.rb
CHANGED
@@ -20,7 +20,7 @@ module Sentry
|
|
20
20
|
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
|
21
21
|
|
22
22
|
attr_accessor(*ATTRIBUTES)
|
23
|
-
attr_reader :configuration, :request, :exception, :
|
23
|
+
attr_reader :configuration, :request, :exception, :threads
|
24
24
|
|
25
25
|
def initialize(configuration:, integration_meta: nil, message: nil)
|
26
26
|
# this needs to go first because some setters rely on configuration
|
@@ -52,19 +52,26 @@ module Sentry
|
|
52
52
|
class << self
|
53
53
|
def get_log_message(event_hash)
|
54
54
|
message = event_hash[:message] || event_hash['message']
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
|
56
|
+
return message unless message.nil? || message.empty?
|
57
|
+
|
58
|
+
message = get_message_from_exception(event_hash)
|
59
|
+
|
60
|
+
return message unless message.nil? || message.empty?
|
61
|
+
|
62
|
+
message = event_hash[:transaction] || event_hash["transaction"]
|
63
|
+
|
64
|
+
return message unless message.nil? || message.empty?
|
65
|
+
|
66
|
+
'<no message value>'
|
58
67
|
end
|
59
68
|
|
60
69
|
def get_message_from_exception(event_hash)
|
61
|
-
(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
"#{event_hash[:exception][:values][0][:type]}: #{event_hash[:exception][:values][0][:value]}"
|
67
|
-
)
|
70
|
+
if exception = event_hash.dig(:exception, :values, 0)
|
71
|
+
"#{exception[:type]}: #{exception[:value]}"
|
72
|
+
elsif exception = event_hash.dig("exception", "values", 0)
|
73
|
+
"#{exception["type"]}: #{exception["value"]}"
|
74
|
+
end
|
68
75
|
end
|
69
76
|
end
|
70
77
|
|
@@ -93,13 +100,11 @@ module Sentry
|
|
93
100
|
end
|
94
101
|
|
95
102
|
def type
|
96
|
-
"event"
|
97
103
|
end
|
98
104
|
|
99
105
|
def to_hash
|
100
106
|
data = serialize_attributes
|
101
107
|
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
|
102
|
-
data[:stacktrace] = stacktrace.to_hash if stacktrace
|
103
108
|
data[:request] = request.to_hash if request
|
104
109
|
data[:exception] = exception.to_hash if exception
|
105
110
|
data[:threads] = threads.to_hash if threads
|
@@ -112,47 +117,23 @@ module Sentry
|
|
112
117
|
end
|
113
118
|
|
114
119
|
def add_request_interface(env)
|
115
|
-
@request = Sentry::RequestInterface.
|
120
|
+
@request = Sentry::RequestInterface.build(env: env)
|
116
121
|
end
|
117
122
|
|
118
123
|
def add_threads_interface(backtrace: nil, **options)
|
119
|
-
@threads = ThreadsInterface.
|
120
|
-
|
124
|
+
@threads = ThreadsInterface.build(
|
125
|
+
backtrace: backtrace,
|
126
|
+
stacktrace_builder: configuration.stacktrace_builder,
|
127
|
+
**options
|
128
|
+
)
|
121
129
|
end
|
122
130
|
|
123
|
-
def add_exception_interface(
|
124
|
-
if
|
125
|
-
@extra.merge!(
|
131
|
+
def add_exception_interface(exception)
|
132
|
+
if exception.respond_to?(:sentry_context)
|
133
|
+
@extra.merge!(exception.sentry_context)
|
126
134
|
end
|
127
135
|
|
128
|
-
@exception = Sentry::ExceptionInterface.
|
129
|
-
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
|
130
|
-
backtraces = Set.new
|
131
|
-
exc_int.values = exceptions.map do |e|
|
132
|
-
SingleExceptionInterface.new.tap do |int|
|
133
|
-
int.type = e.class.to_s
|
134
|
-
int.value = e.message.byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
|
135
|
-
int.module = e.class.to_s.split('::')[0...-1].join('::')
|
136
|
-
|
137
|
-
int.stacktrace =
|
138
|
-
if e.backtrace && !backtraces.include?(e.backtrace.object_id)
|
139
|
-
backtraces << e.backtrace.object_id
|
140
|
-
initialize_stacktrace_interface(e.backtrace)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def initialize_stacktrace_interface(backtrace)
|
148
|
-
StacktraceInterface.new(
|
149
|
-
backtrace: backtrace,
|
150
|
-
project_root: configuration.project_root.to_s,
|
151
|
-
app_dirs_pattern: configuration.app_dirs_pattern,
|
152
|
-
linecache: configuration.linecache,
|
153
|
-
context_lines: configuration.context_lines,
|
154
|
-
backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
|
155
|
-
)
|
136
|
+
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: configuration.stacktrace_builder)
|
156
137
|
end
|
157
138
|
|
158
139
|
private
|
data/lib/sentry/hub.rb
CHANGED
@@ -21,6 +21,10 @@ module Sentry
|
|
21
21
|
current_layer&.client
|
22
22
|
end
|
23
23
|
|
24
|
+
def configuration
|
25
|
+
current_client.configuration
|
26
|
+
end
|
27
|
+
|
24
28
|
def current_scope
|
25
29
|
current_layer&.scope
|
26
30
|
end
|
@@ -69,9 +73,19 @@ module Sentry
|
|
69
73
|
@stack.pop
|
70
74
|
end
|
71
75
|
|
72
|
-
def start_transaction(transaction: nil, **options)
|
73
|
-
|
74
|
-
|
76
|
+
def start_transaction(transaction: nil, custom_sampling_context: {}, **options)
|
77
|
+
return unless configuration.tracing_enabled?
|
78
|
+
|
79
|
+
transaction ||= Transaction.new(**options.merge(hub: self))
|
80
|
+
|
81
|
+
sampling_context = {
|
82
|
+
transaction_context: transaction.to_hash,
|
83
|
+
parent_sampled: transaction.parent_sampled
|
84
|
+
}
|
85
|
+
|
86
|
+
sampling_context.merge!(custom_sampling_context)
|
87
|
+
|
88
|
+
transaction.set_initial_sample_decision(sampling_context: sampling_context)
|
75
89
|
transaction
|
76
90
|
end
|
77
91
|
|
@@ -1,11 +1,29 @@
|
|
1
1
|
module Sentry
|
2
2
|
class ExceptionInterface < Interface
|
3
|
-
|
3
|
+
def initialize(values:)
|
4
|
+
@values = values
|
5
|
+
end
|
4
6
|
|
5
7
|
def to_hash
|
6
8
|
data = super
|
7
9
|
data[:values] = data[:values].map(&:to_hash) if data[:values]
|
8
10
|
data
|
9
11
|
end
|
12
|
+
|
13
|
+
def self.build(exception:, stacktrace_builder:)
|
14
|
+
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
|
15
|
+
processed_backtrace_ids = Set.new
|
16
|
+
|
17
|
+
exceptions = exceptions.map do |e|
|
18
|
+
if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
|
19
|
+
processed_backtrace_ids << e.backtrace.object_id
|
20
|
+
SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
|
21
|
+
else
|
22
|
+
SingleExceptionInterface.new(exception: exception)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
new(values: exceptions)
|
27
|
+
end
|
10
28
|
end
|
11
29
|
end
|
@@ -17,10 +17,10 @@ module Sentry
|
|
17
17
|
|
18
18
|
attr_accessor :url, :method, :data, :query_string, :cookies, :headers, :env
|
19
19
|
|
20
|
-
def self.
|
20
|
+
def self.build(env:)
|
21
21
|
env = clean_env(env)
|
22
|
-
|
23
|
-
self.new(
|
22
|
+
request = ::Rack::Request.new(env)
|
23
|
+
self.new(request: request)
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.clean_env(env)
|
@@ -34,17 +34,17 @@ module Sentry
|
|
34
34
|
env
|
35
35
|
end
|
36
36
|
|
37
|
-
def initialize(
|
38
|
-
env =
|
37
|
+
def initialize(request:)
|
38
|
+
env = request.env
|
39
39
|
|
40
40
|
if Sentry.configuration.send_default_pii
|
41
|
-
self.data = read_data_from(
|
42
|
-
self.cookies =
|
41
|
+
self.data = read_data_from(request)
|
42
|
+
self.cookies = request.cookies
|
43
|
+
self.query_string = request.query_string
|
43
44
|
end
|
44
45
|
|
45
|
-
self.url =
|
46
|
-
self.method =
|
47
|
-
self.query_string = req.query_string
|
46
|
+
self.url = request.scheme && request.url.split('?').first
|
47
|
+
self.method = request.request_method
|
48
48
|
|
49
49
|
self.headers = filter_and_format_headers(env)
|
50
50
|
self.env = filter_and_format_env(env)
|