sentry-ruby-core 4.1.6 → 4.5.1

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.
@@ -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