sentry-ruby 4.0.1 → 4.1.4
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/CHANGELOG.md +79 -0
- data/Gemfile +2 -2
- data/README.md +45 -11
- data/lib/sentry-ruby.rb +91 -33
- data/lib/sentry/background_worker.rb +37 -0
- data/lib/sentry/backtrace.rb +3 -5
- data/lib/sentry/client.rb +26 -10
- data/lib/sentry/configuration.rb +14 -5
- data/lib/sentry/event.rb +22 -28
- data/lib/sentry/hub.rb +13 -7
- data/lib/sentry/integrable.rb +24 -0
- data/lib/sentry/interfaces/request.rb +51 -33
- data/lib/sentry/interfaces/stacktrace.rb +39 -6
- data/lib/sentry/rack.rb +2 -3
- data/lib/sentry/rack/capture_exceptions.rb +68 -0
- data/lib/sentry/rack/deprecations.rb +19 -0
- data/lib/sentry/rake.rb +2 -2
- data/lib/sentry/scope.rb +4 -8
- data/lib/sentry/span.rb +6 -28
- data/lib/sentry/transaction.rb +45 -1
- data/lib/sentry/transport.rb +12 -21
- data/lib/sentry/transport/http_transport.rb +3 -6
- data/lib/sentry/utils/argument_checking_helper.rb +11 -0
- data/lib/sentry/utils/request_id.rb +2 -2
- data/lib/sentry/version.rb +1 -1
- data/sentry-ruby.gemspec +1 -0
- metadata +27 -5
- data/lib/sentry/rack/capture_exception.rb +0 -45
- data/lib/sentry/rack/tracing.rb +0 -39
- data/lib/sentry/transport/state.rb +0 -40
@@ -1,11 +1,31 @@
|
|
1
1
|
module Sentry
|
2
|
-
class StacktraceInterface
|
3
|
-
|
2
|
+
class StacktraceInterface
|
3
|
+
attr_reader :frames
|
4
|
+
|
5
|
+
def initialize(backtrace:, project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
|
6
|
+
@project_root = project_root
|
7
|
+
@frames = []
|
8
|
+
|
9
|
+
parsed_backtrace_lines = Backtrace.parse(
|
10
|
+
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
|
11
|
+
).lines
|
12
|
+
|
13
|
+
parsed_backtrace_lines.reverse.each_with_object(@frames) do |line, frames|
|
14
|
+
frame = convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
|
15
|
+
frames << frame if frame.filename
|
16
|
+
end
|
17
|
+
end
|
4
18
|
|
5
19
|
def to_hash
|
6
|
-
|
7
|
-
|
8
|
-
|
20
|
+
{ frames: @frames.map(&:to_hash) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
|
26
|
+
frame = StacktraceInterface::Frame.new(@project_root, line)
|
27
|
+
frame.set_context(linecache, context_lines) if context_lines
|
28
|
+
frame
|
9
29
|
end
|
10
30
|
|
11
31
|
# Not actually an interface, but I want to use the same style
|
@@ -13,8 +33,14 @@ module Sentry
|
|
13
33
|
attr_accessor :abs_path, :context_line, :function, :in_app,
|
14
34
|
:lineno, :module, :pre_context, :post_context, :vars
|
15
35
|
|
16
|
-
def initialize(project_root)
|
36
|
+
def initialize(project_root, line)
|
17
37
|
@project_root = project_root
|
38
|
+
|
39
|
+
@abs_path = line.file if line.file
|
40
|
+
@function = line.method if line.method
|
41
|
+
@lineno = line.number
|
42
|
+
@in_app = line.in_app
|
43
|
+
@module = line.module_name if line.module_name
|
18
44
|
end
|
19
45
|
|
20
46
|
def filename
|
@@ -33,6 +59,13 @@ module Sentry
|
|
33
59
|
@filename = prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
34
60
|
end
|
35
61
|
|
62
|
+
def set_context(linecache, context_lines)
|
63
|
+
return unless abs_path
|
64
|
+
|
65
|
+
self.pre_context, self.context_line, self.post_context = \
|
66
|
+
linecache.get_file_context(abs_path, lineno, context_lines)
|
67
|
+
end
|
68
|
+
|
36
69
|
def to_hash(*args)
|
37
70
|
data = super(*args)
|
38
71
|
data[:filename] = filename
|
data/lib/sentry/rack.rb
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Sentry
|
2
|
+
module Rack
|
3
|
+
class CaptureExceptions
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
return @app.call(env) unless Sentry.initialized?
|
10
|
+
|
11
|
+
# make sure the current thread has a clean hub
|
12
|
+
Sentry.clone_hub_to_current_thread
|
13
|
+
|
14
|
+
Sentry.with_scope do |scope|
|
15
|
+
scope.clear_breadcrumbs
|
16
|
+
scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
|
17
|
+
scope.set_rack_env(env)
|
18
|
+
|
19
|
+
span =
|
20
|
+
if sentry_trace = env["sentry-trace"]
|
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)
|
27
|
+
|
28
|
+
begin
|
29
|
+
response = @app.call(env)
|
30
|
+
rescue Sentry::Error
|
31
|
+
finish_span(span, 500)
|
32
|
+
raise # Don't capture Sentry errors
|
33
|
+
rescue Exception => e
|
34
|
+
capture_exception(e)
|
35
|
+
finish_span(span, 500)
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
|
39
|
+
exception = collect_exception(env)
|
40
|
+
capture_exception(exception) if exception
|
41
|
+
|
42
|
+
finish_span(span, response[0])
|
43
|
+
|
44
|
+
response
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def collect_exception(env)
|
51
|
+
env['rack.exception'] || env['sinatra.error']
|
52
|
+
end
|
53
|
+
|
54
|
+
def transaction_op
|
55
|
+
"rack.request".freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
def capture_exception(exception)
|
59
|
+
Sentry.capture_exception(exception)
|
60
|
+
end
|
61
|
+
|
62
|
+
def finish_span(span, status_code)
|
63
|
+
span.set_http_status(status_code)
|
64
|
+
span.finish
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sentry
|
2
|
+
module Rack
|
3
|
+
class DeprecatedMiddleware
|
4
|
+
def initialize(_)
|
5
|
+
raise Sentry::Error.new <<~MSG
|
6
|
+
|
7
|
+
You're seeing this message because #{self.class} has been replaced by Sentry::Rack::CaptureExceptions.
|
8
|
+
Removing this middleware from your app and upgrading sentry-rails to 4.1.0+ should solve the issue.
|
9
|
+
MSG
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Tracing < DeprecatedMiddleware
|
14
|
+
end
|
15
|
+
|
16
|
+
class CaptureException < DeprecatedMiddleware
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/sentry/rake.rb
CHANGED
@@ -5,11 +5,11 @@ module Rake
|
|
5
5
|
class Application
|
6
6
|
alias orig_display_error_messsage display_error_message
|
7
7
|
def display_error_message(ex)
|
8
|
-
Sentry.capture_exception(ex) do |scope|
|
8
|
+
Sentry.capture_exception(ex, hint: { background: false }) do |scope|
|
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
|
12
|
+
end if Sentry.initialized?
|
13
13
|
|
14
14
|
orig_display_error_messsage(ex)
|
15
15
|
end
|
data/lib/sentry/scope.rb
CHANGED
@@ -3,6 +3,8 @@ require "etc"
|
|
3
3
|
|
4
4
|
module Sentry
|
5
5
|
class Scope
|
6
|
+
include ArgumentCheckingHelper
|
7
|
+
|
6
8
|
ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span]
|
7
9
|
|
8
10
|
attr_reader(*ATTRIBUTES)
|
@@ -29,7 +31,7 @@ module Sentry
|
|
29
31
|
event.level = level
|
30
32
|
event.transaction = transaction_names.last
|
31
33
|
event.breadcrumbs = breadcrumbs
|
32
|
-
event.rack_env = rack_env
|
34
|
+
event.rack_env = rack_env if rack_env
|
33
35
|
|
34
36
|
unless @event_processors.empty?
|
35
37
|
@event_processors.each do |processor_block|
|
@@ -57,7 +59,7 @@ module Sentry
|
|
57
59
|
copy.user = user.deep_dup
|
58
60
|
copy.transaction_names = transaction_names.deep_dup
|
59
61
|
copy.fingerprint = fingerprint.deep_dup
|
60
|
-
copy.span = span
|
62
|
+
copy.span = span.deep_dup
|
61
63
|
copy
|
62
64
|
end
|
63
65
|
|
@@ -168,12 +170,6 @@ module Sentry
|
|
168
170
|
|
169
171
|
private
|
170
172
|
|
171
|
-
def check_argument_type!(argument, expected_type)
|
172
|
-
unless argument.is_a?(expected_type)
|
173
|
-
raise ArgumentError, "expect the argument to be a #{expected_type}, got #{argument.class} (#{argument})"
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
173
|
def set_default_value
|
178
174
|
@breadcrumbs = BreadcrumbBuffer.new
|
179
175
|
@contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
|
data/lib/sentry/span.rb
CHANGED
@@ -35,11 +35,6 @@ module Sentry
|
|
35
35
|
@tags = {}
|
36
36
|
end
|
37
37
|
|
38
|
-
def set_span_recorder
|
39
|
-
@span_recorder = SpanRecorder.new(1000)
|
40
|
-
@span_recorder.add(self)
|
41
|
-
end
|
42
|
-
|
43
38
|
def finish
|
44
39
|
# already finished
|
45
40
|
return if @timestamp
|
@@ -74,6 +69,7 @@ module Sentry
|
|
74
69
|
{
|
75
70
|
trace_id: @trace_id,
|
76
71
|
span_id: @span_id,
|
72
|
+
parent_span_id: @parent_span_id,
|
77
73
|
description: @description,
|
78
74
|
op: @op,
|
79
75
|
status: @status
|
@@ -82,14 +78,7 @@ module Sentry
|
|
82
78
|
|
83
79
|
def start_child(**options)
|
84
80
|
options = options.dup.merge(trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
|
85
|
-
|
86
|
-
child_span.span_recorder = @span_recorder
|
87
|
-
|
88
|
-
if @span_recorder && @sampled
|
89
|
-
@span_recorder.add(child_span)
|
90
|
-
end
|
91
|
-
|
92
|
-
child_span
|
81
|
+
Span.new(**options)
|
93
82
|
end
|
94
83
|
|
95
84
|
def with_child_span(**options, &block)
|
@@ -100,6 +89,10 @@ module Sentry
|
|
100
89
|
child_span.finish
|
101
90
|
end
|
102
91
|
|
92
|
+
def deep_dup
|
93
|
+
dup
|
94
|
+
end
|
95
|
+
|
103
96
|
def set_op(op)
|
104
97
|
@op = op
|
105
98
|
end
|
@@ -136,20 +129,5 @@ module Sentry
|
|
136
129
|
def set_tag(key, value)
|
137
130
|
@tags[key] = value
|
138
131
|
end
|
139
|
-
|
140
|
-
class SpanRecorder
|
141
|
-
attr_reader :max_length, :spans
|
142
|
-
|
143
|
-
def initialize(max_length)
|
144
|
-
@max_length = max_length
|
145
|
-
@spans = []
|
146
|
-
end
|
147
|
-
|
148
|
-
def add(span)
|
149
|
-
if @spans.count < @max_length
|
150
|
-
@spans << span
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
132
|
end
|
155
133
|
end
|
data/lib/sentry/transaction.rb
CHANGED
@@ -20,6 +20,11 @@ module Sentry
|
|
20
20
|
set_span_recorder
|
21
21
|
end
|
22
22
|
|
23
|
+
def set_span_recorder
|
24
|
+
@span_recorder = SpanRecorder.new(1000)
|
25
|
+
@span_recorder.add(self)
|
26
|
+
end
|
27
|
+
|
23
28
|
def self.from_sentry_trace(sentry_trace, **options)
|
24
29
|
return unless sentry_trace
|
25
30
|
|
@@ -37,6 +42,30 @@ module Sentry
|
|
37
42
|
hash
|
38
43
|
end
|
39
44
|
|
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
|
+
def deep_dup
|
57
|
+
copy = super
|
58
|
+
copy.set_span_recorder
|
59
|
+
|
60
|
+
@span_recorder.spans.each do |span|
|
61
|
+
# span_recorder's first span is the current span, which should not be added to the copy's spans
|
62
|
+
next if span == self
|
63
|
+
copy.span_recorder.add(span.dup)
|
64
|
+
end
|
65
|
+
|
66
|
+
copy
|
67
|
+
end
|
68
|
+
|
40
69
|
def set_initial_sample_desicion(sampling_context = {})
|
41
70
|
unless Sentry.configuration.tracing_enabled?
|
42
71
|
@sampled = false
|
@@ -94,7 +123,7 @@ module Sentry
|
|
94
123
|
@name = UNLABELD_NAME
|
95
124
|
end
|
96
125
|
|
97
|
-
return unless @sampled
|
126
|
+
return unless @sampled || @parent_sampled
|
98
127
|
|
99
128
|
hub ||= Sentry.get_current_hub
|
100
129
|
event = hub.current_client.event_from_transaction(self)
|
@@ -109,5 +138,20 @@ module Sentry
|
|
109
138
|
result += " <#{@name}>" if @name
|
110
139
|
result
|
111
140
|
end
|
141
|
+
|
142
|
+
class SpanRecorder
|
143
|
+
attr_reader :max_length, :spans
|
144
|
+
|
145
|
+
def initialize(max_length)
|
146
|
+
@max_length = max_length
|
147
|
+
@spans = []
|
148
|
+
end
|
149
|
+
|
150
|
+
def add(span)
|
151
|
+
if @spans.count < @max_length
|
152
|
+
@spans << span
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
112
156
|
end
|
113
157
|
end
|
data/lib/sentry/transport.rb
CHANGED
@@ -1,20 +1,17 @@
|
|
1
1
|
require "json"
|
2
2
|
require "base64"
|
3
|
-
require "sentry/transport/state"
|
4
3
|
|
5
4
|
module Sentry
|
6
5
|
class Transport
|
7
6
|
PROTOCOL_VERSION = '5'
|
8
7
|
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
|
9
|
-
CONTENT_TYPE = 'application/json'
|
10
8
|
|
11
|
-
attr_accessor :configuration
|
9
|
+
attr_accessor :configuration
|
12
10
|
|
13
11
|
def initialize(configuration)
|
14
12
|
@configuration = configuration
|
15
13
|
@transport_configuration = configuration.transport
|
16
14
|
@dsn = configuration.dsn
|
17
|
-
@state = State.new
|
18
15
|
end
|
19
16
|
|
20
17
|
def send_data(data, options = {})
|
@@ -22,13 +19,17 @@ module Sentry
|
|
22
19
|
end
|
23
20
|
|
24
21
|
def send_event(event)
|
25
|
-
|
22
|
+
unless configuration.sending_allowed?
|
23
|
+
configuration.logger.debug(LOGGER_PROGNAME) { "Event not sent: #{configuration.error_messages}" }
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
encoded_data = prepare_encoded_event(event)
|
26
28
|
|
27
29
|
return nil unless encoded_data
|
28
30
|
|
29
|
-
send_data(encoded_data
|
31
|
+
send_data(encoded_data)
|
30
32
|
|
31
|
-
state.success
|
32
33
|
event
|
33
34
|
rescue => e
|
34
35
|
failed_for_exception(e, event)
|
@@ -57,7 +58,7 @@ module Sentry
|
|
57
58
|
#{JSON.generate(event_hash)}
|
58
59
|
ENVELOPE
|
59
60
|
|
60
|
-
|
61
|
+
envelope
|
61
62
|
end
|
62
63
|
|
63
64
|
private
|
@@ -66,27 +67,17 @@ module Sentry
|
|
66
67
|
# Convert to hash
|
67
68
|
event_hash = event.to_hash
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
event_id = event_hash[:event_id] || event_hash['event_id']
|
75
|
-
configuration.logger.info(LOGGER_PROGNAME) { "Sending event #{event_id} to Sentry" }
|
70
|
+
event_id = event_hash[:event_id] || event_hash["event_id"]
|
71
|
+
event_type = event_hash[:type] || event_hash["type"]
|
72
|
+
configuration.logger.info(LOGGER_PROGNAME) { "Sending #{event_type} #{event_id} to Sentry" }
|
76
73
|
encode(event_hash)
|
77
74
|
end
|
78
75
|
|
79
76
|
def failed_for_exception(e, event)
|
80
|
-
@state.failure
|
81
77
|
configuration.logger.warn(LOGGER_PROGNAME) { "Unable to record event with remote Sentry server (#{e.class} - #{e.message}):\n#{e.backtrace[0..10].join("\n")}" }
|
82
78
|
log_not_sending(event)
|
83
79
|
end
|
84
80
|
|
85
|
-
def failed_for_previous_failure(event)
|
86
|
-
configuration.logger.warn(LOGGER_PROGNAME) { "Not sending event due to previous failure(s)." }
|
87
|
-
log_not_sending(event)
|
88
|
-
end
|
89
|
-
|
90
81
|
def log_not_sending(event)
|
91
82
|
configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event. Unreported Event: #{Event.get_log_message(event.to_hash)}" }
|
92
83
|
end
|
@@ -2,6 +2,7 @@ require 'faraday'
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class HTTPTransport < Transport
|
5
|
+
CONTENT_TYPE = 'application/json'
|
5
6
|
attr_reader :conn, :adapter
|
6
7
|
|
7
8
|
def initialize(*args)
|
@@ -11,13 +12,9 @@ module Sentry
|
|
11
12
|
@endpoint = @dsn.envelope_endpoint
|
12
13
|
end
|
13
14
|
|
14
|
-
def send_data(data
|
15
|
-
unless configuration.sending_allowed?
|
16
|
-
logger.debug(LOGGER_PROGNAME) { "Event not sent: #{configuration.error_messages}" }
|
17
|
-
end
|
18
|
-
|
15
|
+
def send_data(data)
|
19
16
|
conn.post @endpoint do |req|
|
20
|
-
req.headers['Content-Type'] =
|
17
|
+
req.headers['Content-Type'] = CONTENT_TYPE
|
21
18
|
req.headers['X-Sentry-Auth'] = generate_auth_header
|
22
19
|
req.body = data
|
23
20
|
end
|