sentry-ruby-core 4.2.2 → 4.4.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 +111 -0
- data/Gemfile +4 -0
- data/README.md +20 -22
- 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 -48
- 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 -5
- 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/version.rb +1 -1
- metadata +7 -3
@@ -0,0 +1,50 @@
|
|
1
|
+
module Sentry
|
2
|
+
class StacktraceBuilder
|
3
|
+
attr_reader :project_root, :app_dirs_pattern, :linecache, :context_lines, :backtrace_cleanup_callback
|
4
|
+
|
5
|
+
def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
|
6
|
+
@project_root = project_root
|
7
|
+
@app_dirs_pattern = app_dirs_pattern
|
8
|
+
@linecache = linecache
|
9
|
+
@context_lines = context_lines
|
10
|
+
@backtrace_cleanup_callback = backtrace_cleanup_callback
|
11
|
+
end
|
12
|
+
|
13
|
+
# you can pass a block to customize/exclude frames:
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# builder.build(backtrace) do |frame|
|
17
|
+
# if frame.module.match?(/a_gem/)
|
18
|
+
# nil
|
19
|
+
# else
|
20
|
+
# frame
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# ```
|
24
|
+
def build(backtrace:, &frame_callback)
|
25
|
+
parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
|
26
|
+
|
27
|
+
frames = parsed_lines.reverse.map do |line|
|
28
|
+
frame = convert_parsed_line_into_frame(line)
|
29
|
+
frame = frame_callback.call(frame) if frame_callback
|
30
|
+
frame
|
31
|
+
end.compact
|
32
|
+
|
33
|
+
StacktraceInterface.new(frames: frames)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def convert_parsed_line_into_frame(line)
|
39
|
+
frame = StacktraceInterface::Frame.new(project_root, line)
|
40
|
+
frame.set_context(linecache, context_lines) if context_lines
|
41
|
+
frame
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_backtrace_lines(backtrace)
|
45
|
+
Backtrace.parse(
|
46
|
+
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
|
47
|
+
).lines
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module Sentry
|
2
2
|
class ThreadsInterface
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(crashed: false)
|
3
|
+
def initialize(crashed: false, stacktrace: nil)
|
6
4
|
@id = Thread.current.object_id
|
7
5
|
@name = Thread.current.name
|
8
6
|
@current = true
|
9
7
|
@crashed = crashed
|
8
|
+
@stacktrace = stacktrace
|
10
9
|
end
|
11
10
|
|
12
11
|
def to_hash
|
@@ -22,5 +21,12 @@ module Sentry
|
|
22
21
|
]
|
23
22
|
}
|
24
23
|
end
|
24
|
+
|
25
|
+
# patch this method if you want to change a threads interface's stacktrace frames
|
26
|
+
# also see `StacktraceBuilder.build`.
|
27
|
+
def self.build(backtrace:, stacktrace_builder:, **options)
|
28
|
+
stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
|
29
|
+
new(**options, stacktrace: stacktrace)
|
30
|
+
end
|
25
31
|
end
|
26
32
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "net/http"
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Net
|
5
|
+
module HTTP
|
6
|
+
OP_NAME = "net.http"
|
7
|
+
|
8
|
+
def request(req, body = nil, &block)
|
9
|
+
super.tap do |res|
|
10
|
+
record_sentry_breadcrumb(req, res)
|
11
|
+
record_sentry_span(req, res)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def do_start
|
16
|
+
super.tap do
|
17
|
+
start_sentry_span
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def do_finish
|
22
|
+
super.tap do
|
23
|
+
finish_sentry_span
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def record_sentry_breadcrumb(req, res)
|
30
|
+
if Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
31
|
+
return if from_sentry_sdk?
|
32
|
+
|
33
|
+
request_info = extract_request_info(req)
|
34
|
+
crumb = Sentry::Breadcrumb.new(
|
35
|
+
level: :info,
|
36
|
+
category: OP_NAME,
|
37
|
+
type: :info,
|
38
|
+
data: {
|
39
|
+
method: request_info[:method],
|
40
|
+
url: request_info[:url],
|
41
|
+
status: res.code.to_i
|
42
|
+
}
|
43
|
+
)
|
44
|
+
Sentry.add_breadcrumb(crumb)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def record_sentry_span(req, res)
|
49
|
+
if Sentry.initialized? && @sentry_span
|
50
|
+
request_info = extract_request_info(req)
|
51
|
+
@sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
52
|
+
@sentry_span.set_data(:status, res.code.to_i)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def start_sentry_span
|
57
|
+
if Sentry.initialized? && transaction = Sentry.get_current_scope.get_transaction
|
58
|
+
return if from_sentry_sdk?
|
59
|
+
return if transaction.sampled == false
|
60
|
+
|
61
|
+
child_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
|
62
|
+
@sentry_span = child_span
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def finish_sentry_span
|
67
|
+
if Sentry.initialized? && @sentry_span
|
68
|
+
@sentry_span.set_timestamp(Sentry.utc_now.to_f)
|
69
|
+
@sentry_span = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def from_sentry_sdk?
|
74
|
+
dsn_host = Sentry.configuration.dsn.host
|
75
|
+
dsn_host == self.address
|
76
|
+
end
|
77
|
+
|
78
|
+
def extract_request_info(req)
|
79
|
+
uri = req.uri
|
80
|
+
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
81
|
+
{ method: req.method, url: url }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
Net::HTTP.send(:prepend, Sentry::Net::HTTP)
|
@@ -16,27 +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
|
-
span ||= Sentry.start_transaction(name: scope.transaction_name, op: transaction_op)
|
22
|
-
|
23
|
-
scope.set_span(span)
|
19
|
+
transaction = start_transaction(env, scope)
|
20
|
+
scope.set_span(transaction) if transaction
|
24
21
|
|
25
22
|
begin
|
26
23
|
response = @app.call(env)
|
27
24
|
rescue Sentry::Error
|
28
|
-
|
25
|
+
finish_transaction(transaction, 500)
|
29
26
|
raise # Don't capture Sentry errors
|
30
27
|
rescue Exception => e
|
31
28
|
capture_exception(e)
|
32
|
-
|
29
|
+
finish_transaction(transaction, 500)
|
33
30
|
raise
|
34
31
|
end
|
35
32
|
|
36
33
|
exception = collect_exception(env)
|
37
34
|
capture_exception(exception) if exception
|
38
35
|
|
39
|
-
|
36
|
+
finish_transaction(transaction, response[0])
|
40
37
|
|
41
38
|
response
|
42
39
|
end
|
@@ -56,9 +53,19 @@ module Sentry
|
|
56
53
|
Sentry.capture_exception(exception)
|
57
54
|
end
|
58
55
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
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
|
62
69
|
end
|
63
70
|
end
|
64
71
|
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,31 +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)
|
32
34
|
return if match.nil?
|
33
35
|
trace_id, parent_span_id, sampled_flag = match[1..3]
|
34
36
|
|
35
|
-
|
37
|
+
parent_sampled =
|
38
|
+
if sampled_flag.nil?
|
39
|
+
nil
|
40
|
+
else
|
41
|
+
sampled_flag != "0"
|
42
|
+
end
|
36
43
|
|
37
|
-
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)
|
38
45
|
end
|
39
46
|
|
40
47
|
def to_hash
|
@@ -43,20 +50,9 @@ module Sentry
|
|
43
50
|
hash
|
44
51
|
end
|
45
52
|
|
46
|
-
def start_child(**options)
|
47
|
-
child_span = super
|
48
|
-
child_span.span_recorder = @span_recorder
|
49
|
-
|
50
|
-
if @sampled
|
51
|
-
@span_recorder.add(child_span)
|
52
|
-
end
|
53
|
-
|
54
|
-
child_span
|
55
|
-
end
|
56
|
-
|
57
53
|
def deep_dup
|
58
54
|
copy = super
|
59
|
-
copy.
|
55
|
+
copy.init_span_recorder(@span_recorder.max_length)
|
60
56
|
|
61
57
|
@span_recorder.spans.each do |span|
|
62
58
|
# span_recorder's first span is the current span, which should not be added to the copy's spans
|
@@ -67,38 +63,36 @@ module Sentry
|
|
67
63
|
copy
|
68
64
|
end
|
69
65
|
|
70
|
-
def
|
71
|
-
unless
|
66
|
+
def set_initial_sample_decision(sampling_context:)
|
67
|
+
unless configuration.tracing_enabled?
|
72
68
|
@sampled = false
|
73
69
|
return
|
74
70
|
end
|
75
71
|
|
76
72
|
return unless @sampled.nil?
|
77
73
|
|
78
|
-
|
79
|
-
|
80
|
-
logger = Sentry.configuration.logger
|
81
|
-
sample_rate = Sentry.configuration.traces_sample_rate
|
82
|
-
traces_sampler = Sentry.configuration.traces_sampler
|
74
|
+
traces_sampler = configuration.traces_sampler
|
83
75
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
89
84
|
|
90
|
-
|
91
|
-
end
|
85
|
+
transaction_description = generate_transaction_description
|
92
86
|
|
93
87
|
unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Numeric) && sample_rate >= 0.0 && sample_rate <= 1.0)
|
94
88
|
@sampled = false
|
95
|
-
|
89
|
+
log_warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
|
96
90
|
return
|
97
91
|
end
|
98
92
|
|
99
93
|
if sample_rate == 0.0 || sample_rate == false
|
100
94
|
@sampled = false
|
101
|
-
|
95
|
+
log_debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
|
102
96
|
return
|
103
97
|
end
|
104
98
|
|
@@ -109,15 +103,26 @@ module Sentry
|
|
109
103
|
end
|
110
104
|
|
111
105
|
if @sampled
|
112
|
-
|
106
|
+
log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
|
113
107
|
else
|
114
|
-
|
108
|
+
log_debug(
|
115
109
|
"#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
|
116
110
|
)
|
117
111
|
end
|
118
112
|
end
|
119
113
|
|
120
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
|
+
|
121
126
|
super() # Span#finish doesn't take arguments
|
122
127
|
|
123
128
|
if @name.nil?
|
@@ -126,11 +131,17 @@ module Sentry
|
|
126
131
|
|
127
132
|
return unless @sampled || @parent_sampled
|
128
133
|
|
129
|
-
hub ||= Sentry.get_current_hub
|
130
134
|
event = hub.current_client.event_from_transaction(self)
|
131
135
|
hub.capture_event(event)
|
132
136
|
end
|
133
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
|
+
|
134
145
|
private
|
135
146
|
|
136
147
|
def generate_transaction_description
|