sentry-ruby-core 4.4.2 → 5.0.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/.yardopts +2 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +8 -5
- data/LICENSE.txt +1 -1
- data/README.md +29 -175
- data/bin/console +5 -1
- data/lib/sentry/background_worker.rb +33 -3
- data/lib/sentry/backtrace.rb +1 -3
- data/lib/sentry/breadcrumb/sentry_logger.rb +3 -1
- data/lib/sentry/breadcrumb.rb +28 -2
- data/lib/sentry/breadcrumb_buffer.rb +16 -0
- data/lib/sentry/client.rb +65 -7
- data/lib/sentry/configuration.rb +155 -112
- data/lib/sentry/core_ext/object/deep_dup.rb +4 -0
- data/lib/sentry/core_ext/object/duplicable.rb +2 -0
- data/lib/sentry/dsn.rb +6 -1
- data/lib/sentry/envelope.rb +26 -0
- data/lib/sentry/event.rb +65 -23
- data/lib/sentry/exceptions.rb +2 -0
- data/lib/sentry/hub.rb +31 -5
- data/lib/sentry/integrable.rb +2 -0
- data/lib/sentry/interface.rb +3 -10
- data/lib/sentry/interfaces/exception.rb +13 -3
- data/lib/sentry/interfaces/request.rb +49 -19
- data/lib/sentry/interfaces/single_exception.rb +31 -0
- data/lib/sentry/interfaces/stacktrace.rb +14 -0
- data/lib/sentry/interfaces/stacktrace_builder.rb +39 -10
- data/lib/sentry/interfaces/threads.rb +12 -2
- data/lib/sentry/linecache.rb +3 -0
- data/lib/sentry/net/http.rb +71 -47
- data/lib/sentry/rack/capture_exceptions.rb +2 -0
- data/lib/sentry/rack.rb +2 -1
- data/lib/sentry/rake.rb +33 -9
- data/lib/sentry/release_detector.rb +39 -0
- data/lib/sentry/scope.rb +76 -6
- data/lib/sentry/span.rb +84 -8
- data/lib/sentry/transaction.rb +48 -10
- data/lib/sentry/transaction_event.rb +19 -6
- data/lib/sentry/transport/configuration.rb +4 -2
- data/lib/sentry/transport/dummy_transport.rb +2 -0
- data/lib/sentry/transport/http_transport.rb +57 -38
- data/lib/sentry/transport.rb +80 -19
- data/lib/sentry/utils/argument_checking_helper.rb +2 -0
- data/lib/sentry/utils/custom_inspection.rb +14 -0
- data/lib/sentry/utils/exception_cause_chain.rb +10 -10
- data/lib/sentry/utils/logging_helper.rb +6 -4
- data/lib/sentry/utils/real_ip.rb +9 -1
- data/lib/sentry/utils/request_id.rb +2 -0
- data/lib/sentry/version.rb +3 -1
- data/lib/sentry-ruby.rb +184 -49
- data/sentry-ruby-core.gemspec +2 -3
- data/sentry-ruby.gemspec +2 -3
- metadata +9 -22
- data/.craft.yml +0 -28
- data/lib/sentry/benchmarks/benchmark_transport.rb +0 -14
- data/lib/sentry/rack/deprecations.rb +0 -19
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
# @api private
|
5
|
+
class Envelope
|
6
|
+
def initialize(headers)
|
7
|
+
@headers = headers
|
8
|
+
@items = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_item(headers, payload)
|
12
|
+
@items << [headers, payload]
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
payload = @items.map do |item_headers, item_payload|
|
17
|
+
<<~ENVELOPE
|
18
|
+
#{JSON.generate(item_headers)}
|
19
|
+
#{JSON.generate(item_payload)}
|
20
|
+
ENVELOPE
|
21
|
+
end.join("\n")
|
22
|
+
|
23
|
+
"#{JSON.generate(@headers)}\n#{payload}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/sentry/event.rb
CHANGED
@@ -6,26 +6,44 @@ require 'sentry/interface'
|
|
6
6
|
require 'sentry/backtrace'
|
7
7
|
require 'sentry/utils/real_ip'
|
8
8
|
require 'sentry/utils/request_id'
|
9
|
+
require 'sentry/utils/custom_inspection'
|
9
10
|
|
10
11
|
module Sentry
|
11
12
|
class Event
|
12
|
-
|
13
|
+
# These are readable attributes.
|
14
|
+
SERIALIZEABLE_ATTRIBUTES = %i(
|
13
15
|
event_id level timestamp
|
14
16
|
release environment server_name modules
|
15
17
|
message user tags contexts extra
|
16
|
-
fingerprint breadcrumbs
|
18
|
+
fingerprint breadcrumbs transaction
|
17
19
|
platform sdk type
|
18
20
|
)
|
19
21
|
|
22
|
+
# These are writable attributes.
|
23
|
+
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
|
24
|
+
|
20
25
|
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
|
21
26
|
|
22
|
-
|
23
|
-
attr_reader :configuration, :request, :exception, :threads
|
27
|
+
SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
29
|
+
include CustomInspection
|
30
|
+
|
31
|
+
attr_writer(*WRITER_ATTRIBUTES)
|
32
|
+
attr_reader(*SERIALIZEABLE_ATTRIBUTES)
|
33
|
+
|
34
|
+
# @return [RequestInterface]
|
35
|
+
attr_reader :request
|
36
|
+
|
37
|
+
# @return [ExceptionInterface]
|
38
|
+
attr_reader :exception
|
28
39
|
|
40
|
+
# @return [ThreadsInterface]
|
41
|
+
attr_reader :threads
|
42
|
+
|
43
|
+
# @param configuration [Configuration]
|
44
|
+
# @param integration_meta [Hash, nil]
|
45
|
+
# @param message [String, nil]
|
46
|
+
def initialize(configuration:, integration_meta: nil, message: nil)
|
29
47
|
# Set some simple default values
|
30
48
|
@event_id = SecureRandom.uuid.delete("-")
|
31
49
|
@timestamp = Sentry.utc_now.iso8601
|
@@ -39,17 +57,25 @@ module Sentry
|
|
39
57
|
|
40
58
|
@fingerprint = []
|
41
59
|
|
60
|
+
# configuration data that's directly used by events
|
42
61
|
@server_name = configuration.server_name
|
43
62
|
@environment = configuration.environment
|
44
63
|
@release = configuration.release
|
45
64
|
@modules = configuration.gem_specs if configuration.send_modules
|
46
65
|
|
66
|
+
# configuration options to help events process data
|
67
|
+
@send_default_pii = configuration.send_default_pii
|
68
|
+
@trusted_proxies = configuration.trusted_proxies
|
69
|
+
@stacktrace_builder = configuration.stacktrace_builder
|
70
|
+
@rack_env_whitelist = configuration.rack_env_whitelist
|
71
|
+
|
47
72
|
@message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
|
48
73
|
|
49
74
|
self.level = :error
|
50
75
|
end
|
51
76
|
|
52
77
|
class << self
|
78
|
+
# @!visibility private
|
53
79
|
def get_log_message(event_hash)
|
54
80
|
message = event_hash[:message] || event_hash['message']
|
55
81
|
|
@@ -66,6 +92,7 @@ module Sentry
|
|
66
92
|
'<no message value>'
|
67
93
|
end
|
68
94
|
|
95
|
+
# @!visibility private
|
69
96
|
def get_message_from_exception(event_hash)
|
70
97
|
if exception = event_hash.dig(:exception, :values, 0)
|
71
98
|
"#{exception[:type]}: #{exception[:value]}"
|
@@ -75,21 +102,35 @@ module Sentry
|
|
75
102
|
end
|
76
103
|
end
|
77
104
|
|
105
|
+
# @deprecated This method will be removed in v5.0.0. Please just use Sentry.configuration
|
106
|
+
# @return [Configuration]
|
107
|
+
def configuration
|
108
|
+
Sentry.configuration
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sets the event's timestamp.
|
112
|
+
# @param time [Time, Float]
|
113
|
+
# @return [void]
|
78
114
|
def timestamp=(time)
|
79
115
|
@timestamp = time.is_a?(Time) ? time.to_f : time
|
80
116
|
end
|
81
117
|
|
82
|
-
|
83
|
-
|
118
|
+
# Sets the event's level.
|
119
|
+
# @param level [String, Symbol]
|
120
|
+
# @return [void]
|
121
|
+
def level=(level) # needed to meet the Sentry spec
|
122
|
+
@level = level.to_s == "warn" ? :warning : level
|
84
123
|
end
|
85
124
|
|
125
|
+
# Sets the event's request environment data with RequestInterface.
|
126
|
+
# @see RequestInterface
|
127
|
+
# @param env [Hash]
|
128
|
+
# @return [void]
|
86
129
|
def rack_env=(env)
|
87
130
|
unless request || env.empty?
|
88
|
-
env = env.dup
|
89
|
-
|
90
131
|
add_request_interface(env)
|
91
132
|
|
92
|
-
if
|
133
|
+
if @send_default_pii
|
93
134
|
user[:ip_address] = calculate_real_ip_from_rack(env)
|
94
135
|
end
|
95
136
|
|
@@ -99,9 +140,7 @@ module Sentry
|
|
99
140
|
end
|
100
141
|
end
|
101
142
|
|
102
|
-
|
103
|
-
end
|
104
|
-
|
143
|
+
# @return [Hash]
|
105
144
|
def to_hash
|
106
145
|
data = serialize_attributes
|
107
146
|
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
|
@@ -112,34 +151,37 @@ module Sentry
|
|
112
151
|
data
|
113
152
|
end
|
114
153
|
|
154
|
+
# @return [Hash]
|
115
155
|
def to_json_compatible
|
116
156
|
JSON.parse(JSON.generate(to_hash))
|
117
157
|
end
|
118
158
|
|
119
|
-
|
120
|
-
@request = Sentry::RequestInterface.build(env: env)
|
121
|
-
end
|
122
|
-
|
159
|
+
# @!visibility private
|
123
160
|
def add_threads_interface(backtrace: nil, **options)
|
124
161
|
@threads = ThreadsInterface.build(
|
125
162
|
backtrace: backtrace,
|
126
|
-
stacktrace_builder:
|
163
|
+
stacktrace_builder: @stacktrace_builder,
|
127
164
|
**options
|
128
165
|
)
|
129
166
|
end
|
130
167
|
|
168
|
+
# @!visibility private
|
131
169
|
def add_exception_interface(exception)
|
132
170
|
if exception.respond_to?(:sentry_context)
|
133
171
|
@extra.merge!(exception.sentry_context)
|
134
172
|
end
|
135
173
|
|
136
|
-
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder:
|
174
|
+
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
|
137
175
|
end
|
138
176
|
|
139
177
|
private
|
140
178
|
|
179
|
+
def add_request_interface(env)
|
180
|
+
@request = Sentry::RequestInterface.new(env: env, send_default_pii: @send_default_pii, rack_env_whitelist: @rack_env_whitelist)
|
181
|
+
end
|
182
|
+
|
141
183
|
def serialize_attributes
|
142
|
-
self.class::
|
184
|
+
self.class::SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |att, memo|
|
143
185
|
if value = public_send(att)
|
144
186
|
memo[att] = value
|
145
187
|
end
|
@@ -154,7 +196,7 @@ module Sentry
|
|
154
196
|
:client_ip => env["HTTP_CLIENT_IP"],
|
155
197
|
:real_ip => env["HTTP_X_REAL_IP"],
|
156
198
|
:forwarded_for => env["HTTP_X_FORWARDED_FOR"],
|
157
|
-
:trusted_proxies =>
|
199
|
+
:trusted_proxies => @trusted_proxies
|
158
200
|
).calculate_ip
|
159
201
|
end
|
160
202
|
end
|
data/lib/sentry/exceptions.rb
CHANGED
data/lib/sentry/hub.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "sentry/scope"
|
2
4
|
require "sentry/client"
|
3
5
|
|
@@ -90,10 +92,10 @@ module Sentry
|
|
90
92
|
end
|
91
93
|
|
92
94
|
def capture_exception(exception, **options, &block)
|
93
|
-
return unless current_client
|
94
|
-
|
95
95
|
check_argument_type!(exception, ::Exception)
|
96
96
|
|
97
|
+
return unless current_client
|
98
|
+
|
97
99
|
options[:hint] ||= {}
|
98
100
|
options[:hint][:exception] = exception
|
99
101
|
event = current_client.event_from_exception(exception, options[:hint])
|
@@ -104,19 +106,25 @@ module Sentry
|
|
104
106
|
end
|
105
107
|
|
106
108
|
def capture_message(message, **options, &block)
|
109
|
+
check_argument_type!(message, ::String)
|
110
|
+
|
107
111
|
return unless current_client
|
108
112
|
|
109
113
|
options[:hint] ||= {}
|
110
114
|
options[:hint][:message] = message
|
111
|
-
|
115
|
+
backtrace = options.delete(:backtrace)
|
116
|
+
event = current_client.event_from_message(message, options[:hint], backtrace: backtrace)
|
117
|
+
|
118
|
+
return unless event
|
119
|
+
|
112
120
|
capture_event(event, **options, &block)
|
113
121
|
end
|
114
122
|
|
115
123
|
def capture_event(event, **options, &block)
|
116
|
-
return unless current_client
|
117
|
-
|
118
124
|
check_argument_type!(event, Sentry::Event)
|
119
125
|
|
126
|
+
return unless current_client
|
127
|
+
|
120
128
|
hint = options.delete(:hint) || {}
|
121
129
|
scope = current_scope.dup
|
122
130
|
|
@@ -130,11 +138,18 @@ module Sentry
|
|
130
138
|
|
131
139
|
event = current_client.capture_event(event, scope, hint)
|
132
140
|
|
141
|
+
|
142
|
+
if event && configuration.debug
|
143
|
+
configuration.log_debug(event.to_json_compatible)
|
144
|
+
end
|
145
|
+
|
133
146
|
@last_event_id = event&.event_id
|
134
147
|
event
|
135
148
|
end
|
136
149
|
|
137
150
|
def add_breadcrumb(breadcrumb, hint: {})
|
151
|
+
return unless configuration.enabled_in_current_env?
|
152
|
+
|
138
153
|
if before_breadcrumb = current_client.configuration.before_breadcrumb
|
139
154
|
breadcrumb = before_breadcrumb.call(breadcrumb, hint)
|
140
155
|
end
|
@@ -144,6 +159,17 @@ module Sentry
|
|
144
159
|
current_scope.add_breadcrumb(breadcrumb)
|
145
160
|
end
|
146
161
|
|
162
|
+
# this doesn't do anything to the already initialized background worker
|
163
|
+
# but it temporarily disables dispatching events to it
|
164
|
+
def with_background_worker_disabled(&block)
|
165
|
+
original_background_worker_threads = configuration.background_worker_threads
|
166
|
+
configuration.background_worker_threads = 0
|
167
|
+
|
168
|
+
block.call
|
169
|
+
ensure
|
170
|
+
configuration.background_worker_threads = original_background_worker_threads
|
171
|
+
end
|
172
|
+
|
147
173
|
private
|
148
174
|
|
149
175
|
def current_layer
|
data/lib/sentry/integrable.rb
CHANGED
data/lib/sentry/interface.rb
CHANGED
@@ -1,15 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sentry
|
2
4
|
class Interface
|
3
|
-
|
4
|
-
name = klass.name.split("::").last.downcase.gsub("interface", "")
|
5
|
-
registered[name.to_sym] = klass
|
6
|
-
super
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.registered
|
10
|
-
@@registered ||= {} # rubocop:disable Style/ClassVars
|
11
|
-
end
|
12
|
-
|
5
|
+
# @return [Hash]
|
13
6
|
def to_hash
|
14
7
|
Hash[instance_variables.map { |name| [name[1..-1].to_sym, instance_variable_get(name)] }]
|
15
8
|
end
|
@@ -1,15 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sentry
|
2
4
|
class ExceptionInterface < Interface
|
3
|
-
|
4
|
-
|
5
|
+
# @param exceptions [Array<SingleExceptionInterface>]
|
6
|
+
def initialize(exceptions:)
|
7
|
+
@values = exceptions
|
5
8
|
end
|
6
9
|
|
10
|
+
# @return [Hash]
|
7
11
|
def to_hash
|
8
12
|
data = super
|
9
13
|
data[:values] = data[:values].map(&:to_hash) if data[:values]
|
10
14
|
data
|
11
15
|
end
|
12
16
|
|
17
|
+
# Builds ExceptionInterface with given exception and stacktrace_builder.
|
18
|
+
# @param exception [Exception]
|
19
|
+
# @param stacktrace_builder [StacktraceBuilder]
|
20
|
+
# @see SingleExceptionInterface#build_with_stacktrace
|
21
|
+
# @see SingleExceptionInterface#initialize
|
22
|
+
# @return [ExceptionInterface]
|
13
23
|
def self.build(exception:, stacktrace_builder:)
|
14
24
|
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
|
15
25
|
processed_backtrace_ids = Set.new
|
@@ -23,7 +33,7 @@ module Sentry
|
|
23
33
|
end
|
24
34
|
end
|
25
35
|
|
26
|
-
new(
|
36
|
+
new(exceptions: exceptions)
|
27
37
|
end
|
28
38
|
end
|
29
39
|
end
|
@@ -15,29 +15,45 @@ module Sentry
|
|
15
15
|
# https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
|
16
16
|
MAX_BODY_LIMIT = 4096 * 4
|
17
17
|
|
18
|
-
|
18
|
+
# @return [String]
|
19
|
+
attr_accessor :url
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
# @return [String]
|
22
|
+
attr_accessor :method
|
23
|
+
|
24
|
+
# @return [Hash]
|
25
|
+
attr_accessor :data
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
attr_accessor :query_string
|
29
|
+
|
30
|
+
# @return [String]
|
31
|
+
attr_accessor :cookies
|
25
32
|
|
26
|
-
|
27
|
-
|
33
|
+
# @return [Hash]
|
34
|
+
attr_accessor :headers
|
35
|
+
|
36
|
+
# @return [Hash]
|
37
|
+
attr_accessor :env
|
38
|
+
|
39
|
+
# @param env [Hash]
|
40
|
+
# @param send_default_pii [Boolean]
|
41
|
+
# @param rack_env_whitelist [Array]
|
42
|
+
# @see Configuration#send_default_pii
|
43
|
+
# @see Configuration#rack_env_whitelist
|
44
|
+
def initialize(env:, send_default_pii:, rack_env_whitelist:)
|
45
|
+
env = env.dup
|
46
|
+
|
47
|
+
unless send_default_pii
|
28
48
|
# need to completely wipe out ip addresses
|
29
49
|
RequestInterface::IP_HEADERS.each do |header|
|
30
50
|
env.delete(header)
|
31
51
|
end
|
32
52
|
end
|
33
53
|
|
34
|
-
env
|
35
|
-
end
|
36
|
-
|
37
|
-
def initialize(request:)
|
38
|
-
env = request.env
|
54
|
+
request = ::Rack::Request.new(env)
|
39
55
|
|
40
|
-
if
|
56
|
+
if send_default_pii
|
41
57
|
self.data = read_data_from(request)
|
42
58
|
self.cookies = request.cookies
|
43
59
|
self.query_string = request.query_string
|
@@ -47,7 +63,7 @@ module Sentry
|
|
47
63
|
self.method = request.request_method
|
48
64
|
|
49
65
|
self.headers = filter_and_format_headers(env)
|
50
|
-
self.env = filter_and_format_env(env)
|
66
|
+
self.env = filter_and_format_env(env, rack_env_whitelist)
|
51
67
|
end
|
52
68
|
|
53
69
|
private
|
@@ -57,6 +73,7 @@ module Sentry
|
|
57
73
|
request.POST
|
58
74
|
elsif request.body # JSON requests, etc
|
59
75
|
data = request.body.read(MAX_BODY_LIMIT)
|
76
|
+
data = encode_to_utf_8(data.to_s)
|
60
77
|
request.body.rewind
|
61
78
|
data
|
62
79
|
end
|
@@ -75,7 +92,8 @@ module Sentry
|
|
75
92
|
# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
|
76
93
|
key = key.sub(/^HTTP_/, "")
|
77
94
|
key = key.split('_').map(&:capitalize).join('-')
|
78
|
-
|
95
|
+
|
96
|
+
memo[key] = encode_to_utf_8(value.to_s)
|
79
97
|
rescue StandardError => e
|
80
98
|
# Rails adds objects to the Rack env that can sometimes raise exceptions
|
81
99
|
# when `to_s` is called.
|
@@ -86,6 +104,18 @@ module Sentry
|
|
86
104
|
end
|
87
105
|
end
|
88
106
|
|
107
|
+
def encode_to_utf_8(value)
|
108
|
+
if value.encoding != Encoding::UTF_8 && value.respond_to?(:force_encoding)
|
109
|
+
value = value.dup.force_encoding(Encoding::UTF_8)
|
110
|
+
end
|
111
|
+
|
112
|
+
if !value.valid_encoding?
|
113
|
+
value = value.scrub
|
114
|
+
end
|
115
|
+
|
116
|
+
value
|
117
|
+
end
|
118
|
+
|
89
119
|
def is_skippable_header?(key)
|
90
120
|
key.upcase != key || # lower-case envs aren't real http headers
|
91
121
|
key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
|
@@ -102,11 +132,11 @@ module Sentry
|
|
102
132
|
key == 'HTTP_VERSION' && value == protocol_version
|
103
133
|
end
|
104
134
|
|
105
|
-
def filter_and_format_env(env)
|
106
|
-
return env if
|
135
|
+
def filter_and_format_env(env, rack_env_whitelist)
|
136
|
+
return env if rack_env_whitelist.empty?
|
107
137
|
|
108
138
|
env.select do |k, _v|
|
109
|
-
|
139
|
+
rack_env_whitelist.include? k.to_s
|
110
140
|
end
|
111
141
|
end
|
112
142
|
end
|
@@ -1,5 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sentry/utils/exception_cause_chain"
|
4
|
+
|
1
5
|
module Sentry
|
2
6
|
class SingleExceptionInterface < Interface
|
7
|
+
include CustomInspection
|
8
|
+
|
9
|
+
SKIP_INSPECTION_ATTRIBUTES = [:@stacktrace]
|
10
|
+
PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]".freeze
|
11
|
+
OMISSION_MARK = "...".freeze
|
12
|
+
MAX_LOCAL_BYTES = 1024
|
13
|
+
|
3
14
|
attr_reader :type, :value, :module, :thread_id, :stacktrace
|
4
15
|
|
5
16
|
def initialize(exception:, stacktrace: nil)
|
@@ -20,6 +31,26 @@ module Sentry
|
|
20
31
|
# also see `StacktraceBuilder.build`.
|
21
32
|
def self.build_with_stacktrace(exception:, stacktrace_builder:)
|
22
33
|
stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
|
34
|
+
|
35
|
+
if locals = exception.instance_variable_get(:@sentry_locals)
|
36
|
+
locals.each do |k, v|
|
37
|
+
locals[k] =
|
38
|
+
begin
|
39
|
+
v = v.inspect unless v.is_a?(String)
|
40
|
+
|
41
|
+
if v.length >= MAX_LOCAL_BYTES
|
42
|
+
v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
|
43
|
+
end
|
44
|
+
|
45
|
+
v
|
46
|
+
rescue StandardError
|
47
|
+
PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
stacktrace.frames.last.vars = locals
|
52
|
+
end
|
53
|
+
|
23
54
|
new(exception: exception, stacktrace: stacktrace)
|
24
55
|
end
|
25
56
|
end
|
@@ -1,15 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sentry
|
2
4
|
class StacktraceInterface
|
5
|
+
# @return [<Array[Frame]>]
|
3
6
|
attr_reader :frames
|
4
7
|
|
8
|
+
# @param frames [<Array[Frame]>]
|
5
9
|
def initialize(frames:)
|
6
10
|
@frames = frames
|
7
11
|
end
|
8
12
|
|
13
|
+
# @return [Hash]
|
9
14
|
def to_hash
|
10
15
|
{ frames: @frames.map(&:to_hash) }
|
11
16
|
end
|
12
17
|
|
18
|
+
# @return [String]
|
19
|
+
def inspect
|
20
|
+
@frames.map(&:to_s)
|
21
|
+
end
|
22
|
+
|
13
23
|
private
|
14
24
|
|
15
25
|
# Not actually an interface, but I want to use the same style
|
@@ -28,6 +38,10 @@ module Sentry
|
|
28
38
|
@filename = compute_filename
|
29
39
|
end
|
30
40
|
|
41
|
+
def to_s
|
42
|
+
"#{@filename}:#{@lineno}"
|
43
|
+
end
|
44
|
+
|
31
45
|
def compute_filename
|
32
46
|
return if abs_path.nil?
|
33
47
|
|
@@ -1,7 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sentry
|
2
4
|
class StacktraceBuilder
|
3
|
-
|
5
|
+
# @return [String]
|
6
|
+
attr_reader :project_root
|
7
|
+
|
8
|
+
# @return [Regexp, nil]
|
9
|
+
attr_reader :app_dirs_pattern
|
10
|
+
|
11
|
+
# @return [LineCache]
|
12
|
+
attr_reader :linecache
|
13
|
+
|
14
|
+
# @return [Integer, nil]
|
15
|
+
attr_reader :context_lines
|
16
|
+
|
17
|
+
# @return [Proc, nil]
|
18
|
+
attr_reader :backtrace_cleanup_callback
|
4
19
|
|
20
|
+
# @param project_root [String]
|
21
|
+
# @param app_dirs_pattern [Regexp, nil]
|
22
|
+
# @param linecache [LineCache]
|
23
|
+
# @param context_lines [Integer, nil]
|
24
|
+
# @param backtrace_cleanup_callback [Proc, nil]
|
25
|
+
# @see Configuration#project_root
|
26
|
+
# @see Configuration#app_dirs_pattern
|
27
|
+
# @see Configuration#linecache
|
28
|
+
# @see Configuration#context_lines
|
29
|
+
# @see Configuration#backtrace_cleanup_callback
|
5
30
|
def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
|
6
31
|
@project_root = project_root
|
7
32
|
@app_dirs_pattern = app_dirs_pattern
|
@@ -10,17 +35,21 @@ module Sentry
|
|
10
35
|
@backtrace_cleanup_callback = backtrace_cleanup_callback
|
11
36
|
end
|
12
37
|
|
13
|
-
#
|
38
|
+
# Generates a StacktraceInterface with the given backtrace.
|
39
|
+
# You can pass a block to customize/exclude frames:
|
14
40
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
41
|
+
# @example
|
42
|
+
# builder.build(backtrace) do |frame|
|
43
|
+
# if frame.module.match?(/a_gem/)
|
44
|
+
# nil
|
45
|
+
# else
|
46
|
+
# frame
|
47
|
+
# end
|
21
48
|
# end
|
22
|
-
#
|
23
|
-
#
|
49
|
+
# @param backtrace [Array<String>]
|
50
|
+
# @param frame_callback [Proc]
|
51
|
+
# @yieldparam frame [StacktraceInterface::Frame]
|
52
|
+
# @return [StacktraceInterface]
|
24
53
|
def build(backtrace:, &frame_callback)
|
25
54
|
parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
|
26
55
|
|
@@ -1,5 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sentry
|
2
4
|
class ThreadsInterface
|
5
|
+
# @param crashed [Boolean]
|
6
|
+
# @param stacktrace [Array]
|
3
7
|
def initialize(crashed: false, stacktrace: nil)
|
4
8
|
@id = Thread.current.object_id
|
5
9
|
@name = Thread.current.name
|
@@ -8,6 +12,7 @@ module Sentry
|
|
8
12
|
@stacktrace = stacktrace
|
9
13
|
end
|
10
14
|
|
15
|
+
# @return [Hash]
|
11
16
|
def to_hash
|
12
17
|
{
|
13
18
|
values: [
|
@@ -22,8 +27,13 @@ module Sentry
|
|
22
27
|
}
|
23
28
|
end
|
24
29
|
|
25
|
-
#
|
26
|
-
#
|
30
|
+
# Builds the ThreadsInterface with given backtrace and stacktrace_builder.
|
31
|
+
# Patch this method if you want to change a threads interface's stacktrace frames.
|
32
|
+
# @see StacktraceBuilder.build
|
33
|
+
# @param backtrace [Array]
|
34
|
+
# @param stacktrace_builder [StacktraceBuilder]
|
35
|
+
# @param crashed [Hash]
|
36
|
+
# @return [ThreadsInterface]
|
27
37
|
def self.build(backtrace:, stacktrace_builder:, **options)
|
28
38
|
stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
|
29
39
|
new(**options, stacktrace: stacktrace)
|