sentry-ruby-core 4.2.1 → 4.4.0.pre.beta.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/.craft.yml +4 -3
- data/CHANGELOG.md +116 -0
- data/Gemfile +4 -0
- data/README.md +39 -55
- data/lib/sentry-ruby.rb +9 -9
- data/lib/sentry/background_worker.rb +8 -4
- data/lib/sentry/breadcrumb/sentry_logger.rb +2 -1
- data/lib/sentry/breadcrumb_buffer.rb +3 -2
- data/lib/sentry/client.rb +50 -27
- data/lib/sentry/configuration.rb +27 -11
- data/lib/sentry/event.rb +28 -47
- data/lib/sentry/exceptions.rb +7 -0
- data/lib/sentry/hub.rb +17 -3
- 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 +9 -3
- data/lib/sentry/net/http.rb +87 -0
- data/lib/sentry/rack/capture_exceptions.rb +18 -11
- data/lib/sentry/scope.rb +12 -6
- data/lib/sentry/span.rb +21 -4
- data/lib/sentry/transaction.rb +53 -42
- data/lib/sentry/transaction_event.rb +3 -1
- data/lib/sentry/transport.rb +57 -26
- data/lib/sentry/transport/configuration.rb +3 -1
- data/lib/sentry/transport/http_transport.rb +92 -6
- data/lib/sentry/utils/logging_helper.rb +24 -0
- data/lib/sentry/utils/real_ip.rb +1 -1
- data/lib/sentry/version.rb +1 -1
- metadata +9 -5
data/lib/sentry/client.rb
CHANGED
@@ -2,10 +2,13 @@ require "sentry/transport"
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class Client
|
5
|
-
|
5
|
+
include LoggingHelper
|
6
|
+
|
7
|
+
attr_reader :transport, :configuration, :logger
|
6
8
|
|
7
9
|
def initialize(configuration)
|
8
10
|
@configuration = configuration
|
11
|
+
@logger = configuration.logger
|
9
12
|
|
10
13
|
if transport_class = configuration.transport.transport_class
|
11
14
|
@transport = transport_class.new(configuration)
|
@@ -26,32 +29,17 @@ module Sentry
|
|
26
29
|
scope.apply_to_event(event, hint)
|
27
30
|
|
28
31
|
if async_block = configuration.async
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
event_hash = event.to_json_compatible
|
33
|
-
|
34
|
-
if async_block.arity == 2
|
35
|
-
hint = JSON.parse(JSON.generate(hint))
|
36
|
-
async_block.call(event_hash, hint)
|
37
|
-
else
|
38
|
-
async_block.call(event_hash)
|
39
|
-
end
|
40
|
-
rescue => e
|
41
|
-
configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
|
42
|
-
send_event(event, hint)
|
43
|
-
end
|
32
|
+
dispatch_async_event(async_block, event, hint)
|
33
|
+
elsif hint.fetch(:background, true)
|
34
|
+
dispatch_background_event(event, hint)
|
44
35
|
else
|
45
|
-
|
46
|
-
Sentry.background_worker.perform do
|
47
|
-
send_event(event, hint)
|
48
|
-
end
|
49
|
-
else
|
50
|
-
send_event(event, hint)
|
51
|
-
end
|
36
|
+
send_event(event, hint)
|
52
37
|
end
|
53
38
|
|
54
39
|
event
|
40
|
+
rescue => e
|
41
|
+
log_error("Event capturing failed", e, debug: configuration.debug)
|
42
|
+
nil
|
55
43
|
end
|
56
44
|
|
57
45
|
def event_from_exception(exception, hint = {})
|
@@ -85,16 +73,51 @@ module Sentry
|
|
85
73
|
|
86
74
|
def send_event(event, hint = nil)
|
87
75
|
event_type = event.is_a?(Event) ? event.type : event["type"]
|
88
|
-
event = configuration.before_send.call(event, hint) if configuration.before_send && event_type == "event"
|
89
76
|
|
90
|
-
if
|
91
|
-
configuration.
|
92
|
-
|
77
|
+
if event_type != TransactionEvent::TYPE && configuration.before_send
|
78
|
+
event = configuration.before_send.call(event, hint)
|
79
|
+
|
80
|
+
if event.nil?
|
81
|
+
log_info("Discarded event because before_send returned nil")
|
82
|
+
return
|
83
|
+
end
|
93
84
|
end
|
94
85
|
|
95
86
|
transport.send_event(event)
|
96
87
|
|
97
88
|
event
|
89
|
+
rescue => e
|
90
|
+
loggable_event_type = (event_type || "event").capitalize
|
91
|
+
log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
|
92
|
+
|
93
|
+
event_info = Event.get_log_message(event.to_hash)
|
94
|
+
log_info("Unreported #{loggable_event_type}: #{event_info}")
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def dispatch_background_event(event, hint)
|
101
|
+
Sentry.background_worker.perform do
|
102
|
+
send_event(event, hint)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def dispatch_async_event(async_block, event, hint)
|
107
|
+
# We have to convert to a JSON-like hash, because background job
|
108
|
+
# processors (esp ActiveJob) may not like weird types in the event hash
|
109
|
+
event_hash = event.to_json_compatible
|
110
|
+
|
111
|
+
if async_block.arity == 2
|
112
|
+
hint = JSON.parse(JSON.generate(hint))
|
113
|
+
async_block.call(event_hash, hint)
|
114
|
+
else
|
115
|
+
async_block.call(event_hash)
|
116
|
+
end
|
117
|
+
rescue => e
|
118
|
+
loggable_event_type = event_hash["type"] || "event"
|
119
|
+
log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
|
120
|
+
send_event(event, hint)
|
98
121
|
end
|
99
122
|
end
|
100
123
|
end
|
data/lib/sentry/configuration.rb
CHANGED
@@ -4,9 +4,11 @@ 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
|
11
|
+
include LoggingHelper
|
10
12
|
# Directories to be recognized as part of your app. e.g. if you
|
11
13
|
# have an `engines` dir at the root of your project, you may want
|
12
14
|
# to set this to something like /(app|config|engines|lib)/
|
@@ -61,12 +63,19 @@ module Sentry
|
|
61
63
|
# - :active_support_logger
|
62
64
|
attr_reader :breadcrumbs_logger
|
63
65
|
|
66
|
+
# Max number of breadcrumbs a breadcrumb buffer can hold
|
67
|
+
attr_accessor :max_breadcrumbs
|
68
|
+
|
64
69
|
# Number of lines of code context to capture, or nil for none
|
65
70
|
attr_accessor :context_lines
|
66
71
|
|
67
72
|
# RACK_ENV by default.
|
68
73
|
attr_reader :environment
|
69
74
|
|
75
|
+
# Whether the SDK should run in the debugging mode. Default is false.
|
76
|
+
# If set to true, SDK errors will be logged with backtrace
|
77
|
+
attr_accessor :debug
|
78
|
+
|
70
79
|
# the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
|
71
80
|
attr_reader :dsn
|
72
81
|
|
@@ -163,14 +172,14 @@ module Sentry
|
|
163
172
|
LOG_PREFIX = "** [Sentry] ".freeze
|
164
173
|
MODULE_SEPARATOR = "::".freeze
|
165
174
|
|
166
|
-
AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
|
167
|
-
|
168
175
|
# Post initialization callbacks are called at the end of initialization process
|
169
176
|
# allowing extending the configuration of sentry-ruby by multiple extensions
|
170
177
|
@@post_initialization_callbacks = []
|
171
178
|
|
172
179
|
def initialize
|
180
|
+
self.debug = false
|
173
181
|
self.background_worker_threads = Concurrent.processor_count
|
182
|
+
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
|
174
183
|
self.breadcrumbs_logger = []
|
175
184
|
self.context_lines = 3
|
176
185
|
self.environment = environment_from_env
|
@@ -195,6 +204,7 @@ module Sentry
|
|
195
204
|
|
196
205
|
@transport = Transport::Configuration.new
|
197
206
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
207
|
+
|
198
208
|
run_post_initialization_callbacks
|
199
209
|
end
|
200
210
|
|
@@ -220,10 +230,6 @@ module Sentry
|
|
220
230
|
if logger.is_a?(Array)
|
221
231
|
logger
|
222
232
|
else
|
223
|
-
unless AVAILABLE_BREADCRUMBS_LOGGERS.include?(logger)
|
224
|
-
raise Sentry::Error, "Unsupported breadcrumbs logger. Supported loggers: #{AVAILABLE_BREADCRUMBS_LOGGERS}"
|
225
|
-
end
|
226
|
-
|
227
233
|
Array(logger)
|
228
234
|
end
|
229
235
|
|
@@ -272,10 +278,10 @@ module Sentry
|
|
272
278
|
def exception_class_allowed?(exc)
|
273
279
|
if exc.is_a?(Sentry::Error)
|
274
280
|
# Try to prevent error reporting loops
|
275
|
-
|
281
|
+
log_debug("Refusing to capture Sentry error: #{exc.inspect}")
|
276
282
|
false
|
277
283
|
elsif excluded_exception?(exc)
|
278
|
-
|
284
|
+
log_debug("User excluded error: #{exc.inspect}")
|
279
285
|
false
|
280
286
|
else
|
281
287
|
true
|
@@ -287,7 +293,17 @@ module Sentry
|
|
287
293
|
end
|
288
294
|
|
289
295
|
def tracing_enabled?
|
290
|
-
!!((@traces_sample_rate && @traces_sample_rate
|
296
|
+
!!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler)
|
297
|
+
end
|
298
|
+
|
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
|
+
)
|
291
307
|
end
|
292
308
|
|
293
309
|
private
|
@@ -298,7 +314,7 @@ module Sentry
|
|
298
314
|
detect_release_from_capistrano ||
|
299
315
|
detect_release_from_heroku
|
300
316
|
rescue => e
|
301
|
-
|
317
|
+
log_error("Error detecting release", e, debug: debug)
|
302
318
|
end
|
303
319
|
|
304
320
|
def excluded_exception?(incoming_exception)
|
@@ -333,7 +349,7 @@ module Sentry
|
|
333
349
|
def detect_release_from_heroku
|
334
350
|
return unless running_on_heroku?
|
335
351
|
return if ENV['CI']
|
336
|
-
|
352
|
+
log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
|
337
353
|
|
338
354
|
ENV['HEROKU_SLUG_COMMIT']
|
339
355
|
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
|
|
@@ -93,13 +100,11 @@ module Sentry
|
|
93
100
|
end
|
94
101
|
|
95
102
|
def type
|
96
|
-
"event"
|
97
103
|
end
|
98
104
|
|
99
105
|
def to_hash
|
100
106
|
data = serialize_attributes
|
101
107
|
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
|
102
|
-
data[:stacktrace] = stacktrace.to_hash if stacktrace
|
103
108
|
data[:request] = request.to_hash if request
|
104
109
|
data[:exception] = exception.to_hash if exception
|
105
110
|
data[:threads] = threads.to_hash if threads
|
@@ -112,47 +117,23 @@ module Sentry
|
|
112
117
|
end
|
113
118
|
|
114
119
|
def add_request_interface(env)
|
115
|
-
@request = Sentry::RequestInterface.
|
120
|
+
@request = Sentry::RequestInterface.build(env: env)
|
116
121
|
end
|
117
122
|
|
118
123
|
def add_threads_interface(backtrace: nil, **options)
|
119
|
-
@threads = ThreadsInterface.
|
120
|
-
|
124
|
+
@threads = ThreadsInterface.build(
|
125
|
+
backtrace: backtrace,
|
126
|
+
stacktrace_builder: configuration.stacktrace_builder,
|
127
|
+
**options
|
128
|
+
)
|
121
129
|
end
|
122
130
|
|
123
|
-
def add_exception_interface(
|
124
|
-
if
|
125
|
-
@extra.merge!(
|
131
|
+
def add_exception_interface(exception)
|
132
|
+
if exception.respond_to?(:sentry_context)
|
133
|
+
@extra.merge!(exception.sentry_context)
|
126
134
|
end
|
127
135
|
|
128
|
-
@exception = Sentry::ExceptionInterface.
|
129
|
-
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
|
130
|
-
backtraces = Set.new
|
131
|
-
exc_int.values = exceptions.map do |e|
|
132
|
-
SingleExceptionInterface.new.tap do |int|
|
133
|
-
int.type = e.class.to_s
|
134
|
-
int.value = e.message.byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
|
135
|
-
int.module = e.class.to_s.split('::')[0...-1].join('::')
|
136
|
-
|
137
|
-
int.stacktrace =
|
138
|
-
if e.backtrace && !backtraces.include?(e.backtrace.object_id)
|
139
|
-
backtraces << e.backtrace.object_id
|
140
|
-
initialize_stacktrace_interface(e.backtrace)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def initialize_stacktrace_interface(backtrace)
|
148
|
-
StacktraceInterface.new(
|
149
|
-
backtrace: backtrace,
|
150
|
-
project_root: configuration.project_root.to_s,
|
151
|
-
app_dirs_pattern: configuration.app_dirs_pattern,
|
152
|
-
linecache: configuration.linecache,
|
153
|
-
context_lines: configuration.context_lines,
|
154
|
-
backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
|
155
|
-
)
|
136
|
+
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: configuration.stacktrace_builder)
|
156
137
|
end
|
157
138
|
|
158
139
|
private
|
data/lib/sentry/hub.rb
CHANGED
@@ -21,6 +21,10 @@ module Sentry
|
|
21
21
|
current_layer&.client
|
22
22
|
end
|
23
23
|
|
24
|
+
def configuration
|
25
|
+
current_client.configuration
|
26
|
+
end
|
27
|
+
|
24
28
|
def current_scope
|
25
29
|
current_layer&.scope
|
26
30
|
end
|
@@ -69,9 +73,19 @@ module Sentry
|
|
69
73
|
@stack.pop
|
70
74
|
end
|
71
75
|
|
72
|
-
def start_transaction(transaction: nil, **options)
|
73
|
-
|
74
|
-
|
76
|
+
def start_transaction(transaction: nil, custom_sampling_context: {}, **options)
|
77
|
+
return unless configuration.tracing_enabled?
|
78
|
+
|
79
|
+
transaction ||= Transaction.new(**options.merge(hub: self))
|
80
|
+
|
81
|
+
sampling_context = {
|
82
|
+
transaction_context: transaction.to_hash,
|
83
|
+
parent_sampled: transaction.parent_sampled
|
84
|
+
}
|
85
|
+
|
86
|
+
sampling_context.merge!(custom_sampling_context)
|
87
|
+
|
88
|
+
transaction.set_initial_sample_decision(sampling_context: sampling_context)
|
75
89
|
transaction
|
76
90
|
end
|
77
91
|
|
@@ -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)
|