sentry-ruby-core 4.2.2 → 4.4.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 +111 -0
- data/Gemfile +4 -0
- data/README.md +20 -22
- 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 -48
- 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 -5
- 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/version.rb +1 -1
- metadata +7 -3
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) && sending_allowed?
|
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,48 +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
|
-
int.thread_id = Thread.current.object_id
|
137
|
-
|
138
|
-
int.stacktrace =
|
139
|
-
if e.backtrace && !backtraces.include?(e.backtrace.object_id)
|
140
|
-
backtraces << e.backtrace.object_id
|
141
|
-
initialize_stacktrace_interface(e.backtrace)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def initialize_stacktrace_interface(backtrace)
|
149
|
-
StacktraceInterface.new(
|
150
|
-
backtrace: backtrace,
|
151
|
-
project_root: configuration.project_root.to_s,
|
152
|
-
app_dirs_pattern: configuration.app_dirs_pattern,
|
153
|
-
linecache: configuration.linecache,
|
154
|
-
context_lines: configuration.context_lines,
|
155
|
-
backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
|
156
|
-
)
|
136
|
+
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: configuration.stacktrace_builder)
|
157
137
|
end
|
158
138
|
|
159
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)
|
@@ -1,15 +1,26 @@
|
|
1
1
|
module Sentry
|
2
2
|
class SingleExceptionInterface < Interface
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
attr_reader :type, :value, :module, :thread_id, :stacktrace
|
4
|
+
|
5
|
+
def initialize(exception:, stacktrace: nil)
|
6
|
+
@type = exception.class.to_s
|
7
|
+
@value = (exception.message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
|
8
|
+
@module = exception.class.to_s.split('::')[0...-1].join('::')
|
9
|
+
@thread_id = Thread.current.object_id
|
10
|
+
@stacktrace = stacktrace
|
11
|
+
end
|
8
12
|
|
9
13
|
def to_hash
|
10
14
|
data = super
|
11
15
|
data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
|
12
16
|
data
|
13
17
|
end
|
18
|
+
|
19
|
+
# patch this method if you want to change an exception's stacktrace frames
|
20
|
+
# also see `StacktraceBuilder.build`.
|
21
|
+
def self.build_with_stacktrace(exception:, stacktrace_builder:)
|
22
|
+
stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
|
23
|
+
new(exception: exception, stacktrace: stacktrace)
|
24
|
+
end
|
14
25
|
end
|
15
26
|
end
|
@@ -2,18 +2,8 @@ module Sentry
|
|
2
2
|
class StacktraceInterface
|
3
3
|
attr_reader :frames
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@frames = []
|
8
|
-
|
9
|
-
parsed_backtrace_lines = Backtrace.parse(
|
10
|
-
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
|
11
|
-
).lines
|
12
|
-
|
13
|
-
parsed_backtrace_lines.reverse.each_with_object(@frames) do |line, frames|
|
14
|
-
frame = convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
|
15
|
-
frames << frame if frame.filename
|
16
|
-
end
|
5
|
+
def initialize(frames:)
|
6
|
+
@frames = frames
|
17
7
|
end
|
18
8
|
|
19
9
|
def to_hash
|
@@ -22,30 +12,24 @@ module Sentry
|
|
22
12
|
|
23
13
|
private
|
24
14
|
|
25
|
-
def convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
|
26
|
-
frame = StacktraceInterface::Frame.new(@project_root, line)
|
27
|
-
frame.set_context(linecache, context_lines) if context_lines
|
28
|
-
frame
|
29
|
-
end
|
30
|
-
|
31
15
|
# Not actually an interface, but I want to use the same style
|
32
16
|
class Frame < Interface
|
33
|
-
attr_accessor :abs_path, :context_line, :function, :in_app,
|
34
|
-
|
17
|
+
attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
|
18
|
+
:lineno, :module, :pre_context, :post_context, :vars
|
35
19
|
|
36
20
|
def initialize(project_root, line)
|
37
21
|
@project_root = project_root
|
38
22
|
|
39
|
-
@abs_path = line.file
|
23
|
+
@abs_path = line.file
|
40
24
|
@function = line.method if line.method
|
41
25
|
@lineno = line.number
|
42
26
|
@in_app = line.in_app
|
43
27
|
@module = line.module_name if line.module_name
|
28
|
+
@filename = compute_filename
|
44
29
|
end
|
45
30
|
|
46
|
-
def
|
31
|
+
def compute_filename
|
47
32
|
return if abs_path.nil?
|
48
|
-
return @filename if instance_variable_defined?(:@filename)
|
49
33
|
|
50
34
|
prefix =
|
51
35
|
if under_project_root? && in_app
|
@@ -56,19 +40,18 @@ module Sentry
|
|
56
40
|
longest_load_path
|
57
41
|
end
|
58
42
|
|
59
|
-
|
43
|
+
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
60
44
|
end
|
61
45
|
|
62
46
|
def set_context(linecache, context_lines)
|
63
47
|
return unless abs_path
|
64
48
|
|
65
|
-
|
49
|
+
@pre_context, @context_line, @post_context = \
|
66
50
|
linecache.get_file_context(abs_path, lineno, context_lines)
|
67
51
|
end
|
68
52
|
|
69
53
|
def to_hash(*args)
|
70
54
|
data = super(*args)
|
71
|
-
data[:filename] = filename
|
72
55
|
data.delete(:vars) unless vars && !vars.empty?
|
73
56
|
data.delete(:pre_context) unless pre_context && !pre_context.empty?
|
74
57
|
data.delete(:post_context) unless post_context && !post_context.empty?
|