sentry-ruby-core 4.1.6 → 4.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- span =
20
- if sentry_trace = env["HTTP_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)
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
- finish_span(span, 500)
25
+ finish_transaction(transaction, 500)
32
26
  raise # Don't capture Sentry errors
33
27
  rescue Exception => e
34
28
  capture_exception(e)
35
- finish_span(span, 500)
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
- finish_span(span, response[0])
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 finish_span(span, status_code)
63
- span.set_http_status(status_code)
64
- span.finish
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
- @breadcrumbs = BreadcrumbBuffer.new
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 = contexts_hash
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
- # transaction will always be the first in the span_recorder
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(description: nil, op: nil, status: nil, trace_id: nil, parent_span_id: nil, sampled: nil, start_timestamp: nil, timestamp: nil)
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)
@@ -10,30 +10,38 @@ module Sentry
10
10
  UNLABELD_NAME = "<unlabeled transaction>".freeze
11
11
  MESSAGE_PREFIX = "[Tracing]"
12
12
 
13
- attr_reader :name, :parent_sampled
13
+ include LoggingHelper
14
14
 
15
- def initialize(name: nil, parent_sampled: nil, **options)
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
- set_span_recorder
21
- end
22
-
23
- def set_span_recorder
24
- @span_recorder = SpanRecorder.new(1000)
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
- sampled = sampled_flag != "0"
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: sampled, **options)
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.set_span_recorder
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 set_initial_sample_desicion(sampling_context = {})
70
- unless Sentry.configuration.tracing_enabled?
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
- transaction_description = generate_transaction_description
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
- if traces_sampler.is_a?(Proc)
84
- sampling_context = sampling_context.merge(
85
- parent_sampled: @parent_sampled,
86
- transaction_context: self.to_hash
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
- sample_rate = traces_sampler.call(sampling_context)
90
- end
85
+ transaction_description = generate_transaction_description
91
86
 
92
- unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Float) && sample_rate >= 0.0 && sample_rate <= 1.0)
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
- logger.warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
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
- logger.debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
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
- logger.debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
106
+ log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
112
107
  else
113
- logger.debug(
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