sentry-ruby-core 4.2.2 → 4.4.0

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,50 @@
1
+ module Sentry
2
+ class StacktraceBuilder
3
+ attr_reader :project_root, :app_dirs_pattern, :linecache, :context_lines, :backtrace_cleanup_callback
4
+
5
+ def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
6
+ @project_root = project_root
7
+ @app_dirs_pattern = app_dirs_pattern
8
+ @linecache = linecache
9
+ @context_lines = context_lines
10
+ @backtrace_cleanup_callback = backtrace_cleanup_callback
11
+ end
12
+
13
+ # you can pass a block to customize/exclude frames:
14
+ #
15
+ # ```ruby
16
+ # builder.build(backtrace) do |frame|
17
+ # if frame.module.match?(/a_gem/)
18
+ # nil
19
+ # else
20
+ # frame
21
+ # end
22
+ # end
23
+ # ```
24
+ def build(backtrace:, &frame_callback)
25
+ parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
26
+
27
+ frames = parsed_lines.reverse.map do |line|
28
+ frame = convert_parsed_line_into_frame(line)
29
+ frame = frame_callback.call(frame) if frame_callback
30
+ frame
31
+ end.compact
32
+
33
+ StacktraceInterface.new(frames: frames)
34
+ end
35
+
36
+ private
37
+
38
+ def convert_parsed_line_into_frame(line)
39
+ frame = StacktraceInterface::Frame.new(project_root, line)
40
+ frame.set_context(linecache, context_lines) if context_lines
41
+ frame
42
+ end
43
+
44
+ def parse_backtrace_lines(backtrace)
45
+ Backtrace.parse(
46
+ backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
47
+ ).lines
48
+ end
49
+ end
50
+ end
@@ -1,12 +1,11 @@
1
1
  module Sentry
2
2
  class ThreadsInterface
3
- attr_accessor :stacktrace
4
-
5
- def initialize(crashed: false)
3
+ def initialize(crashed: false, stacktrace: nil)
6
4
  @id = Thread.current.object_id
7
5
  @name = Thread.current.name
8
6
  @current = true
9
7
  @crashed = crashed
8
+ @stacktrace = stacktrace
10
9
  end
11
10
 
12
11
  def to_hash
@@ -22,5 +21,12 @@ module Sentry
22
21
  ]
23
22
  }
24
23
  end
24
+
25
+ # patch this method if you want to change a threads interface's stacktrace frames
26
+ # also see `StacktraceBuilder.build`.
27
+ def self.build(backtrace:, stacktrace_builder:, **options)
28
+ stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
29
+ new(**options, stacktrace: stacktrace)
30
+ end
25
31
  end
26
32
  end
@@ -0,0 +1,87 @@
1
+ require "net/http"
2
+
3
+ module Sentry
4
+ module Net
5
+ module HTTP
6
+ OP_NAME = "net.http"
7
+
8
+ def request(req, body = nil, &block)
9
+ super.tap do |res|
10
+ record_sentry_breadcrumb(req, res)
11
+ record_sentry_span(req, res)
12
+ end
13
+ end
14
+
15
+ def do_start
16
+ super.tap do
17
+ start_sentry_span
18
+ end
19
+ end
20
+
21
+ def do_finish
22
+ super.tap do
23
+ finish_sentry_span
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def record_sentry_breadcrumb(req, res)
30
+ if Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
31
+ return if from_sentry_sdk?
32
+
33
+ request_info = extract_request_info(req)
34
+ crumb = Sentry::Breadcrumb.new(
35
+ level: :info,
36
+ category: OP_NAME,
37
+ type: :info,
38
+ data: {
39
+ method: request_info[:method],
40
+ url: request_info[:url],
41
+ status: res.code.to_i
42
+ }
43
+ )
44
+ Sentry.add_breadcrumb(crumb)
45
+ end
46
+ end
47
+
48
+ def record_sentry_span(req, res)
49
+ if Sentry.initialized? && @sentry_span
50
+ request_info = extract_request_info(req)
51
+ @sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
52
+ @sentry_span.set_data(:status, res.code.to_i)
53
+ end
54
+ end
55
+
56
+ def start_sentry_span
57
+ if Sentry.initialized? && transaction = Sentry.get_current_scope.get_transaction
58
+ return if from_sentry_sdk?
59
+ return if transaction.sampled == false
60
+
61
+ child_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
62
+ @sentry_span = child_span
63
+ end
64
+ end
65
+
66
+ def finish_sentry_span
67
+ if Sentry.initialized? && @sentry_span
68
+ @sentry_span.set_timestamp(Sentry.utc_now.to_f)
69
+ @sentry_span = nil
70
+ end
71
+ end
72
+
73
+ def from_sentry_sdk?
74
+ dsn_host = Sentry.configuration.dsn.host
75
+ dsn_host == self.address
76
+ end
77
+
78
+ def extract_request_info(req)
79
+ uri = req.uri
80
+ url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
81
+ { method: req.method, url: url }
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ Net::HTTP.send(:prepend, Sentry::Net::HTTP)
@@ -16,27 +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
- sentry_trace = env["HTTP_SENTRY_TRACE"]
20
- span = Sentry::Transaction.from_sentry_trace(sentry_trace, name: scope.transaction_name, op: transaction_op) if sentry_trace
21
- span ||= Sentry.start_transaction(name: scope.transaction_name, op: transaction_op)
22
-
23
- scope.set_span(span)
19
+ transaction = start_transaction(env, scope)
20
+ scope.set_span(transaction) if transaction
24
21
 
