sentry-ruby 4.0.1 → 4.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|