sentry-ruby 5.13.0 → 5.19.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/Gemfile +4 -18
- data/README.md +20 -10
- data/Rakefile +1 -1
- data/bin/console +1 -0
- data/lib/sentry/attachment.rb +42 -0
- data/lib/sentry/background_worker.rb +9 -2
- data/lib/sentry/backpressure_monitor.rb +45 -0
- data/lib/sentry/backtrace.rb +7 -3
- data/lib/sentry/check_in_event.rb +1 -1
- data/lib/sentry/client.rb +69 -16
- data/lib/sentry/configuration.rb +57 -14
- data/lib/sentry/cron/configuration.rb +23 -0
- data/lib/sentry/cron/monitor_check_ins.rb +40 -26
- data/lib/sentry/cron/monitor_schedule.rb +1 -1
- data/lib/sentry/dsn.rb +1 -1
- data/lib/sentry/envelope.rb +18 -1
- data/lib/sentry/error_event.rb +2 -2
- data/lib/sentry/event.rb +13 -39
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +9 -0
- data/lib/sentry/hub.rb +17 -4
- data/lib/sentry/integrable.rb +4 -0
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +5 -3
- data/lib/sentry/interfaces/mechanism.rb +20 -0
- data/lib/sentry/interfaces/request.rb +2 -2
- data/lib/sentry/interfaces/single_exception.rb +7 -4
- data/lib/sentry/interfaces/stacktrace_builder.rb +8 -0
- data/lib/sentry/metrics/aggregator.rb +248 -0
- data/lib/sentry/metrics/configuration.rb +47 -0
- data/lib/sentry/metrics/counter_metric.rb +25 -0
- data/lib/sentry/metrics/distribution_metric.rb +25 -0
- data/lib/sentry/metrics/gauge_metric.rb +35 -0
- data/lib/sentry/metrics/local_aggregator.rb +53 -0
- data/lib/sentry/metrics/metric.rb +19 -0
- data/lib/sentry/metrics/set_metric.rb +28 -0
- data/lib/sentry/metrics/timing.rb +43 -0
- data/lib/sentry/metrics.rb +56 -0
- data/lib/sentry/net/http.rb +22 -39
- data/lib/sentry/propagation_context.rb +9 -8
- data/lib/sentry/puma.rb +1 -1
- data/lib/sentry/rack/capture_exceptions.rb +14 -2
- data/lib/sentry/rake.rb +3 -14
- data/lib/sentry/redis.rb +2 -1
- data/lib/sentry/release_detector.rb +1 -1
- data/lib/sentry/scope.rb +47 -37
- data/lib/sentry/session.rb +2 -2
- data/lib/sentry/session_flusher.rb +6 -38
- data/lib/sentry/span.rb +40 -5
- data/lib/sentry/test_helper.rb +2 -1
- data/lib/sentry/threaded_periodic_worker.rb +39 -0
- data/lib/sentry/transaction.rb +25 -16
- data/lib/sentry/transaction_event.rb +5 -0
- data/lib/sentry/transport/configuration.rb +73 -1
- data/lib/sentry/transport/http_transport.rb +68 -37
- data/lib/sentry/transport/spotlight_transport.rb +50 -0
- data/lib/sentry/transport.rb +32 -37
- data/lib/sentry/utils/argument_checking_helper.rb +6 -0
- data/lib/sentry/utils/http_tracing.rb +41 -0
- data/lib/sentry/utils/logging_helper.rb +0 -4
- data/lib/sentry/utils/real_ip.rb +1 -1
- data/lib/sentry/utils/request_id.rb +1 -1
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +57 -24
- data/sentry-ruby.gemspec +12 -5
- metadata +42 -7
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Cron
|
5
|
+
class Configuration
|
6
|
+
# Defaults set here will apply to all {Cron::MonitorConfig} objects unless overwritten.
|
7
|
+
|
8
|
+
# How long (in minutes) after the expected checkin time will we wait
|
9
|
+
# until we consider the checkin to have been missed.
|
10
|
+
# @return [Integer, nil]
|
11
|
+
attr_accessor :default_checkin_margin
|
12
|
+
|
13
|
+
# How long (in minutes) is the checkin allowed to run for in in_progress
|
14
|
+
# before it is considered failed.
|
15
|
+
# @return [Integer, nil]
|
16
|
+
attr_accessor :default_max_runtime
|
17
|
+
|
18
|
+
# tz database style timezone string
|
19
|
+
# @return [String, nil]
|
20
|
+
attr_accessor :default_timezone
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
module Sentry
|
2
2
|
module Cron
|
3
3
|
module MonitorCheckIns
|
4
|
+
MAX_SLUG_LENGTH = 50
|
5
|
+
|
4
6
|
module Patch
|
5
|
-
def perform(*args)
|
6
|
-
slug = self.class.sentry_monitor_slug
|
7
|
+
def perform(*args, **opts)
|
8
|
+
slug = self.class.sentry_monitor_slug
|
7
9
|
monitor_config = self.class.sentry_monitor_config
|
8
10
|
|
9
11
|
check_in_id = Sentry.capture_check_in(slug,
|
@@ -11,39 +13,53 @@ module Sentry
|
|
11
13
|
monitor_config: monitor_config)
|
12
14
|
|
13
15
|
start = Sentry.utc_now.to_i
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
16
|
+
|
17
|
+
begin
|
18
|
+
# need to do this on ruby <= 2.6 sadly
|
19
|
+
ret = method(:perform).super_method.arity == 0 ? super() : super
|
20
|
+
duration = Sentry.utc_now.to_i - start
|
21
|
+
|
22
|
+
Sentry.capture_check_in(slug,
|
23
|
+
:ok,
|
24
|
+
check_in_id: check_in_id,
|
25
|
+
duration: duration,
|
26
|
+
monitor_config: monitor_config)
|
27
|
+
|
28
|
+
ret
|
29
|
+
rescue Exception
|
30
|
+
duration = Sentry.utc_now.to_i - start
|
31
|
+
|
32
|
+
Sentry.capture_check_in(slug,
|
33
|
+
:error,
|
34
|
+
check_in_id: check_in_id,
|
35
|
+
duration: duration,
|
36
|
+
monitor_config: monitor_config)
|
37
|
+
|
38
|
+
raise
|
39
|
+
end
|
34
40
|
end
|
35
41
|
end
|
36
42
|
|
37
43
|
module ClassMethods
|
38
44
|
def sentry_monitor_check_ins(slug: nil, monitor_config: nil)
|
45
|
+
if monitor_config && Sentry.configuration
|
46
|
+
cron_config = Sentry.configuration.cron
|
47
|
+
monitor_config.checkin_margin ||= cron_config.default_checkin_margin
|
48
|
+
monitor_config.max_runtime ||= cron_config.default_max_runtime
|
49
|
+
monitor_config.timezone ||= cron_config.default_timezone
|
50
|
+
end
|
51
|
+
|
39
52
|
@sentry_monitor_slug = slug
|
40
53
|
@sentry_monitor_config = monitor_config
|
41
54
|
|
42
55
|
prepend Patch
|
43
56
|
end
|
44
57
|
|
45
|
-
def sentry_monitor_slug
|
46
|
-
@sentry_monitor_slug
|
58
|
+
def sentry_monitor_slug(name: self.name)
|
59
|
+
@sentry_monitor_slug ||= begin
|
60
|
+
slug = name.gsub('::', '-').downcase
|
61
|
+
slug[-MAX_SLUG_LENGTH..-1] || slug
|
62
|
+
end
|
47
63
|
end
|
48
64
|
|
49
65
|
def sentry_monitor_config
|
@@ -51,8 +67,6 @@ module Sentry
|
|
51
67
|
end
|
52
68
|
end
|
53
69
|
|
54
|
-
extend ClassMethods
|
55
|
-
|
56
70
|
def self.included(base)
|
57
71
|
base.extend(ClassMethods)
|
58
72
|
end
|
data/lib/sentry/dsn.rb
CHANGED
@@ -5,7 +5,7 @@ require "uri"
|
|
5
5
|
module Sentry
|
6
6
|
class DSN
|
7
7
|
PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
|
8
|
-
REQUIRED_ATTRIBUTES = %w
|
8
|
+
REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
|
9
9
|
|
10
10
|
attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
|
11
11
|
|
data/lib/sentry/envelope.rb
CHANGED
@@ -18,8 +18,25 @@ module Sentry
|
|
18
18
|
@headers[:type] || 'event'
|
19
19
|
end
|
20
20
|
|
21
|
+
# rate limits and client reports use the data_category rather than envelope item type
|
22
|
+
def self.data_category(type)
|
23
|
+
case type
|
24
|
+
when 'session', 'attachment', 'transaction', 'profile', 'span' then type
|
25
|
+
when 'sessions' then 'session'
|
26
|
+
when 'check_in' then 'monitor'
|
27
|
+
when 'statsd', 'metric_meta' then 'metric_bucket'
|
28
|
+
when 'event' then 'error'
|
29
|
+
when 'client_report' then 'internal'
|
30
|
+
else 'default'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def data_category
|
35
|
+
self.class.data_category(type)
|
36
|
+
end
|
37
|
+
|
21
38
|
def to_s
|
22
|
-
[JSON.generate(@headers), JSON.generate(@payload)].join("\n")
|
39
|
+
[JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
|
23
40
|
end
|
24
41
|
|
25
42
|
def serialize
|
data/lib/sentry/error_event.rb
CHANGED
@@ -27,12 +27,12 @@ module Sentry
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# @!visibility private
|
30
|
-
def add_exception_interface(exception)
|
30
|
+
def add_exception_interface(exception, mechanism:)
|
31
31
|
if exception.respond_to?(:sentry_context)
|
32
32
|
@extra.merge!(exception.sentry_context)
|
33
33
|
end
|
34
34
|
|
35
|
-
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
|
35
|
+
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder, mechanism: mechanism)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
data/lib/sentry/event.rb
CHANGED
@@ -14,16 +14,16 @@ module Sentry
|
|
14
14
|
class Event
|
15
15
|
TYPE = "event"
|
16
16
|
# These are readable attributes.
|
17
|
-
SERIALIZEABLE_ATTRIBUTES = %i
|
17
|
+
SERIALIZEABLE_ATTRIBUTES = %i[
|
18
18
|
event_id level timestamp
|
19
19
|
release environment server_name modules
|
20
20
|
message user tags contexts extra
|
21
21
|
fingerprint breadcrumbs transaction transaction_info
|
22
22
|
platform sdk type
|
23
|
-
|
23
|
+
]
|
24
24
|
|
25
25
|
# These are writable attributes.
|
26
|
-
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i
|
26
|
+
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i[type timestamp level]
|
27
27
|
|
28
28
|
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
|
29
29
|
|
@@ -42,6 +42,9 @@ module Sentry
|
|
42
42
|
# @return [Hash, nil]
|
43
43
|
attr_accessor :dynamic_sampling_context
|
44
44
|
|
45
|
+
# @return [Array<Attachment>]
|
46
|
+
attr_accessor :attachments
|
47
|
+
|
45
48
|
# @param configuration [Configuration]
|
46
49
|
# @param integration_meta [Hash, nil]
|
47
50
|
# @param message [String, nil]
|
@@ -57,6 +60,7 @@ module Sentry
|
|
57
60
|
@extra = {}
|
58
61
|
@contexts = {}
|
59
62
|
@tags = {}
|
63
|
+
@attachments = []
|
60
64
|
|
61
65
|
@fingerprint = []
|
62
66
|
@dynamic_sampling_context = nil
|
@@ -76,34 +80,6 @@ module Sentry
|
|
76
80
|
@message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
|
77
81
|
end
|
78
82
|
|
79
|
-
class << self
|
80
|
-
# @!visibility private
|
81
|
-
def get_log_message(event_hash)
|
82
|
-
message = event_hash[:message] || event_hash['message']
|
83
|
-
|
84
|
-
return message unless message.nil? || message.empty?
|
85
|
-
|
86
|
-
message = get_message_from_exception(event_hash)
|
87
|
-
|
88
|
-
return message unless message.nil? || message.empty?
|
89
|
-
|
90
|
-
message = event_hash[:transaction] || event_hash["transaction"]
|
91
|
-
|
92
|
-
return message unless message.nil? || message.empty?
|
93
|
-
|
94
|
-
'<no message value>'
|
95
|
-
end
|
96
|
-
|
97
|
-
# @!visibility private
|
98
|
-
def get_message_from_exception(event_hash)
|
99
|
-
if exception = event_hash.dig(:exception, :values, 0)
|
100
|
-
"#{exception[:type]}: #{exception[:value]}"
|
101
|
-
elsif exception = event_hash.dig("exception", "values", 0)
|
102
|
-
"#{exception["type"]}: #{exception["value"]}"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
83
|
# @deprecated This method will be removed in v5.0.0. Please just use Sentry.configuration
|
108
84
|
# @return [Configuration]
|
109
85
|
def configuration
|
@@ -132,9 +108,7 @@ module Sentry
|
|
132
108
|
unless request || env.empty?
|
133
109
|
add_request_interface(env)
|
134
110
|
|
135
|
-
if @send_default_pii
|
136
|
-
user[:ip_address] = calculate_real_ip_from_rack(env)
|
137
|
-
end
|
111
|
+
user[:ip_address] ||= calculate_real_ip_from_rack(env) if @send_default_pii
|
138
112
|
|
139
113
|
if request_id = Utils::RequestId.read_from(env)
|
140
114
|
tags[:request_id] = request_id
|
@@ -173,11 +147,11 @@ module Sentry
|
|
173
147
|
# REMOTE_ADDR to determine the Event IP, and must use other headers instead.
|
174
148
|
def calculate_real_ip_from_rack(env)
|
175
149
|
Utils::RealIp.new(
|
176
|
-
:
|
177
|
-
:
|
178
|
-
:
|
179
|
-
:
|
180
|
-
:
|
150
|
+
remote_addr: env["REMOTE_ADDR"],
|
151
|
+
client_ip: env["HTTP_CLIENT_IP"],
|
152
|
+
real_ip: env["HTTP_X_REAL_IP"],
|
153
|
+
forwarded_for: env["HTTP_X_FORWARDED_FOR"],
|
154
|
+
trusted_proxies: @trusted_proxies
|
181
155
|
).calculate_ip
|
182
156
|
end
|
183
157
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Faraday
|
5
|
+
OP_NAME = "http.client"
|
6
|
+
|
7
|
+
module Connection
|
8
|
+
# Since there's no way to preconfigure Faraday connections and add our instrumentation
|
9
|
+
# by default, we need to extend the connection constructor and do it there
|
10
|
+
#
|
11
|
+
# @see https://lostisland.github.io/faraday/#/customization/index?id=configuration
|
12
|
+
def initialize(url = nil, options = nil)
|
13
|
+
super
|
14
|
+
|
15
|
+
# Ensure that we attach instrumentation only if the adapter is not net/http
|
16
|
+
# because if is is, then the net/http instrumentation will take care of it
|
17
|
+
if builder.adapter.name != "Faraday::Adapter::NetHttp"
|
18
|
+
# Make sure that it's going to be the first middleware so that it can capture
|
19
|
+
# the entire request processing involving other middlewares
|
20
|
+
builder.insert(0, ::Faraday::Request::Instrumentation, name: OP_NAME, instrumenter: Instrumenter.new)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Instrumenter
|
26
|
+
SPAN_ORIGIN = "auto.http.faraday"
|
27
|
+
BREADCRUMB_CATEGORY = "http"
|
28
|
+
|
29
|
+
include Utils::HttpTracing
|
30
|
+
|
31
|
+
def instrument(op_name, env, &block)
|
32
|
+
return block.call unless Sentry.initialized?
|
33
|
+
|
34
|
+
Sentry.with_child_span(op: op_name, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
|
35
|
+
request_info = extract_request_info(env)
|
36
|
+
|
37
|
+
if propagate_trace?(request_info[:url])
|
38
|
+
set_propagation_headers(env[:request_headers])
|
39
|
+
end
|
40
|
+
|
41
|
+
res = block.call
|
42
|
+
response_status = res.status
|
43
|
+
|
44
|
+
if record_sentry_breadcrumb?
|
45
|
+
record_sentry_breadcrumb(request_info, response_status)
|
46
|
+
end
|
47
|
+
|
48
|
+
if sentry_span
|
49
|
+
set_span_info(sentry_span, request_info, response_status)
|
50
|
+
end
|
51
|
+
|
52
|
+
res
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def extract_request_info(env)
|
59
|
+
url = env[:url].scheme + "://" + env[:url].host + env[:url].path
|
60
|
+
result = { method: env[:method].to_s.upcase, url: url }
|
61
|
+
|
62
|
+
if Sentry.configuration.send_default_pii
|
63
|
+
result[:query] = env[:url].query
|
64
|
+
result[:body] = env[:body]
|
65
|
+
end
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
Sentry.register_patch(:faraday) do
|
74
|
+
if defined?(::Faraday)
|
75
|
+
::Faraday::Connection.prepend(Sentry::Faraday::Connection)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sentry.register_patch(:graphql) do |config|
|
4
|
+
if defined?(::GraphQL::Schema) && defined?(::GraphQL::Tracing::SentryTrace) && ::GraphQL::Schema.respond_to?(:trace_with)
|
5
|
+
::GraphQL::Schema.trace_with(::GraphQL::Tracing::SentryTrace, set_transaction_name: true)
|
6
|
+
else
|
7
|
+
config.logger.warn(Sentry::LOGGER_PROGNAME) { 'You tried to enable the GraphQL integration but no GraphQL gem was detected. Make sure you have the `graphql` gem (>= 2.2.6) in your Gemfile.' }
|
8
|
+
end
|
9
|
+
end
|
data/lib/sentry/hub.rb
CHANGED
@@ -156,7 +156,7 @@ module Sentry
|
|
156
156
|
capture_event(event, **options, &block)
|
157
157
|
end
|
158
158
|
|
159
|
-
def capture_check_in(slug, status, **options
|
159
|
+
def capture_check_in(slug, status, **options)
|
160
160
|
check_argument_type!(slug, ::String)
|
161
161
|
check_argument_includes!(status, Sentry::CheckInEvent::VALID_STATUSES)
|
162
162
|
|
@@ -176,7 +176,7 @@ module Sentry
|
|
176
176
|
|
177
177
|
return unless event
|
178
178
|
|
179
|
-
capture_event(event, **options
|
179
|
+
capture_event(event, **options)
|
180
180
|
event.check_in_id
|
181
181
|
end
|
182
182
|
|
@@ -193,7 +193,14 @@ module Sentry
|
|
193
193
|
elsif custom_scope = options[:scope]
|
194
194
|
scope.update_from_scope(custom_scope)
|
195
195
|
elsif !options.empty?
|
196
|
-
scope.update_from_options(**options)
|
196
|
+
unsupported_option_keys = scope.update_from_options(**options)
|
197
|
+
|
198
|
+
unless unsupported_option_keys.empty?
|
199
|
+
configuration.log_debug <<~MSG
|
200
|
+
Options #{unsupported_option_keys} are not supported and will not be applied to the event.
|
201
|
+
You may want to set them under the `extra` option.
|
202
|
+
MSG
|
203
|
+
end
|
197
204
|
end
|
198
205
|
|
199
206
|
event = current_client.capture_event(event, scope, hint)
|
@@ -245,7 +252,7 @@ module Sentry
|
|
245
252
|
end
|
246
253
|
|
247
254
|
def with_session_tracking(&block)
|
248
|
-
return yield unless configuration.
|
255
|
+
return yield unless configuration.session_tracking?
|
249
256
|
|
250
257
|
start_session
|
251
258
|
yield
|
@@ -279,6 +286,12 @@ module Sentry
|
|
279
286
|
headers
|
280
287
|
end
|
281
288
|
|
289
|
+
def get_trace_propagation_meta
|
290
|
+
get_trace_propagation_headers.map do |k, v|
|
291
|
+
"<meta name=\"#{k}\" content=\"#{v}\">"
|
292
|
+
end.join("\n")
|
293
|
+
end
|
294
|
+
|
282
295
|
def continue_trace(env, **options)
|
283
296
|
configure_scope { |s| s.generate_propagation_context(env) }
|
284
297
|
|
data/lib/sentry/integrable.rb
CHANGED
@@ -14,6 +14,10 @@ module Sentry
|
|
14
14
|
def capture_exception(exception, **options, &block)
|
15
15
|
options[:hint] ||= {}
|
16
16
|
options[:hint][:integration] = integration_name
|
17
|
+
|
18
|
+
# within an integration, we usually intercept uncaught exceptions so we set handled to false.
|
19
|
+
options[:hint][:mechanism] ||= Sentry::Mechanism.new(type: integration_name, handled: false)
|
20
|
+
|
17
21
|
Sentry.capture_exception(exception, **options, &block)
|
18
22
|
end
|
19
23
|
|
data/lib/sentry/interface.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "set"
|
3
4
|
|
4
5
|
module Sentry
|
@@ -23,17 +24,18 @@ module Sentry
|
|
23
24
|
# @param stacktrace_builder [StacktraceBuilder]
|
24
25
|
# @see SingleExceptionInterface#build_with_stacktrace
|
25
26
|
# @see SingleExceptionInterface#initialize
|
27
|
+
# @param mechanism [Mechanism]
|
26
28
|
# @return [ExceptionInterface]
|
27
|
-
def self.build(exception:, stacktrace_builder:)
|
29
|
+
def self.build(exception:, stacktrace_builder:, mechanism:)
|
28
30
|
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
|
29
31
|
processed_backtrace_ids = Set.new
|
30
32
|
|
31
33
|
exceptions = exceptions.map do |e|
|
32
34
|
if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
|
33
35
|
processed_backtrace_ids << e.backtrace.object_id
|
34
|
-
SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
|
36
|
+
SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder, mechanism: mechanism)
|
35
37
|
else
|
36
|
-
SingleExceptionInterface.new(exception: exception)
|
38
|
+
SingleExceptionInterface.new(exception: exception, mechanism: mechanism)
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
class Mechanism < Interface
|
5
|
+
# Generic identifier, mostly the source integration for this exception.
|
6
|
+
# @return [String]
|
7
|
+
attr_accessor :type
|
8
|
+
|
9
|
+
# A manually captured exception has handled set to true,
|
10
|
+
# false if coming from an integration where we intercept an uncaught exception.
|
11
|
+
# Defaults to true here and will be set to false explicitly in integrations.
|
12
|
+
# @return [Boolean]
|
13
|
+
attr_accessor :handled
|
14
|
+
|
15
|
+
def initialize(type: 'generic', handled: true)
|
16
|
+
@type = type
|
17
|
+
@handled = handled
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class RequestInterface < Interface
|
5
|
-
REQUEST_ID_HEADERS = %w
|
6
|
-
CONTENT_HEADERS = %w
|
5
|
+
REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
|
6
|
+
CONTENT_HEADERS = %w[CONTENT_TYPE CONTENT_LENGTH].freeze
|
7
7
|
IP_HEADERS = [
|
8
8
|
"REMOTE_ADDR",
|
9
9
|
"HTTP_CLIENT_IP",
|
@@ -11,10 +11,10 @@ module Sentry
|
|
11
11
|
OMISSION_MARK = "...".freeze
|
12
12
|
MAX_LOCAL_BYTES = 1024
|
13
13
|
|
14
|
-
attr_reader :type, :module, :thread_id, :stacktrace
|
14
|
+
attr_reader :type, :module, :thread_id, :stacktrace, :mechanism
|
15
15
|
attr_accessor :value
|
16
16
|
|
17
|
-
def initialize(exception:, stacktrace: nil)
|
17
|
+
def initialize(exception:, mechanism:, stacktrace: nil)
|
18
18
|
@type = exception.class.to_s
|
19
19
|
exception_message =
|
20
20
|
if exception.respond_to?(:detailed_message)
|
@@ -22,23 +22,26 @@ module Sentry
|
|
22
22
|
else
|
23
23
|
exception.message || ""
|
24
24
|
end
|
25
|
+
exception_message = exception_message.inspect unless exception_message.is_a?(String)
|
25
26
|
|
26
27
|
@value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
|
27
28
|
|
28
29
|
@module = exception.class.to_s.split('::')[0...-1].join('::')
|
29
30
|
@thread_id = Thread.current.object_id
|
30
31
|
@stacktrace = stacktrace
|
32
|
+
@mechanism = mechanism
|
31
33
|
end
|
32
34
|
|
33
35
|
def to_hash
|
34
36
|
data = super
|
35
37
|
data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
|
38
|
+
data[:mechanism] = data[:mechanism].to_hash
|
36
39
|
data
|
37
40
|
end
|
38
41
|
|
39
42
|
# patch this method if you want to change an exception's stacktrace frames
|
40
43
|
# also see `StacktraceBuilder.build`.
|
41
|
-
def self.build_with_stacktrace(exception:, stacktrace_builder:)
|
44
|
+
def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:)
|
42
45
|
stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
|
43
46
|
|
44
47
|
if locals = exception.instance_variable_get(:@sentry_locals)
|
@@ -60,7 +63,7 @@ module Sentry
|
|
60
63
|
stacktrace.frames.last.vars = locals
|
61
64
|
end
|
62
65
|
|
63
|
-
new(exception: exception, stacktrace: stacktrace)
|
66
|
+
new(exception: exception, stacktrace: stacktrace, mechanism: mechanism)
|
64
67
|
end
|
65
68
|
end
|
66
69
|
end
|
@@ -62,6 +62,14 @@ module Sentry
|
|
62
62
|
StacktraceInterface.new(frames: frames)
|
63
63
|
end
|
64
64
|
|
65
|
+
# Get the code location hash for a single line for where metrics where added.
|
66
|
+
# @return [Hash]
|
67
|
+
def metrics_code_location(unparsed_line)
|
68
|
+
parsed_line = Backtrace::Line.parse(unparsed_line)
|
69
|
+
frame = convert_parsed_line_into_frame(parsed_line)
|
70
|
+
frame.to_hash.reject { |k, _| %i[project_root in_app].include?(k) }
|
71
|
+
end
|
72
|
+
|
65
73
|
private
|
66
74
|
|
67
75
|
def convert_parsed_line_into_frame(line)
|