sentry-ruby 5.10.0 → 5.16.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/Gemfile +2 -13
- data/README.md +9 -9
- data/lib/sentry/background_worker.rb +8 -1
- data/lib/sentry/backpressure_monitor.rb +75 -0
- data/lib/sentry/breadcrumb.rb +8 -2
- data/lib/sentry/check_in_event.rb +60 -0
- data/lib/sentry/client.rb +48 -10
- data/lib/sentry/configuration.rb +50 -4
- data/lib/sentry/cron/configuration.rb +23 -0
- data/lib/sentry/cron/monitor_check_ins.rb +75 -0
- data/lib/sentry/cron/monitor_config.rb +53 -0
- data/lib/sentry/cron/monitor_schedule.rb +42 -0
- data/lib/sentry/envelope.rb +1 -1
- data/lib/sentry/event.rb +6 -28
- data/lib/sentry/hub.rb +69 -1
- data/lib/sentry/integrable.rb +6 -0
- data/lib/sentry/interfaces/single_exception.rb +5 -3
- data/lib/sentry/net/http.rb +25 -22
- data/lib/sentry/profiler.rb +18 -7
- data/lib/sentry/propagation_context.rb +134 -0
- data/lib/sentry/puma.rb +11 -4
- data/lib/sentry/rack/capture_exceptions.rb +1 -4
- data/lib/sentry/rake.rb +0 -13
- data/lib/sentry/redis.rb +8 -3
- data/lib/sentry/release_detector.rb +1 -1
- data/lib/sentry/scope.rb +29 -13
- data/lib/sentry/span.rb +39 -2
- data/lib/sentry/test_helper.rb +18 -12
- data/lib/sentry/transaction.rb +18 -19
- data/lib/sentry/transaction_event.rb +0 -3
- data/lib/sentry/transport/configuration.rb +74 -1
- data/lib/sentry/transport/http_transport.rb +68 -37
- data/lib/sentry/transport/spotlight_transport.rb +50 -0
- data/lib/sentry/transport.rb +21 -17
- data/lib/sentry/utils/argument_checking_helper.rb +6 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +83 -25
- metadata +10 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sentry/cron/monitor_schedule'
|
|
4
|
+
|
|
5
|
+
module Sentry
|
|
6
|
+
module Cron
|
|
7
|
+
class MonitorConfig
|
|
8
|
+
# The monitor schedule configuration
|
|
9
|
+
# @return [MonitorSchedule::Crontab, MonitorSchedule::Interval]
|
|
10
|
+
attr_accessor :schedule
|
|
11
|
+
|
|
12
|
+
# How long (in minutes) after the expected checkin time will we wait
|
|
13
|
+
# until we consider the checkin to have been missed.
|
|
14
|
+
# @return [Integer, nil]
|
|
15
|
+
attr_accessor :checkin_margin
|
|
16
|
+
|
|
17
|
+
# How long (in minutes) is the checkin allowed to run for in in_progress
|
|
18
|
+
# before it is considered failed.
|
|
19
|
+
# @return [Integer, nil]
|
|
20
|
+
attr_accessor :max_runtime
|
|
21
|
+
|
|
22
|
+
# tz database style timezone string
|
|
23
|
+
# @return [String, nil]
|
|
24
|
+
attr_accessor :timezone
|
|
25
|
+
|
|
26
|
+
def initialize(schedule, checkin_margin: nil, max_runtime: nil, timezone: nil)
|
|
27
|
+
@schedule = schedule
|
|
28
|
+
@checkin_margin = checkin_margin
|
|
29
|
+
@max_runtime = max_runtime
|
|
30
|
+
@timezone = timezone
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.from_crontab(crontab, **options)
|
|
34
|
+
new(MonitorSchedule::Crontab.new(crontab), **options)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.from_interval(num, unit, **options)
|
|
38
|
+
return nil unless MonitorSchedule::Interval::VALID_UNITS.include?(unit)
|
|
39
|
+
|
|
40
|
+
new(MonitorSchedule::Interval.new(num, unit), **options)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_hash
|
|
44
|
+
{
|
|
45
|
+
schedule: schedule.to_hash,
|
|
46
|
+
checkin_margin: checkin_margin,
|
|
47
|
+
max_runtime: max_runtime,
|
|
48
|
+
timezone: timezone
|
|
49
|
+
}.compact
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Cron
|
|
5
|
+
module MonitorSchedule
|
|
6
|
+
class Crontab
|
|
7
|
+
# A crontab formatted string such as "0 * * * *".
|
|
8
|
+
# @return [String]
|
|
9
|
+
attr_accessor :value
|
|
10
|
+
|
|
11
|
+
def initialize(value)
|
|
12
|
+
@value = value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_hash
|
|
16
|
+
{ type: :crontab, value: value }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Interval
|
|
21
|
+
# The number representing duration of the interval.
|
|
22
|
+
# @return [Integer]
|
|
23
|
+
attr_accessor :value
|
|
24
|
+
|
|
25
|
+
# The unit representing duration of the interval.
|
|
26
|
+
# @return [Symbol]
|
|
27
|
+
attr_accessor :unit
|
|
28
|
+
|
|
29
|
+
VALID_UNITS = %i(year month week day hour minute)
|
|
30
|
+
|
|
31
|
+
def initialize(value, unit)
|
|
32
|
+
@value = value
|
|
33
|
+
@unit = unit
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_hash
|
|
37
|
+
{ type: :interval, value: value, unit: unit }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/sentry/envelope.rb
CHANGED
data/lib/sentry/event.rb
CHANGED
|
@@ -37,6 +37,11 @@ module Sentry
|
|
|
37
37
|
# @return [RequestInterface]
|
|
38
38
|
attr_reader :request
|
|
39
39
|
|
|
40
|
+
# Dynamic Sampling Context (DSC) that gets attached
|
|
41
|
+
# as the trace envelope header in the transport.
|
|
42
|
+
# @return [Hash, nil]
|
|
43
|
+
attr_accessor :dynamic_sampling_context
|
|
44
|
+
|
|
40
45
|
# @param configuration [Configuration]
|
|
41
46
|
# @param integration_meta [Hash, nil]
|
|
42
47
|
# @param message [String, nil]
|
|
@@ -54,6 +59,7 @@ module Sentry
|
|
|
54
59
|
@tags = {}
|
|
55
60
|
|
|
56
61
|
@fingerprint = []
|
|
62
|
+
@dynamic_sampling_context = nil
|
|
57
63
|
|
|
58
64
|
# configuration data that's directly used by events
|
|
59
65
|
@server_name = configuration.server_name
|
|
@@ -70,34 +76,6 @@ module Sentry
|
|
|
70
76
|
@message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
|
|
71
77
|
end
|
|
72
78
|
|
|
73
|
-
class << self
|
|
74
|
-
# @!visibility private
|
|
75
|
-
def get_log_message(event_hash)
|
|
76
|
-
message = event_hash[:message] || event_hash['message']
|
|
77
|
-
|
|
78
|
-
return message unless message.nil? || message.empty?
|
|
79
|
-
|
|
80
|
-
message = get_message_from_exception(event_hash)
|
|
81
|
-
|
|
82
|
-
return message unless message.nil? || message.empty?
|
|
83
|
-
|
|
84
|
-
message = event_hash[:transaction] || event_hash["transaction"]
|
|
85
|
-
|
|
86
|
-
return message unless message.nil? || message.empty?
|
|
87
|
-
|
|
88
|
-
'<no message value>'
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# @!visibility private
|
|
92
|
-
def get_message_from_exception(event_hash)
|
|
93
|
-
if exception = event_hash.dig(:exception, :values, 0)
|
|
94
|
-
"#{exception[:type]}: #{exception[:value]}"
|
|
95
|
-
elsif exception = event_hash.dig("exception", "values", 0)
|
|
96
|
-
"#{exception["type"]}: #{exception["value"]}"
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
79
|
# @deprecated This method will be removed in v5.0.0. Please just use Sentry.configuration
|
|
102
80
|
# @return [Configuration]
|
|
103
81
|
def configuration
|
data/lib/sentry/hub.rb
CHANGED
|
@@ -156,6 +156,30 @@ module Sentry
|
|
|
156
156
|
capture_event(event, **options, &block)
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
+
def capture_check_in(slug, status, **options)
|
|
160
|
+
check_argument_type!(slug, ::String)
|
|
161
|
+
check_argument_includes!(status, Sentry::CheckInEvent::VALID_STATUSES)
|
|
162
|
+
|
|
163
|
+
return unless current_client
|
|
164
|
+
|
|
165
|
+
options[:hint] ||= {}
|
|
166
|
+
options[:hint][:slug] = slug
|
|
167
|
+
|
|
168
|
+
event = current_client.event_from_check_in(
|
|
169
|
+
slug,
|
|
170
|
+
status,
|
|
171
|
+
options[:hint],
|
|
172
|
+
duration: options.delete(:duration),
|
|
173
|
+
monitor_config: options.delete(:monitor_config),
|
|
174
|
+
check_in_id: options.delete(:check_in_id)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return unless event
|
|
178
|
+
|
|
179
|
+
capture_event(event, **options)
|
|
180
|
+
event.check_in_id
|
|
181
|
+
end
|
|
182
|
+
|
|
159
183
|
def capture_event(event, **options, &block)
|
|
160
184
|
check_argument_type!(event, Sentry::Event)
|
|
161
185
|
|
|
@@ -178,7 +202,7 @@ module Sentry
|
|
|
178
202
|
configuration.log_debug(event.to_json_compatible)
|
|
179
203
|
end
|
|
180
204
|
|
|
181
|
-
@last_event_id = event&.event_id
|
|
205
|
+
@last_event_id = event&.event_id if event.is_a?(Sentry::ErrorEvent)
|
|
182
206
|
event
|
|
183
207
|
end
|
|
184
208
|
|
|
@@ -229,6 +253,50 @@ module Sentry
|
|
|
229
253
|
end_session
|
|
230
254
|
end
|
|
231
255
|
|
|
256
|
+
def get_traceparent
|
|
257
|
+
return nil unless current_scope
|
|
258
|
+
|
|
259
|
+
current_scope.get_span&.to_sentry_trace ||
|
|
260
|
+
current_scope.propagation_context.get_traceparent
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def get_baggage
|
|
264
|
+
return nil unless current_scope
|
|
265
|
+
|
|
266
|
+
current_scope.get_span&.to_baggage ||
|
|
267
|
+
current_scope.propagation_context.get_baggage&.serialize
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def get_trace_propagation_headers
|
|
271
|
+
headers = {}
|
|
272
|
+
|
|
273
|
+
traceparent = get_traceparent
|
|
274
|
+
headers[SENTRY_TRACE_HEADER_NAME] = traceparent if traceparent
|
|
275
|
+
|
|
276
|
+
baggage = get_baggage
|
|
277
|
+
headers[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
|
|
278
|
+
|
|
279
|
+
headers
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def continue_trace(env, **options)
|
|
283
|
+
configure_scope { |s| s.generate_propagation_context(env) }
|
|
284
|
+
|
|
285
|
+
return nil unless configuration.tracing_enabled?
|
|
286
|
+
|
|
287
|
+
propagation_context = current_scope.propagation_context
|
|
288
|
+
return nil unless propagation_context.incoming_trace
|
|
289
|
+
|
|
290
|
+
Transaction.new(
|
|
291
|
+
hub: self,
|
|
292
|
+
trace_id: propagation_context.trace_id,
|
|
293
|
+
parent_span_id: propagation_context.parent_span_id,
|
|
294
|
+
parent_sampled: propagation_context.parent_sampled,
|
|
295
|
+
baggage: propagation_context.baggage,
|
|
296
|
+
**options
|
|
297
|
+
)
|
|
298
|
+
end
|
|
299
|
+
|
|
232
300
|
private
|
|
233
301
|
|
|
234
302
|
def current_layer
|
data/lib/sentry/integrable.rb
CHANGED
|
@@ -22,5 +22,11 @@ module Sentry
|
|
|
22
22
|
options[:hint][:integration] = integration_name
|
|
23
23
|
Sentry.capture_message(message, **options, &block)
|
|
24
24
|
end
|
|
25
|
+
|
|
26
|
+
def capture_check_in(slug, status, **options, &block)
|
|
27
|
+
options[:hint] ||= {}
|
|
28
|
+
options[:hint][:integration] = integration_name
|
|
29
|
+
Sentry.capture_check_in(slug, status, **options, &block)
|
|
30
|
+
end
|
|
25
31
|
end
|
|
26
32
|
end
|
|
@@ -11,7 +11,8 @@ module Sentry
|
|
|
11
11
|
OMISSION_MARK = "...".freeze
|
|
12
12
|
MAX_LOCAL_BYTES = 1024
|
|
13
13
|
|
|
14
|
-
attr_reader :type, :
|
|
14
|
+
attr_reader :type, :module, :thread_id, :stacktrace
|
|
15
|
+
attr_accessor :value
|
|
15
16
|
|
|
16
17
|
def initialize(exception:, stacktrace: nil)
|
|
17
18
|
@type = exception.class.to_s
|
|
@@ -21,8 +22,9 @@ module Sentry
|
|
|
21
22
|
else
|
|
22
23
|
exception.message || ""
|
|
23
24
|
end
|
|
25
|
+
exception_message = exception_message.inspect unless exception_message.is_a?(String)
|
|
24
26
|
|
|
25
|
-
@value = exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
|
|
27
|
+
@value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
|
|
26
28
|
|
|
27
29
|
@module = exception.class.to_s.split('::')[0...-1].join('::')
|
|
28
30
|
@thread_id = Thread.current.object_id
|
|
@@ -50,7 +52,7 @@ module Sentry
|
|
|
50
52
|
v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
v
|
|
55
|
+
Utils::EncodingHelper.encode_to_utf_8(v)
|
|
54
56
|
rescue StandardError
|
|
55
57
|
PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
|
|
56
58
|
end
|
data/lib/sentry/net/http.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "net/http"
|
|
4
|
+
require "resolv"
|
|
4
5
|
|
|
5
6
|
module Sentry
|
|
6
7
|
# @api private
|
|
@@ -30,18 +31,21 @@ module Sentry
|
|
|
30
31
|
return super if from_sentry_sdk?
|
|
31
32
|
|
|
32
33
|
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
|
|
33
|
-
|
|
34
|
+
request_info = extract_request_info(req)
|
|
35
|
+
|
|
36
|
+
if propagate_trace?(request_info[:url], Sentry.configuration)
|
|
37
|
+
set_propagation_headers(req)
|
|
38
|
+
end
|
|
34
39
|
|
|
35
40
|
super.tap do |res|
|
|
36
|
-
record_sentry_breadcrumb(
|
|
41
|
+
record_sentry_breadcrumb(request_info, res)
|
|
37
42
|
|
|
38
43
|
if sentry_span
|
|
39
|
-
request_info = extract_request_info(req)
|
|
40
44
|
sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
|
41
|
-
sentry_span.set_data(
|
|
42
|
-
sentry_span.set_data(
|
|
43
|
-
sentry_span.set_data(
|
|
44
|
-
sentry_span.set_data(
|
|
45
|
+
sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
|
|
46
|
+
sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
|
|
47
|
+
sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
|
|
48
|
+
sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
end
|
|
@@ -49,23 +53,13 @@ module Sentry
|
|
|
49
53
|
|
|
50
54
|
private
|
|
51
55
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
client = Sentry.get_current_client
|
|
56
|
-
|
|
57
|
-
trace = client.generate_sentry_trace(sentry_span)
|
|
58
|
-
req[SENTRY_TRACE_HEADER_NAME] = trace if trace
|
|
59
|
-
|
|
60
|
-
baggage = client.generate_baggage(sentry_span)
|
|
61
|
-
req[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
|
|
56
|
+
def set_propagation_headers(req)
|
|
57
|
+
Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
|
|
62
58
|
end
|
|
63
59
|
|
|
64
|
-
def record_sentry_breadcrumb(
|
|
60
|
+
def record_sentry_breadcrumb(request_info, res)
|
|
65
61
|
return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
|
66
62
|
|
|
67
|
-
request_info = extract_request_info(req)
|
|
68
|
-
|
|
69
63
|
crumb = Sentry::Breadcrumb.new(
|
|
70
64
|
level: :info,
|
|
71
65
|
category: BREADCRUMB_CATEGORY,
|
|
@@ -84,7 +78,10 @@ module Sentry
|
|
|
84
78
|
end
|
|
85
79
|
|
|
86
80
|
def extract_request_info(req)
|
|
87
|
-
|
|
81
|
+
# IPv6 url could look like '::1/path', and that won't parse without
|
|
82
|
+
# wrapping it in square brackets.
|
|
83
|
+
hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
|
|
84
|
+
uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
|
|
88
85
|
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
|
89
86
|
|
|
90
87
|
result = { method: req.method, url: url }
|
|
@@ -96,8 +93,14 @@ module Sentry
|
|
|
96
93
|
|
|
97
94
|
result
|
|
98
95
|
end
|
|
96
|
+
|
|
97
|
+
def propagate_trace?(url, configuration)
|
|
98
|
+
url &&
|
|
99
|
+
configuration.propagate_traces &&
|
|
100
|
+
configuration.trace_propagation_targets.any? { |target| url.match?(target) }
|
|
101
|
+
end
|
|
99
102
|
end
|
|
100
103
|
end
|
|
101
104
|
end
|
|
102
105
|
|
|
103
|
-
Sentry.register_patch(Sentry::Net::HTTP, Net::HTTP)
|
|
106
|
+
Sentry.register_patch(:http, Sentry::Net::HTTP, Net::HTTP)
|
data/lib/sentry/profiler.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Sentry
|
|
|
9
9
|
# 101 Hz in microseconds
|
|
10
10
|
DEFAULT_INTERVAL = 1e6 / 101
|
|
11
11
|
MICRO_TO_NANO_SECONDS = 1e3
|
|
12
|
+
MIN_SAMPLES_REQUIRED = 2
|
|
12
13
|
|
|
13
14
|
attr_reader :sampled, :started, :event_id
|
|
14
15
|
|
|
@@ -73,14 +74,19 @@ module Sentry
|
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
def to_hash
|
|
76
|
-
|
|
77
|
+
unless @sampled
|
|
78
|
+
record_lost_event(:sample_rate)
|
|
79
|
+
return {}
|
|
80
|
+
end
|
|
81
|
+
|
|
77
82
|
return {} unless @started
|
|
78
83
|
|
|
79
84
|
results = StackProf.results
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
|
|
86
|
+
if !results || results.empty? || results[:samples] == 0 || !results[:raw]
|
|
87
|
+
record_lost_event(:insufficient_data)
|
|
88
|
+
return {}
|
|
89
|
+
end
|
|
84
90
|
|
|
85
91
|
frame_map = {}
|
|
86
92
|
|
|
@@ -103,7 +109,7 @@ module Sentry
|
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
frame_hash[:module] = mod if mod
|
|
106
|
-
frame_hash[:lineno] = frame_data[:line] if frame_data[:line]
|
|
112
|
+
frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
|
|
107
113
|
|
|
108
114
|
frame_hash
|
|
109
115
|
end
|
|
@@ -157,8 +163,9 @@ module Sentry
|
|
|
157
163
|
|
|
158
164
|
log('Some samples thrown away') if samples.size != results[:samples]
|
|
159
165
|
|
|
160
|
-
if samples.size <=
|
|
166
|
+
if samples.size <= MIN_SAMPLES_REQUIRED
|
|
161
167
|
log('Not enough samples, discarding profiler')
|
|
168
|
+
record_lost_event(:insufficient_data)
|
|
162
169
|
return {}
|
|
163
170
|
end
|
|
164
171
|
|
|
@@ -218,5 +225,9 @@ module Sentry
|
|
|
218
225
|
|
|
219
226
|
[function, mod]
|
|
220
227
|
end
|
|
228
|
+
|
|
229
|
+
def record_lost_event(reason)
|
|
230
|
+
Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
|
|
231
|
+
end
|
|
221
232
|
end
|
|
222
233
|
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "sentry/baggage"
|
|
5
|
+
|
|
6
|
+
module Sentry
|
|
7
|
+
class PropagationContext
|
|
8
|
+
SENTRY_TRACE_REGEXP = Regexp.new(
|
|
9
|
+
"^[ \t]*" + # whitespace
|
|
10
|
+
"([0-9a-f]{32})?" + # trace_id
|
|
11
|
+
"-?([0-9a-f]{16})?" + # span_id
|
|
12
|
+
"-?([01])?" + # sampled
|
|
13
|
+
"[ \t]*$" # whitespace
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# An uuid that can be used to identify a trace.
|
|
17
|
+
# @return [String]
|
|
18
|
+
attr_reader :trace_id
|
|
19
|
+
# An uuid that can be used to identify the span.
|
|
20
|
+
# @return [String]
|
|
21
|
+
attr_reader :span_id
|
|
22
|
+
# Span parent's span_id.
|
|
23
|
+
# @return [String, nil]
|
|
24
|
+
attr_reader :parent_span_id
|
|
25
|
+
# The sampling decision of the parent transaction.
|
|
26
|
+
# @return [Boolean, nil]
|
|
27
|
+
attr_reader :parent_sampled
|
|
28
|
+
# Is there an incoming trace or not?
|
|
29
|
+
# @return [Boolean]
|
|
30
|
+
attr_reader :incoming_trace
|
|
31
|
+
# This is only for accessing the current baggage variable.
|
|
32
|
+
# Please use the #get_baggage method for interfacing outside this class.
|
|
33
|
+
# @return [Baggage, nil]
|
|
34
|
+
attr_reader :baggage
|
|
35
|
+
|
|
36
|
+
def initialize(scope, env = nil)
|
|
37
|
+
@scope = scope
|
|
38
|
+
@parent_span_id = nil
|
|
39
|
+
@parent_sampled = nil
|
|
40
|
+
@baggage = nil
|
|
41
|
+
@incoming_trace = false
|
|
42
|
+
|
|
43
|
+
if env
|
|
44
|
+
sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
|
|
45
|
+
baggage_header = env["HTTP_BAGGAGE"] || env[BAGGAGE_HEADER_NAME]
|
|
46
|
+
|
|
47
|
+
if sentry_trace_header
|
|
48
|
+
sentry_trace_data = self.class.extract_sentry_trace(sentry_trace_header)
|
|
49
|
+
|
|
50
|
+
if sentry_trace_data
|
|
51
|
+
@trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
|
|
52
|
+
|
|
53
|
+
@baggage = if baggage_header && !baggage_header.empty?
|
|
54
|
+
Baggage.from_incoming_header(baggage_header)
|
|
55
|
+
else
|
|
56
|
+
# If there's an incoming sentry-trace but no incoming baggage header,
|
|
57
|
+
# for instance in traces coming from older SDKs,
|
|
58
|
+
# baggage will be empty and frozen and won't be populated as head SDK.
|
|
59
|
+
Baggage.new({})
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@baggage.freeze!
|
|
63
|
+
@incoming_trace = true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
@trace_id ||= SecureRandom.uuid.delete("-")
|
|
69
|
+
@span_id = SecureRandom.uuid.delete("-").slice(0, 16)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
|
|
73
|
+
#
|
|
74
|
+
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
|
75
|
+
# @return [Array, nil]
|
|
76
|
+
def self.extract_sentry_trace(sentry_trace)
|
|
77
|
+
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
|
78
|
+
return nil if match.nil?
|
|
79
|
+
|
|
80
|
+
trace_id, parent_span_id, sampled_flag = match[1..3]
|
|
81
|
+
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
|
82
|
+
|
|
83
|
+
[trace_id, parent_span_id, parent_sampled]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the trace context that can be used to embed in an Event.
|
|
87
|
+
# @return [Hash]
|
|
88
|
+
def get_trace_context
|
|
89
|
+
{
|
|
90
|
+
trace_id: trace_id,
|
|
91
|
+
span_id: span_id,
|
|
92
|
+
parent_span_id: parent_span_id
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the sentry-trace header from the propagation context.
|
|
97
|
+
# @return [String]
|
|
98
|
+
def get_traceparent
|
|
99
|
+
"#{trace_id}-#{span_id}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns the Baggage from the propagation context or populates as head SDK if empty.
|
|
103
|
+
# @return [Baggage, nil]
|
|
104
|
+
def get_baggage
|
|
105
|
+
populate_head_baggage if @baggage.nil? || @baggage.mutable
|
|
106
|
+
@baggage
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns the Dynamic Sampling Context from the baggage.
|
|
110
|
+
# @return [String, nil]
|
|
111
|
+
def get_dynamic_sampling_context
|
|
112
|
+
get_baggage&.dynamic_sampling_context
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def populate_head_baggage
|
|
118
|
+
return unless Sentry.initialized?
|
|
119
|
+
|
|
120
|
+
configuration = Sentry.configuration
|
|
121
|
+
|
|
122
|
+
items = {
|
|
123
|
+
"trace_id" => trace_id,
|
|
124
|
+
"environment" => configuration.environment,
|
|
125
|
+
"release" => configuration.release,
|
|
126
|
+
"public_key" => configuration.dsn&.public_key,
|
|
127
|
+
"user_segment" => @scope.user && @scope.user["segment"]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
items.compact!
|
|
131
|
+
@baggage = Baggage.new(items, mutable: false)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/sentry/puma.rb
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
return unless defined?(Puma::Server)
|
|
4
|
+
|
|
3
5
|
module Sentry
|
|
4
6
|
module Puma
|
|
5
7
|
module Server
|
|
8
|
+
PUMA_4_AND_PRIOR = Gem::Version.new(::Puma::Const::PUMA_VERSION) < Gem::Version.new("5.0.0")
|
|
9
|
+
|
|
6
10
|
def lowlevel_error(e, env, status=500)
|
|
7
|
-
result =
|
|
11
|
+
result =
|
|
12
|
+
if PUMA_4_AND_PRIOR
|
|
13
|
+
super(e, env)
|
|
14
|
+
else
|
|
15
|
+
super
|
|
16
|
+
end
|
|
8
17
|
|
|
9
18
|
begin
|
|
10
19
|
Sentry.capture_exception(e) do |scope|
|
|
@@ -20,6 +29,4 @@ module Sentry
|
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
Sentry.register_patch(Sentry::Puma::Server, Puma::Server)
|
|
25
|
-
end
|
|
32
|
+
Sentry.register_patch(:puma, Sentry::Puma::Server, Puma::Server)
|
|
@@ -62,11 +62,8 @@ module Sentry
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def start_transaction(env, scope)
|
|
65
|
-
sentry_trace = env["HTTP_SENTRY_TRACE"]
|
|
66
|
-
baggage = env["HTTP_BAGGAGE"]
|
|
67
|
-
|
|
68
65
|
options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
|
|
69
|
-
transaction = Sentry
|
|
66
|
+
transaction = Sentry.continue_trace(env, **options)
|
|
70
67
|
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
|
71
68
|
end
|
|
72
69
|
|
data/lib/sentry/rake.rb
CHANGED
|
@@ -17,15 +17,6 @@ module Sentry
|
|
|
17
17
|
super
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
-
|
|
21
|
-
module Task
|
|
22
|
-
# @api private
|
|
23
|
-
def execute(args=nil)
|
|
24
|
-
return super unless Sentry.initialized? && Sentry.get_current_hub
|
|
25
|
-
|
|
26
|
-
super
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
20
|
end
|
|
30
21
|
end
|
|
31
22
|
|
|
@@ -34,8 +25,4 @@ module Rake
|
|
|
34
25
|
class Application
|
|
35
26
|
prepend(Sentry::Rake::Application)
|
|
36
27
|
end
|
|
37
|
-
|
|
38
|
-
class Task
|
|
39
|
-
prepend(Sentry::Rake::Task)
|
|
40
|
-
end
|
|
41
28
|
end
|
data/lib/sentry/redis.rb
CHANGED
|
@@ -19,7 +19,10 @@ module Sentry
|
|
|
19
19
|
|
|
20
20
|
if span
|
|
21
21
|
span.set_description(commands_description)
|
|
22
|
-
span.set_data(
|
|
22
|
+
span.set_data(Span::DataConventions::DB_SYSTEM, "redis")
|
|
23
|
+
span.set_data(Span::DataConventions::DB_NAME, db)
|
|
24
|
+
span.set_data(Span::DataConventions::SERVER_ADDRESS, host)
|
|
25
|
+
span.set_data(Span::DataConventions::SERVER_PORT, port)
|
|
23
26
|
end
|
|
24
27
|
end
|
|
25
28
|
end
|
|
@@ -96,8 +99,10 @@ end
|
|
|
96
99
|
|
|
97
100
|
if defined?(::Redis::Client)
|
|
98
101
|
if Gem::Version.new(::Redis::VERSION) < Gem::Version.new("5.0")
|
|
99
|
-
Sentry.register_patch(Sentry::Redis::OldClientPatch, ::Redis::Client)
|
|
102
|
+
Sentry.register_patch(:redis, Sentry::Redis::OldClientPatch, ::Redis::Client)
|
|
100
103
|
elsif defined?(RedisClient)
|
|
101
|
-
|
|
104
|
+
Sentry.register_patch(:redis) do
|
|
105
|
+
RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
|
|
106
|
+
end
|
|
102
107
|
end
|
|
103
108
|
end
|