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.
@@ -1,11 +1,31 @@
1
1
  module Sentry
2
- class StacktraceInterface < Interface
3
- attr_accessor :frames
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
- data = super
7
- data[:frames] = data[:frames].map(&:to_hash)
8
- data
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
@@ -1,5 +1,4 @@
1
- require 'time'
2
1
  require 'rack'
3
2
 
4
- require 'sentry/rack/capture_exception'
5
- require 'sentry/rack/tracing'
3
+ require 'sentry/rack/capture_exceptions'
4
+ require 'sentry/rack/deprecations'
@@ -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
@@ -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
@@ -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 }
@@ -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
- child_span = Span.new(options)
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
@@ -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
@@ -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, :state
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
- content_type, encoded_data = prepare_encoded_event(event)
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, content_type: content_type)
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
- [CONTENT_TYPE, envelope]
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
- unless @state.should_try?
70
- failed_for_previous_failure(event_hash)
71
- return
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, options = {})
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'] = options[: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