sentry-ruby-core 4.1.6 → 4.5.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/.craft.yml +3 -3
- data/CHANGELOG.md +171 -1
- data/Gemfile +6 -3
- data/README.md +39 -49
- data/lib/sentry-ruby.rb +30 -9
- data/lib/sentry/background_worker.rb +8 -4
- data/lib/sentry/breadcrumb.rb +7 -2
- data/lib/sentry/breadcrumb/sentry_logger.rb +2 -1
- data/lib/sentry/breadcrumb_buffer.rb +3 -2
- data/lib/sentry/client.rb +62 -28
- data/lib/sentry/configuration.rb +80 -17
- data/lib/sentry/event.rb +33 -45
- data/lib/sentry/exceptions.rb +7 -0
- data/lib/sentry/hub.rb +24 -4
- data/lib/sentry/interface.rb +1 -0
- 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 +32 -0
- data/lib/sentry/net/http.rb +126 -0
- data/lib/sentry/rack/capture_exceptions.rb +18 -14
- data/lib/sentry/rake.rb +1 -1
- data/lib/sentry/scope.rb +12 -6
- data/lib/sentry/span.rb +21 -4
- data/lib/sentry/transaction.rb +55 -43
- data/lib/sentry/transaction_event.rb +3 -1
- data/lib/sentry/transport.rb +58 -27
- 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 +13 -7
- data/lib/sentry/version.rb +1 -1
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +1 -1
- metadata +9 -4
@@ -0,0 +1,126 @@
|
|
1
|
+
require "net/http"
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Net
|
5
|
+
module HTTP
|
6
|
+
OP_NAME = "net.http"
|
7
|
+
|
8
|
+
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
|
9
|
+
# Here's part of its definition. As you can see, it usually calls itself inside a #start block
|
10
|
+
#
|
11
|
+
# ```
|
12
|
+
# def request(req, body = nil, &block)
|
13
|
+
# unless started?
|
14
|
+
# start {
|
15
|
+
# req['connection'] ||= 'close'
|
16
|
+
# return request(req, body, &block) # <- request will be called for the second time from the first call
|
17
|
+
# }
|
18
|
+
# end
|
19
|
+
# # .....
|
20
|
+
# end
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# So when the entire flow looks like this:
|
24
|
+
#
|
25
|
+
# 1. #request is called.
|
26
|
+
# - But because the request hasn't started yet, it calls #start (which then calls #do_start)
|
27
|
+
# - At this moment @sentry_span is still nil, so #set_sentry_trace_header returns early
|
28
|
+
# 2. #do_start then creates a new Span and assigns it to @sentry_span
|
29
|
+
# 3. #request is called for the second time.
|
30
|
+
# - This time @sentry_span should present. So #set_sentry_trace_header will set the sentry-trace header on the request object
|
31
|
+
# 4. Once the request finished, it
|
32
|
+
# - Records a breadcrumb if http_logger is set
|
33
|
+
# - Finishes the Span inside @sentry_span and clears the instance variable
|
34
|
+
#
|
35
|
+
def request(req, body = nil, &block)
|
36
|
+
set_sentry_trace_header(req)
|
37
|
+
|
38
|
+
super.tap do |res|
|
39
|
+
record_sentry_breadcrumb(req, res)
|
40
|
+
record_sentry_span(req, res)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def do_start
|
45
|
+
super.tap do
|
46
|
+
start_sentry_span
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def do_finish
|
51
|
+
super.tap do
|
52
|
+
finish_sentry_span
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def set_sentry_trace_header(req)
|
59
|
+
return unless @sentry_span
|
60
|
+
|
61
|
+
trace = Sentry.get_current_client.generate_sentry_trace(@sentry_span)
|
62
|
+
req[SENTRY_TRACE_HEADER_NAME] = trace if trace
|
63
|
+
end
|
64
|
+
|
65
|
+
def record_sentry_breadcrumb(req, res)
|
66
|
+
if Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
67
|
+
return if from_sentry_sdk?
|
68
|
+
|
69
|
+
request_info = extract_request_info(req)
|
70
|
+
crumb = Sentry::Breadcrumb.new(
|
71
|
+
level: :info,
|
72
|
+
category: OP_NAME,
|
73
|
+
type: :info,
|
74
|
+
data: {
|
75
|
+
method: request_info[:method],
|
76
|
+
url: request_info[:url],
|
77
|
+
status: res.code.to_i
|
78
|
+
}
|
79
|
+
)
|
80
|
+
Sentry.add_breadcrumb(crumb)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def record_sentry_span(req, res)
|
85
|
+
if Sentry.initialized? && @sentry_span
|
86
|
+
request_info = extract_request_info(req)
|
87
|
+
@sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
88
|
+
@sentry_span.set_data(:status, res.code.to_i)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def start_sentry_span
|
93
|
+
if Sentry.initialized? && transaction = Sentry.get_current_scope.get_transaction
|
94
|
+
return if from_sentry_sdk?
|
95
|
+
return if transaction.sampled == false
|
96
|
+
|
97
|
+
child_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
|
98
|
+
@sentry_span = child_span
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def finish_sentry_span
|
103
|
+
if Sentry.initialized? && @sentry_span
|
104
|
+
@sentry_span.set_timestamp(Sentry.utc_now.to_f)
|
105
|
+
@sentry_span = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def from_sentry_sdk?
|
110
|
+
dsn = Sentry.configuration.dsn
|
111
|
+
dsn && dsn.host == self.address
|
112
|
+
end
|
113
|
+
|
114
|
+
def extract_request_info(req)
|
115
|
+
uri = req.uri
|
116
|
+
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
117
|
+
{ method: req.method, url: url }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Sentry.register_patch do
|
124
|
+
patch = Sentry::Net::HTTP
|
125
|
+
Net::HTTP.send(:prepend, patch) unless Net::HTTP.ancestors.include?(patch)
|
126
|
+
end
|
@@ -16,30 +16,24 @@ module Sentry
|
|
16
16
|
scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
|
17
17
|
scope.set_rack_env(env)
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
Sentry::Transaction.from_sentry_trace(sentry_trace, name: scope.transaction_name, op: transaction_op)
|
22
|
-
else
|
23
|
-
Sentry.start_transaction(name: scope.transaction_name, op: transaction_op)
|
24
|
-
end
|
25
|
-
|
26
|
-
scope.set_span(span)
|
19
|
+
transaction = start_transaction(env, scope)
|
20
|
+
scope.set_span(transaction) if transaction
|
27
21
|
|
28
22
|
begin
|
29
23
|
response = @app.call(env)
|
30
24
|
rescue Sentry::Error
|
31
|
-
|
25
|
+
finish_transaction(transaction, 500)
|
32
26
|
raise # Don't capture Sentry errors
|
33
27
|
rescue Exception => e
|
34
28
|
capture_exception(e)
|
35
|
-
|
29
|
+
finish_transaction(transaction, 500)
|
36
30
|
raise
|
37
31
|
end
|
38
32
|
|
39
33
|
exception = collect_exception(env)
|
40
34
|
capture_exception(exception) if exception
|
41
35
|
|
42
|
-
|
36
|
+
finish_transaction(transaction, response[0])
|
43
37
|
|
44
38
|
response
|
45
39
|
end
|
@@ -59,9 +53,19 @@ module Sentry
|
|
59
53
|
Sentry.capture_exception(exception)
|
60
54
|
end
|
61
55
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
56
|
+
def start_transaction(env, scope)
|
57
|
+
sentry_trace = env["HTTP_SENTRY_TRACE"]
|
58
|
+
options = { name: scope.transaction_name, op: transaction_op }
|
59
|
+
transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
|
60
|
+
Sentry.start_transaction(transaction: transaction, **options)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def finish_transaction(transaction, status_code)
|
65
|
+
return unless transaction
|
66
|
+
|
67
|
+
transaction.set_http_status(status_code)
|
68
|
+
transaction.finish
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
data/lib/sentry/rake.rb
CHANGED
@@ -9,7 +9,7 @@ module Rake
|
|
9
9
|
task_name = top_level_tasks.join(' ')
|
10
10
|
scope.set_transaction_name(task_name)
|
11
11
|
scope.set_tag("rake_task", task_name)
|
12
|
-
end if Sentry.initialized?
|
12
|
+
end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration
|
13
13
|
|
14
14
|
orig_display_error_messsage(ex)
|
15
15
|
end
|
data/lib/sentry/scope.rb
CHANGED
@@ -9,7 +9,8 @@ module Sentry
|
|
9
9
|
|
10
10
|
attr_reader(*ATTRIBUTES)
|
11
11
|
|
12
|
-
def initialize
|
12
|
+
def initialize(max_breadcrumbs: nil)
|
13
|
+
@max_breadcrumbs = max_breadcrumbs
|
13
14
|
set_default_value
|
14
15
|
end
|
15
16
|
|
@@ -47,7 +48,7 @@ module Sentry
|
|
47
48
|
end
|
48
49
|
|
49
50
|
def clear_breadcrumbs
|
50
|
-
|
51
|
+
set_new_breadcrumb_buffer
|
51
52
|
end
|
52
53
|
|
53
54
|
def dup
|
@@ -125,10 +126,11 @@ module Sentry
|
|
125
126
|
|
126
127
|
def set_contexts(contexts_hash)
|
127
128
|
check_argument_type!(contexts_hash, Hash)
|
128
|
-
@contexts
|
129
|
+
@contexts.merge!(contexts_hash)
|
129
130
|
end
|
130
131
|
|
131
132
|
def set_context(key, value)
|
133
|
+
check_argument_type!(value, Hash)
|
132
134
|
@contexts.merge!(key => value)
|
133
135
|
end
|
134
136
|
|
@@ -145,8 +147,7 @@ module Sentry
|
|
145
147
|
end
|
146
148
|
|
147
149
|
def get_transaction
|
148
|
-
|
149
|
-
span.span_recorder.spans.first if span
|
150
|
+
span.transaction if span
|
150
151
|
end
|
151
152
|
|
152
153
|
def get_span
|
@@ -171,7 +172,6 @@ module Sentry
|
|
171
172
|
private
|
172
173
|
|
173
174
|
def set_default_value
|
174
|
-
@breadcrumbs = BreadcrumbBuffer.new
|
175
175
|
@contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
|
176
176
|
@extra = {}
|
177
177
|
@tags = {}
|
@@ -182,8 +182,14 @@ module Sentry
|
|
182
182
|
@event_processors = []
|
183
183
|
@rack_env = {}
|
184
184
|
@span = nil
|
185
|
+
set_new_breadcrumb_buffer
|
185
186
|
end
|
186
187
|
|
188
|
+
def set_new_breadcrumb_buffer
|
189
|
+
@breadcrumbs = BreadcrumbBuffer.new(@max_breadcrumbs)
|
190
|
+
end
|
191
|
+
|
192
|
+
|
187
193
|
class << self
|
188
194
|
def os_context
|
189
195
|
@os_context ||=
|
data/lib/sentry/span.rb
CHANGED
@@ -19,9 +19,18 @@ module Sentry
|
|
19
19
|
|
20
20
|
|
21
21
|
attr_reader :trace_id, :span_id, :parent_span_id, :sampled, :start_timestamp, :timestamp, :description, :op, :status, :tags, :data
|
22
|
-
attr_accessor :span_recorder
|
23
|
-
|
24
|
-
def initialize(
|
22
|
+
attr_accessor :span_recorder, :transaction
|
23
|
+
|
24
|
+
def initialize(
|
25
|
+
description: nil,
|
26
|
+
op: nil,
|
27
|
+
status: nil,
|
28
|
+
trace_id: nil,
|
29
|
+
parent_span_id: nil,
|
30
|
+
sampled: nil,
|
31
|
+
start_timestamp: nil,
|
32
|
+
timestamp: nil
|
33
|
+
)
|
25
34
|
@trace_id = trace_id || SecureRandom.uuid.delete("-")
|
26
35
|
@span_id = SecureRandom.hex(8)
|
27
36
|
@parent_span_id = parent_span_id
|
@@ -78,7 +87,15 @@ module Sentry
|
|
78
87
|
|
79
88
|
def start_child(**options)
|
80
89
|
options = options.dup.merge(trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
|
81
|
-
Span.new(**options)
|
90
|
+
new_span = Span.new(**options)
|
91
|
+
new_span.transaction = transaction
|
92
|
+
new_span.span_recorder = span_recorder
|
93
|
+
|
94
|
+
if span_recorder
|
95
|
+
span_recorder.add(new_span)
|
96
|
+
end
|
97
|
+
|
98
|
+
new_span
|
82
99
|
end
|
83
100
|
|
84
101
|
def with_child_span(**options, &block)
|
data/lib/sentry/transaction.rb
CHANGED
@@ -10,30 +10,38 @@ module Sentry
|
|
10
10
|
UNLABELD_NAME = "<unlabeled transaction>".freeze
|
11
11
|
MESSAGE_PREFIX = "[Tracing]"
|
12
12
|
|
13
|
-
|
13
|
+
include LoggingHelper
|
14
14
|
|
15
|
-
|
15
|
+
attr_reader :name, :parent_sampled, :hub, :configuration, :logger
|
16
|
+
|
17
|
+
def initialize(name: nil, parent_sampled: nil, hub:, **options)
|
16
18
|
super(**options)
|
17
19
|
|
18
20
|
@name = name
|
19
21
|
@parent_sampled = parent_sampled
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@span_recorder.add(self)
|
22
|
+
@transaction = self
|
23
|
+
@hub = hub
|
24
|
+
@configuration = hub.configuration
|
25
|
+
@logger = configuration.logger
|
26
|
+
init_span_recorder
|
26
27
|
end
|
27
28
|
|
28
|
-
def self.from_sentry_trace(sentry_trace, **options)
|
29
|
+
def self.from_sentry_trace(sentry_trace, hub: Sentry.get_current_hub, **options)
|
30
|
+
return unless hub.configuration.tracing_enabled?
|
29
31
|
return unless sentry_trace
|
30
32
|
|
31
33
|
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
34
|
+
return if match.nil?
|
32
35
|
trace_id, parent_span_id, sampled_flag = match[1..3]
|
33
36
|
|
34
|
-
|
37
|
+
parent_sampled =
|
38
|
+
if sampled_flag.nil?
|
39
|
+
nil
|
40
|
+
else
|
41
|
+
sampled_flag != "0"
|
42
|
+
end
|
35
43
|
|
36
|
-
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled:
|
44
|
+
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: parent_sampled, hub: hub, **options)
|
37
45
|
end
|
38
46
|
|
39
47
|
def to_hash
|
@@ -42,20 +50,9 @@ module Sentry
|
|
42
50
|
hash
|
43
51
|
end
|
44
52
|
|
45
|
-
def start_child(**options)
|
46
|
-
child_span = super
|
47
|
-
child_span.span_recorder = @span_recorder
|
48
|
-
|
49
|
-
if @sampled
|
50
|
-
@span_recorder.add(child_span)
|
51
|
-
end
|
52
|
-
|
53
|
-
child_span
|
54
|
-
end
|
55
|
-
|
56
53
|
def deep_dup
|
57
54
|
copy = super
|
58
|
-
copy.
|
55
|
+
copy.init_span_recorder(@span_recorder.max_length)
|
59
56
|
|
60
57
|
@span_recorder.spans.each do |span|
|
61
58
|
# span_recorder's first span is the current span, which should not be added to the copy's spans
|
@@ -66,38 +63,36 @@ module Sentry
|
|
66
63
|
copy
|
67
64
|
end
|
68
65
|
|
69
|
-
def
|
70
|
-
unless
|
66
|
+
def set_initial_sample_decision(sampling_context:)
|
67
|
+
unless configuration.tracing_enabled?
|
71
68
|
@sampled = false
|
72
69
|
return
|
73
70
|
end
|
74
71
|
|
75
72
|
return unless @sampled.nil?
|
76
73
|
|
77
|
-
|
78
|
-
|
79
|
-
logger = Sentry.configuration.logger
|
80
|
-
sample_rate = Sentry.configuration.traces_sample_rate
|
81
|
-
traces_sampler = Sentry.configuration.traces_sampler
|
74
|
+
traces_sampler = configuration.traces_sampler
|
82
75
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
76
|
+
sample_rate =
|
77
|
+
if traces_sampler.is_a?(Proc)
|
78
|
+
traces_sampler.call(sampling_context)
|
79
|
+
elsif !sampling_context[:parent_sampled].nil?
|
80
|
+
sampling_context[:parent_sampled]
|
81
|
+
else
|
82
|
+
configuration.traces_sample_rate
|
83
|
+
end
|
88
84
|
|
89
|
-
|
90
|
-
end
|
85
|
+
transaction_description = generate_transaction_description
|
91
86
|
|
92
|
-
unless [true, false].include?(sample_rate) || (sample_rate.is_a?(
|
87
|
+
unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Numeric) && sample_rate >= 0.0 && sample_rate <= 1.0)
|
93
88
|
@sampled = false
|
94
|
-
|
89
|
+
log_warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
|
95
90
|
return
|
96
91
|
end
|
97
92
|
|
98
93
|
if sample_rate == 0.0 || sample_rate == false
|
99
94
|
@sampled = false
|
100
|
-
|
95
|
+
log_debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
|
101
96
|
return
|
102
97
|
end
|
103
98
|
|
@@ -108,15 +103,26 @@ module Sentry
|
|
108
103
|
end
|
109
104
|
|
110
105
|
if @sampled
|
111
|
-
|
106
|
+
log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
|
112
107
|
else
|
113
|
-
|
108
|
+
log_debug(
|
114
109
|
"#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
|
115
110
|
)
|
116
111
|
end
|
117
112
|
end
|
118
113
|
|
119
114
|
def finish(hub: nil)
|
115
|
+
if hub
|
116
|
+
log_warn(
|
117
|
+
<<~MSG
|
118
|
+
Specifying a different hub in `Transaction#finish` will be deprecated in version 5.0.
|
119
|
+
Please use `Hub#start_transaction` with the designated hub.
|
120
|
+
MSG
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
hub ||= @hub
|
125
|
+
|
120
126
|
super() # Span#finish doesn't take arguments
|
121
127
|
|
122
128
|
if @name.nil?
|
@@ -125,11 +131,17 @@ module Sentry
|
|
125
131
|
|
126
132
|
return unless @sampled || @parent_sampled
|
127
133
|
|
128
|
-
hub ||= Sentry.get_current_hub
|
129
134
|
event = hub.current_client.event_from_transaction(self)
|
130
135
|
hub.capture_event(event)
|
131
136
|
end
|
132
137
|
|
138
|
+
protected
|
139
|
+
|
140
|
+
def init_span_recorder(limit = 1000)
|
141
|
+
@span_recorder = SpanRecorder.new(limit)
|
142
|
+
@span_recorder.add(self)
|
143
|
+
end
|
144
|
+
|
133
145
|
private
|
134
146
|
|
135
147
|
def generate_transaction_description
|