sentry-ruby-core 4.1.5 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -1
- data/Gemfile +3 -0
- data/Makefile +4 -0
- data/README.md +25 -29
- data/lib/sentry-ruby.rb +2 -4
- data/lib/sentry/background_worker.rb +1 -0
- data/lib/sentry/breadcrumb.rb +25 -6
- data/lib/sentry/breadcrumb/sentry_logger.rb +1 -0
- data/lib/sentry/breadcrumb_buffer.rb +4 -3
- data/lib/sentry/client.rb +50 -28
- data/lib/sentry/configuration.rb +63 -16
- data/lib/sentry/event.rb +33 -44
- data/lib/sentry/exceptions.rb +7 -0
- data/lib/sentry/hub.rb +11 -3
- 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/rack/capture_exceptions.rb +18 -14
- data/lib/sentry/scope.rb +9 -3
- data/lib/sentry/transaction.rb +16 -9
- data/lib/sentry/transport.rb +0 -12
- data/lib/sentry/transport/configuration.rb +3 -1
- data/lib/sentry/transport/http_transport.rb +18 -2
- data/lib/sentry/utils/real_ip.rb +13 -7
- data/lib/sentry/version.rb +1 -1
- metadata +6 -2
data/lib/sentry/configuration.rb
CHANGED
@@ -4,6 +4,7 @@ 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
|
@@ -38,10 +39,19 @@ module Sentry
|
|
38
39
|
#
|
39
40
|
attr_accessor :backtrace_cleanup_callback
|
40
41
|
|
42
|
+
# Optional Proc, called before adding the breadcrumb to the current scope
|
43
|
+
# E.g.: lambda { |breadcrumb, hint| breadcrumb }
|
44
|
+
# E.g.: lambda { |breadcrumb, hint| nil }
|
45
|
+
# E.g.: lambda { |breadcrumb, hint|
|
46
|
+
# breadcrumb.message = 'a'
|
47
|
+
# breadcrumb
|
48
|
+
# }
|
49
|
+
attr_reader :before_breadcrumb
|
50
|
+
|
41
51
|
# 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|
|
52
|
+
# E.g.: lambda { |event, hint| event }
|
53
|
+
# E.g.: lambda { |event, hint| nil }
|
54
|
+
# E.g.: lambda { |event, hint|
|
45
55
|
# event[:message] = 'a'
|
46
56
|
# event
|
47
57
|
# }
|
@@ -52,6 +62,9 @@ module Sentry
|
|
52
62
|
# - :active_support_logger
|
53
63
|
attr_reader :breadcrumbs_logger
|
54
64
|
|
65
|
+
# Max number of breadcrumbs a breadcrumb buffer can hold
|
66
|
+
attr_accessor :max_breadcrumbs
|
67
|
+
|
55
68
|
# Number of lines of code context to capture, or nil for none
|
56
69
|
attr_accessor :context_lines
|
57
70
|
|
@@ -109,6 +122,9 @@ module Sentry
|
|
109
122
|
# will not be sent to Sentry.
|
110
123
|
attr_accessor :send_default_pii
|
111
124
|
|
125
|
+
# IP ranges for trusted proxies that will be skipped when calculating IP address.
|
126
|
+
attr_accessor :trusted_proxies
|
127
|
+
|
112
128
|
attr_accessor :server_name
|
113
129
|
|
114
130
|
# Return a Transport::Configuration object for transport-related configurations.
|
@@ -153,23 +169,29 @@ module Sentry
|
|
153
169
|
|
154
170
|
AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
|
155
171
|
|
172
|
+
# Post initialization callbacks are called at the end of initialization process
|
173
|
+
# allowing extending the configuration of sentry-ruby by multiple extensions
|
174
|
+
@@post_initialization_callbacks = []
|
175
|
+
|
156
176
|
def initialize
|
157
177
|
self.background_worker_threads = Concurrent.processor_count
|
178
|
+
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
|
158
179
|
self.breadcrumbs_logger = []
|
159
180
|
self.context_lines = 3
|
160
181
|
self.environment = environment_from_env
|
161
182
|
self.enabled_environments = []
|
162
183
|
self.exclude_loggers = []
|
163
184
|
self.excluded_exceptions = IGNORE_DEFAULT.dup
|
164
|
-
self.inspect_exception_causes_for_exclusion =
|
185
|
+
self.inspect_exception_causes_for_exclusion = true
|
165
186
|
self.linecache = ::Sentry::LineCache.new
|
166
187
|
self.logger = ::Sentry::Logger.new(STDOUT)
|
167
|
-
self.project_root =
|
188
|
+
self.project_root = Dir.pwd
|
168
189
|
|
169
190
|
self.release = detect_release
|
170
191
|
self.sample_rate = 1.0
|
171
192
|
self.send_modules = true
|
172
193
|
self.send_default_pii = false
|
194
|
+
self.trusted_proxies = []
|
173
195
|
self.dsn = ENV['SENTRY_DSN']
|
174
196
|
self.server_name = server_name_from_env
|
175
197
|
|
@@ -178,7 +200,8 @@ module Sentry
|
|
178
200
|
|
179
201
|
@transport = Transport::Configuration.new
|
180
202
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
181
|
-
|
203
|
+
|
204
|
+
run_post_initialization_callbacks
|
182
205
|
end
|
183
206
|
|
184
207
|
def dsn=(value)
|
@@ -223,6 +246,14 @@ module Sentry
|
|
223
246
|
@before_send = value
|
224
247
|
end
|
225
248
|
|
249
|
+
def before_breadcrumb=(value)
|
250
|
+
unless value.nil? || value.respond_to?(:call)
|
251
|
+
raise ArgumentError, "before_breadcrumb must be callable (or nil to disable)"
|
252
|
+
end
|
253
|
+
|
254
|
+
@before_breadcrumb = value
|
255
|
+
end
|
256
|
+
|
226
257
|
def environment=(environment)
|
227
258
|
@environment = environment.to_s
|
228
259
|
end
|
@@ -265,16 +296,18 @@ module Sentry
|
|
265
296
|
!!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
|
266
297
|
end
|
267
298
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
+
)
|
276
307
|
end
|
277
308
|
|
309
|
+
private
|
310
|
+
|
278
311
|
def detect_release
|
279
312
|
detect_release_from_env ||
|
280
313
|
detect_release_from_git ||
|
@@ -390,7 +423,21 @@ module Sentry
|
|
390
423
|
end
|
391
424
|
end
|
392
425
|
|
393
|
-
|
394
|
-
|
426
|
+
def run_post_initialization_callbacks
|
427
|
+
self.class.post_initialization_callbacks.each do |hook|
|
428
|
+
instance_eval(&hook)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# allow extensions to add their hooks to the Configuration class
|
433
|
+
def self.add_post_initialization_callback(&block)
|
434
|
+
self.post_initialization_callbacks << block
|
435
|
+
end
|
436
|
+
|
437
|
+
protected
|
438
|
+
|
439
|
+
def self.post_initialization_callbacks
|
440
|
+
@@post_initialization_callbacks
|
441
|
+
end
|
395
442
|
end
|
396
443
|
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
|
|
@@ -99,9 +106,9 @@ module Sentry
|
|
99
106
|
def to_hash
|
100
107
|
data = serialize_attributes
|
101
108
|
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
|
102
|
-
data[:stacktrace] = stacktrace.to_hash if stacktrace
|
103
109
|
data[:request] = request.to_hash if request
|
104
110
|
data[:exception] = exception.to_hash if exception
|
111
|
+
data[:threads] = threads.to_hash if threads
|
105
112
|
|
106
113
|
data
|
107
114
|
end
|
@@ -111,42 +118,23 @@ module Sentry
|
|
111
118
|
end
|
112
119
|
|
113
120
|
def add_request_interface(env)
|
114
|
-
@request = Sentry::RequestInterface.
|
121
|
+
@request = Sentry::RequestInterface.build(env: env)
|
115
122
|
end
|
116
123
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
124
|
+
def add_threads_interface(backtrace: nil, **options)
|
125
|
+
@threads = ThreadsInterface.build(
|
126
|
+
backtrace: backtrace,
|
127
|
+
stacktrace_builder: configuration.stacktrace_builder,
|
128
|
+
**options
|
129
|
+
)
|
130
|
+
end
|
121
131
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
exc_int.values = exceptions.map do |e|
|
126
|
-
SingleExceptionInterface.new.tap do |int|
|
127
|
-
int.type = e.class.to_s
|
128
|
-
int.value = e.message.byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
|
129
|
-
int.module = e.class.to_s.split('::')[0...-1].join('::')
|
130
|
-
|
131
|
-
int.stacktrace =
|
132
|
-
if e.backtrace && !backtraces.include?(e.backtrace.object_id)
|
133
|
-
backtraces << e.backtrace.object_id
|
134
|
-
initialize_stacktrace_interface(e.backtrace)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
132
|
+
def add_exception_interface(exception)
|
133
|
+
if exception.respond_to?(:sentry_context)
|
134
|
+
@extra.merge!(exception.sentry_context)
|
138
135
|
end
|
139
|
-
end
|
140
136
|
|
141
|
-
|
142
|
-
StacktraceInterface.new(
|
143
|
-
backtrace: backtrace,
|
144
|
-
project_root: configuration.project_root.to_s,
|
145
|
-
app_dirs_pattern: configuration.app_dirs_pattern,
|
146
|
-
linecache: configuration.linecache,
|
147
|
-
context_lines: configuration.context_lines,
|
148
|
-
backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
|
149
|
-
)
|
137
|
+
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: configuration.stacktrace_builder)
|
150
138
|
end
|
151
139
|
|
152
140
|
private
|
@@ -166,7 +154,8 @@ module Sentry
|
|
166
154
|
:remote_addr => env["REMOTE_ADDR"],
|
167
155
|
:client_ip => env["HTTP_CLIENT_IP"],
|
168
156
|
:real_ip => env["HTTP_X_REAL_IP"],
|
169
|
-
:forwarded_for => env["HTTP_X_FORWARDED_FOR"]
|
157
|
+
:forwarded_for => env["HTTP_X_FORWARDED_FOR"],
|
158
|
+
:trusted_proxies => configuration.trusted_proxies
|
170
159
|
).calculate_ip
|
171
160
|
end
|
172
161
|
end
|
data/lib/sentry/hub.rb
CHANGED
@@ -69,9 +69,11 @@ module Sentry
|
|
69
69
|
@stack.pop
|
70
70
|
end
|
71
71
|
|
72
|
-
def start_transaction(transaction: nil, **options)
|
72
|
+
def start_transaction(transaction: nil, configuration: Sentry.configuration, **options)
|
73
|
+
return unless configuration.tracing_enabled?
|
74
|
+
|
73
75
|
transaction ||= Transaction.new(**options)
|
74
|
-
transaction.
|
76
|
+
transaction.set_initial_sample_decision(configuration: current_client.configuration)
|
75
77
|
transaction
|
76
78
|
end
|
77
79
|
|
@@ -120,7 +122,13 @@ module Sentry
|
|
120
122
|
event
|
121
123
|
end
|
122
124
|
|
123
|
-
def add_breadcrumb(breadcrumb)
|
125
|
+
def add_breadcrumb(breadcrumb, hint: {})
|
126
|
+
if before_breadcrumb = current_client.configuration.before_breadcrumb
|
127
|
+
breadcrumb = before_breadcrumb.call(breadcrumb, hint)
|
128
|
+
end
|
129
|
+
|
130
|
+
return unless breadcrumb
|
131
|
+
|
124
132
|
current_scope.add_breadcrumb(breadcrumb)
|
125
133
|
end
|
126
134
|
|
data/lib/sentry/interface.rb
CHANGED
@@ -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,14 +1,26 @@
|
|
1
1
|
module Sentry
|
2
2
|
class SingleExceptionInterface < Interface
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
7
12
|
|
8
13
|
def to_hash
|
9
14
|
data = super
|
10
15
|
data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
|
11
16
|
data
|
12
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
|
13
25
|
end
|
14
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?
|