sentry-ruby-core 4.1.5 → 4.3.0
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 +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?
|