sentry-ruby-core 4.1.6 → 4.3.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 +87 -1
- data/Gemfile +1 -0
- data/README.md +29 -33
- data/lib/sentry-ruby.rb +4 -6
- data/lib/sentry/background_worker.rb +1 -0
- data/lib/sentry/breadcrumb/sentry_logger.rb +2 -1
- data/lib/sentry/breadcrumb_buffer.rb +3 -2
- data/lib/sentry/client.rb +50 -28
- data/lib/sentry/configuration.rb +62 -7
- 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 +5 -2
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?
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Sentry
|
2
|
+
class StacktraceBuilder
|
3
|
+
attr_reader :project_root, :app_dirs_pattern, :linecache, :context_lines, :backtrace_cleanup_callback
|
4
|
+
|
5
|
+
def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
|
6
|
+
@project_root = project_root
|
7
|
+
@app_dirs_pattern = app_dirs_pattern
|
8
|
+
@linecache = linecache
|
9
|
+
@context_lines = context_lines
|
10
|
+
@backtrace_cleanup_callback = backtrace_cleanup_callback
|
11
|
+
end
|
12
|
+
|
13
|
+
# you can pass a block to customize/exclude frames:
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# builder.build(backtrace) do |frame|
|
17
|
+
# if frame.module.match?(/a_gem/)
|
18
|
+
# nil
|
19
|
+
# else
|
20
|
+
# frame
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# ```
|
24
|
+
def build(backtrace:, &frame_callback)
|
25
|
+
parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
|
26
|
+
|
27
|
+
frames = parsed_lines.reverse.map do |line|
|
28
|
+
frame = convert_parsed_line_into_frame(line)
|
29
|
+
frame = frame_callback.call(frame) if frame_callback
|
30
|
+
frame
|
31
|
+
end.compact
|
32
|
+
|
33
|
+
StacktraceInterface.new(frames: frames)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def convert_parsed_line_into_frame(line)
|
39
|
+
frame = StacktraceInterface::Frame.new(project_root, line)
|
40
|
+
frame.set_context(linecache, context_lines) if context_lines
|
41
|
+
frame
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_backtrace_lines(backtrace)
|
45
|
+
Backtrace.parse(
|
46
|
+
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
|
47
|
+
).lines
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Sentry
|
2
|
+
class ThreadsInterface
|
3
|
+
def initialize(crashed: false, stacktrace: nil)
|
4
|
+
@id = Thread.current.object_id
|
5
|
+
@name = Thread.current.name
|
6
|
+
@current = true
|
7
|
+
@crashed = crashed
|
8
|
+
@stacktrace = stacktrace
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
{
|
13
|
+
values: [
|
14
|
+
{
|
15
|
+
id: @id,
|
16
|
+
name: @name,
|
17
|
+
crashed: @crashed,
|
18
|
+
current: @current,
|
19
|
+
stacktrace: @stacktrace&.to_hash
|
20
|
+
}
|
21
|
+
]
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# patch this method if you want to change a threads interface's stacktrace frames
|
26
|
+
# also see `StacktraceBuilder.build`.
|
27
|
+
def self.build(backtrace:, stacktrace_builder:, **options)
|
28
|
+
stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
|
29
|
+
new(**options, stacktrace: stacktrace)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -16,30 +16,24 @@ module Sentry
|
|
16
16
|
scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
|
17
17
|
scope.set_rack_env(env)
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
Sentry::Transaction.from_sentry_trace(sentry_trace, name: scope.transaction_name, op: transaction_op)
|
22
|
-
else
|
23
|
-
Sentry.start_transaction(name: scope.transaction_name, op: transaction_op)
|
24
|
-
end
|
25
|
-
|
26
|
-
scope.set_span(span)
|
19
|
+
transaction = start_transaction(env, scope)
|
20
|
+
scope.set_span(transaction) if transaction
|
27
21
|
|
28
22
|
begin
|
29
23
|
response = @app.call(env)
|
30
24
|
rescue Sentry::Error
|
31
|
-
|
25
|
+
finish_transaction(transaction, 500)
|
32
26
|
raise # Don't capture Sentry errors
|
33
27
|
rescue Exception => e
|
34
28
|
capture_exception(e)
|
35
|
-
|
29
|
+
finish_transaction(transaction, 500)
|
36
30
|
raise
|
37
31
|
end
|
38
32
|
|
39
33
|
exception = collect_exception(env)
|
40
34
|
capture_exception(exception) if exception
|
41
35
|
|
42
|
-
|
36
|
+
finish_transaction(transaction, response[0])
|
43
37
|
|
44
38
|
response
|
45
39
|
end
|
@@ -59,9 +53,19 @@ module Sentry
|
|
59
53
|
Sentry.capture_exception(exception)
|
60
54
|
end
|
61
55
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
56
|
+
def start_transaction(env, scope)
|
57
|
+
sentry_trace = env["HTTP_SENTRY_TRACE"]
|
58
|
+
options = { name: scope.transaction_name, op: transaction_op }
|
59
|
+
transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
|
60
|
+
Sentry.start_transaction(transaction: transaction, **options)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def finish_transaction(transaction, status_code)
|
65
|
+
return unless transaction
|
66
|
+
|
67
|
+
transaction.set_http_status(status_code)
|
68
|
+
transaction.finish
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|