25
22
  begin
26
23
  response = @app.call(env)
27
24
  rescue Sentry::Error
28
- finish_span(span, 500)
25
+ finish_transaction(transaction, 500)
29
26
  raise # Don't capture Sentry errors
30
27
  rescue Exception => e
31
28
  capture_exception(e)
32
- finish_span(span, 500)
29
+ finish_transaction(transaction, 500)
33
30
  raise
34
31
  end
35
32
 
36
33
  exception = collect_exception(env)
37
34
  capture_exception(exception) if exception
38
35
 
39
- finish_span(span, response[0])
36
+ finish_transaction(transaction, response[0])
40
37
 
41
38
  response
42
39
  end
@@ -56,9 +53,19 @@ module Sentry
56
53
  Sentry.capture_exception(exception)
57
54
  end
58
55
 
59
- def finish_span(span, status_code)
60
- span.set_http_status(status_code)
61
- 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
62
69
  end
63
70
  end
64
71
  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,31 +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)
32
34
  return if match.nil?
33
35
  trace_id, parent_span_id, sampled_flag = match[1..3]
34
36
 
35
- sampled = sampled_flag != "0"
37
+ parent_sampled =
38
+ if sampled_flag.nil?
39
+ nil
40
+ else
41
+ sampled_flag != "0"
42
+ end
36
43
 
37
- new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: sampled, sampled: sampled, **options)
44
+ new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: parent_sampled, hub: hub, **options)
38
45
  end
39
46
 
40
47
  def to_hash
@@ -43,20 +50,9 @@ module Sentry
43
50
  hash
44
51
  end
45
52
 
46
- def start_child(**options)
47
- child_span = super
48
- child_span.span_recorder = @span_recorder
49
-
50
- if @sampled
51
- @span_recorder.add(child_span)
52
- end
53
-
54
- child_span
55
- end
56
-
57
53
  def deep_dup
58
54
  copy = super
59
- copy.set_span_recorder
55
+ copy.init_span_recorder(@span_recorder.max_length)
60
56
 
61
57
  @span_recorder.spans.each do |span|
62
58
  # span_recorder's first span is the current span, which should not be added to the copy's spans
@@ -67,38 +63,36 @@ module Sentry
67
63
  copy
68
64
  end
69
65
 
70
- def set_initial_sample_desicion(sampling_context = {})
71
- unless Sentry.configuration.tracing_enabled?
66
+ def set_initial_sample_decision(sampling_context:)
67
+ unless configuration.tracing_enabled?
72
68
  @sampled = false
73
69
  return
74
70
  end
75
71
 
76
72
  return unless @sampled.nil?
77
73
 
78
- transaction_description = generate_transaction_description
79
-
80
- logger = Sentry.configuration.logger
81
- sample_rate = Sentry.configuration.traces_sample_rate
82
- traces_sampler = Sentry.configuration.traces_sampler
74
+ traces_sampler = configuration.traces_sampler
83
75
 
84
- if traces_sampler.is_a?(Proc)
85
- sampling_context = sampling_context.merge(
86
- parent_sampled: @parent_sampled,
87
- transaction_context: self.to_hash
88
- )
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
89
84
 
90
- sample_rate = traces_sampler.call(sampling_context)
91
- end
85
+ transaction_description = generate_transaction_description
92
86
 
93
87
  unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Numeric) && sample_rate >= 0.0 && sample_rate <= 1.0)
94
88
  @sampled = false
95
- 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}")
96
90
  return
97
91
  end
98
92
 
99
93
  if sample_rate == 0.0 || sample_rate == false
100
94
  @sampled = false
101
- 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")
102
96
  return
103
97
  end
104
98
 
@@ -109,15 +103,26 @@ module Sentry
109
103
  end
110
104
 
111
105
  if @sampled
112
- logger.debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
106
+ log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
113
107
  else
114
- logger.debug(
108
+ log_debug(
115
109
  "#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
116
110
  )
117
111
  end
118
112
  end
119
113
 
120
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
+
121
126
  super() # Span#finish doesn't take arguments
122
127
 
123
128
  if @name.nil?
@@ -126,11 +131,17 @@ module Sentry
126
131
 
127
132
  return unless @sampled || @parent_sampled
128
133
 
129
- hub ||= Sentry.get_current_hub
130
134
  event = hub.current_client.event_from_transaction(self)
131
135
  hub.capture_event(event)
132
136
  end
133
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
+
134
145
  private
135
146
 
136
147
  def generate_transaction_description