sentry-ruby-core 4.4.0 → 5.1.1
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 +12 -0
- data/Gemfile +9 -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 +66 -7
- data/lib/sentry/configuration.rb +156 -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 +49 -0
- data/lib/sentry/event.rb +65 -23
- data/lib/sentry/exceptions.rb +2 -0
- data/lib/sentry/hub.rb +37 -6
- 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 +52 -21
- 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 +79 -51
- 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/redis.rb +88 -0
- 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 +50 -13
- 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 +55 -42
- data/lib/sentry/transport.rb +101 -32
- 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 +247 -47
- data/sentry-ruby-core.gemspec +2 -3
- data/sentry-ruby.gemspec +2 -3
- metadata +10 -22
- data/.craft.yml +0 -29
- data/lib/sentry/benchmarks/benchmark_transport.rb +0 -14
- data/lib/sentry/rack/deprecations.rb +0 -19
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
# @api private
|
5
|
+
class Envelope
|
6
|
+
class Item
|
7
|
+
attr_accessor :headers, :payload
|
8
|
+
|
9
|
+
def initialize(headers, payload)
|
10
|
+
@headers = headers
|
11
|
+
@payload = payload
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
@headers[:type] || 'event'
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
<<~ITEM
|
20
|
+
#{JSON.generate(@headers)}
|
21
|
+
#{JSON.generate(@payload)}
|
22
|
+
ITEM
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :headers, :items
|
27
|
+
|
28
|
+
def initialize(headers = {})
|
29
|
+
@headers = headers
|
30
|
+
@items = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_item(headers, payload)
|
34
|
+
@items << Item.new(headers, payload)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
[JSON.generate(@headers), *@items.map(&:to_s)].join("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
def item_types
|
42
|
+
@items.map(&:type)
|
43
|
+
end
|
44
|
+
|
45
|
+
def event_id
|
46
|
+
@headers[:event_id]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
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,33 +92,44 @@ 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 if Sentry.exception_captured?(exception)
|
98
|
+
|
99
|
+
return unless current_client
|
100
|
+
|
97
101
|
options[:hint] ||= {}
|
98
102
|
options[:hint][:exception] = exception
|
99
103
|
event = current_client.event_from_exception(exception, options[:hint])
|
100
104
|
|
101
105
|
return unless event
|
102
106
|
|
103
|
-
capture_event(event, **options, &block)
|
107
|
+
capture_event(event, **options, &block).tap do
|
108
|
+
# mark the exception as captured so we can use this information to avoid duplicated capturing
|
109
|
+
exception.instance_variable_set(Sentry::CAPTURED_SIGNATURE, true)
|
110
|
+
end
|
104
111
|
end
|
105
112
|
|
106
113
|
def capture_message(message, **options, &block)
|
114
|
+
check_argument_type!(message, ::String)
|
115
|
+
|
107
116
|
return unless current_client
|
108
117
|
|
109
118
|
options[:hint] ||= {}
|
110
119
|
options[:hint][:message] = message
|
111
|
-
|
120
|
+
backtrace = options.delete(:backtrace)
|
121
|
+
event = current_client.event_from_message(message, options[:hint], backtrace: backtrace)
|
122
|
+
|
123
|
+
return unless event
|
124
|
+
|
112
125
|
capture_event(event, **options, &block)
|
113
126
|
end
|
114
127
|
|
115
128
|
def capture_event(event, **options, &block)
|
116
|
-
return unless current_client
|
117
|
-
|
118
129
|
check_argument_type!(event, Sentry::Event)
|
119
130
|
|
131
|
+
return unless current_client
|
132
|
+
|
120
133
|
hint = options.delete(:hint) || {}
|
121
134
|
scope = current_scope.dup
|
122
135
|
|
@@ -130,11 +143,18 @@ module Sentry
|
|
130
143
|
|
131
144
|
event = current_client.capture_event(event, scope, hint)
|
132
145
|
|
146
|
+
|
147
|
+
if event && configuration.debug
|
148
|
+
configuration.log_debug(event.to_json_compatible)
|
149
|
+
end
|
150
|
+
|
133
151
|
@last_event_id = event&.event_id
|
134
152
|
event
|
135
153
|
end
|
136
154
|
|
137
155
|
def add_breadcrumb(breadcrumb, hint: {})
|
156
|
+
return unless configuration.enabled_in_current_env?
|
157
|
+
|
138
158
|
if before_breadcrumb = current_client.configuration.before_breadcrumb
|
139
159
|
breadcrumb = before_breadcrumb.call(breadcrumb, hint)
|
140
160
|
end
|
@@ -144,6 +164,17 @@ module Sentry
|
|
144
164
|
current_scope.add_breadcrumb(breadcrumb)
|
145
165
|
end
|
146
166
|
|
167
|
+
# this doesn't do anything to the already initialized background worker
|
168
|
+
# but it temporarily disables dispatching events to it
|
169
|
+
def with_background_worker_disabled(&block)
|
170
|
+
original_background_worker_threads = configuration.background_worker_threads
|
171
|
+
configuration.background_worker_threads = 0
|
172
|
+
|
173
|
+
block.call
|
174
|
+
ensure
|
175
|
+
configuration.background_worker_threads = original_background_worker_threads
|
176
|
+
end
|
177
|
+
|
147
178
|
private
|
148
179
|
|
149
180
|
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
|
@@ -46,8 +62,8 @@ module Sentry
|
|
46
62
|
self.url = request.scheme && request.url.split('?').first
|
47
63
|
self.method = request.request_method
|
48
64
|
|
49
|
-
self.headers = filter_and_format_headers(env)
|
50
|
-
self.env = filter_and_format_env(env)
|
65
|
+
self.headers = filter_and_format_headers(env, send_default_pii)
|
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
|
@@ -64,18 +81,20 @@ module Sentry
|
|
64
81
|
e.message
|
65
82
|
end
|
66
83
|
|
67
|
-
def filter_and_format_headers(env)
|
84
|
+
def filter_and_format_headers(env, send_default_pii)
|
68
85
|
env.each_with_object({}) do |(key, value), memo|
|
69
86
|
begin
|
70
87
|
key = key.to_s # rack env can contain symbols
|
71
88
|
next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
|
72
89
|
next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
|
73
90
|
next if is_skippable_header?(key)
|
91
|
+
next if key == "HTTP_AUTHORIZATION" && !send_default_pii
|
74
92
|
|
75
93
|
# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
|
76
94
|
key = key.sub(/^HTTP_/, "")
|
77
95
|
key = key.split('_').map(&:capitalize).join('-')
|
78
|
-
|
96
|
+
|
97
|
+
memo[key] = encode_to_utf_8(value.to_s)
|
79
98
|
rescue StandardError => e
|
80
99
|
# Rails adds objects to the Rack env that can sometimes raise exceptions
|
81
100
|
# when `to_s` is called.
|
@@ -86,6 +105,18 @@ module Sentry
|
|
86
105
|
end
|
87
106
|
end
|
88
107
|
|
108
|
+
def encode_to_utf_8(value)
|
109
|
+
if value.encoding != Encoding::UTF_8 && value.respond_to?(:force_encoding)
|
110
|
+
value = value.dup.force_encoding(Encoding::UTF_8)
|
111
|
+
end
|
112
|
+
|
113
|
+
if !value.valid_encoding?
|
114
|
+
value = value.scrub
|
115
|
+
end
|
116
|
+
|
117
|
+
value
|
118
|
+
end
|
119
|
+
|
89
120
|
def is_skippable_header?(key)
|
90
121
|
key.upcase != key || # lower-case envs aren't real http headers
|
91
122
|
key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
|
@@ -102,11 +133,11 @@ module Sentry
|
|
102
133
|
key == 'HTTP_VERSION' && value == protocol_version
|
103
134
|
end
|
104
135
|
|
105
|
-
def filter_and_format_env(env)
|
106
|
-
return env if
|
136
|
+
def filter_and_format_env(env, rack_env_whitelist)
|
137
|
+
return env if rack_env_whitelist.empty?
|
107
138
|
|
108
139
|
env.select do |k, _v|
|
109
|
-
|
140
|
+
rack_env_whitelist.include? k.to_s
|
110
141
|
end
|
111
142
|
end
|
112
143
|
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
|
|