sentry-ruby 5.16.1 → 5.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -0
  3. data/README.md +20 -10
  4. data/Rakefile +3 -1
  5. data/bin/console +2 -0
  6. data/lib/sentry/attachment.rb +40 -0
  7. data/lib/sentry/background_worker.rb +1 -1
  8. data/lib/sentry/backpressure_monitor.rb +2 -32
  9. data/lib/sentry/backtrace.rb +10 -8
  10. data/lib/sentry/baggage.rb +7 -7
  11. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  12. data/lib/sentry/check_in_event.rb +5 -5
  13. data/lib/sentry/client.rb +61 -11
  14. data/lib/sentry/configuration.rb +77 -31
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/monitor_check_ins.rb +3 -1
  17. data/lib/sentry/cron/monitor_config.rb +1 -1
  18. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  19. data/lib/sentry/dsn.rb +4 -4
  20. data/lib/sentry/envelope/item.rb +88 -0
  21. data/lib/sentry/envelope.rb +2 -68
  22. data/lib/sentry/error_event.rb +2 -2
  23. data/lib/sentry/event.rb +20 -18
  24. data/lib/sentry/faraday.rb +77 -0
  25. data/lib/sentry/graphql.rb +9 -0
  26. data/lib/sentry/hub.rb +23 -3
  27. data/lib/sentry/integrable.rb +4 -0
  28. data/lib/sentry/interface.rb +1 -0
  29. data/lib/sentry/interfaces/exception.rb +5 -3
  30. data/lib/sentry/interfaces/mechanism.rb +20 -0
  31. data/lib/sentry/interfaces/request.rb +7 -7
  32. data/lib/sentry/interfaces/single_exception.rb +9 -7
  33. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  34. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  35. data/lib/sentry/logger.rb +1 -1
  36. data/lib/sentry/metrics/aggregator.rb +248 -0
  37. data/lib/sentry/metrics/configuration.rb +47 -0
  38. data/lib/sentry/metrics/counter_metric.rb +25 -0
  39. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  40. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  41. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  42. data/lib/sentry/metrics/metric.rb +19 -0
  43. data/lib/sentry/metrics/set_metric.rb +28 -0
  44. data/lib/sentry/metrics/timing.rb +43 -0
  45. data/lib/sentry/metrics.rb +56 -0
  46. data/lib/sentry/net/http.rb +18 -39
  47. data/lib/sentry/profiler/helpers.rb +46 -0
  48. data/lib/sentry/profiler.rb +25 -56
  49. data/lib/sentry/propagation_context.rb +10 -9
  50. data/lib/sentry/puma.rb +1 -1
  51. data/lib/sentry/rack/capture_exceptions.rb +16 -4
  52. data/lib/sentry/rack.rb +2 -2
  53. data/lib/sentry/rake.rb +4 -2
  54. data/lib/sentry/redis.rb +2 -1
  55. data/lib/sentry/release_detector.rb +4 -4
  56. data/lib/sentry/scope.rb +36 -26
  57. data/lib/sentry/session.rb +2 -2
  58. data/lib/sentry/session_flusher.rb +7 -39
  59. data/lib/sentry/span.rb +46 -5
  60. data/lib/sentry/test_helper.rb +5 -2
  61. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  62. data/lib/sentry/transaction.rb +19 -17
  63. data/lib/sentry/transaction_event.rb +6 -2
  64. data/lib/sentry/transport/configuration.rb +0 -1
  65. data/lib/sentry/transport/http_transport.rb +12 -12
  66. data/lib/sentry/transport.rb +18 -26
  67. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  68. data/lib/sentry/utils/env_helper.rb +21 -0
  69. data/lib/sentry/utils/http_tracing.rb +41 -0
  70. data/lib/sentry/utils/logging_helper.rb +0 -4
  71. data/lib/sentry/utils/real_ip.rb +2 -2
  72. data/lib/sentry/utils/request_id.rb +1 -1
  73. data/lib/sentry/vernier/output.rb +89 -0
  74. data/lib/sentry/vernier/profiler.rb +125 -0
  75. data/lib/sentry/version.rb +1 -1
  76. data/lib/sentry-ruby.rb +38 -6
  77. data/sentry-ruby-core.gemspec +3 -1
  78. data/sentry-ruby.gemspec +15 -6
  79. metadata +44 -7
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class GaugeMetric < Metric
6
+ attr_reader :last, :min, :max, :sum, :count
7
+
8
+ def initialize(value)
9
+ value = value.to_f
10
+ @last = value
11
+ @min = value
12
+ @max = value
13
+ @sum = value
14
+ @count = 1
15
+ end
16
+
17
+ def add(value)
18
+ value = value.to_f
19
+ @last = value
20
+ @min = [@min, value].min
21
+ @max = [@max, value].max
22
+ @sum += value
23
+ @count += 1
24
+ end
25
+
26
+ def serialize
27
+ [last, min, max, sum, count]
28
+ end
29
+
30
+ def weight
31
+ 5
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class LocalAggregator
6
+ # exposed only for testing
7
+ attr_reader :buckets
8
+
9
+ def initialize
10
+ @buckets = {}
11
+ end
12
+
13
+ def add(key, value)
14
+ if @buckets[key]
15
+ @buckets[key].add(value)
16
+ else
17
+ @buckets[key] = GaugeMetric.new(value)
18
+ end
19
+ end
20
+
21
+ def to_hash
22
+ return nil if @buckets.empty?
23
+
24
+ @buckets.map do |bucket_key, metric|
25
+ type, key, unit, tags = bucket_key
26
+
27
+ payload_key = "#{type}:#{key}@#{unit}"
28
+ payload_value = {
29
+ tags: deserialize_tags(tags),
30
+ min: metric.min,
31
+ max: metric.max,
32
+ count: metric.count,
33
+ sum: metric.sum
34
+ }
35
+
36
+ [payload_key, payload_value]
37
+ end.to_h
38
+ end
39
+
40
+ private
41
+
42
+ def deserialize_tags(tags)
43
+ tags.inject({}) do |h, tag|
44
+ k, v = tag
45
+ old = h[k]
46
+ # make it an array if key repeats
47
+ h[k] = old ? (old.is_a?(Array) ? old << v : [old, v]) : v
48
+ h
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class Metric
6
+ def add(value)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def serialize
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def weight
15
+ raise NotImplementedError
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "zlib"
5
+
6
+ module Sentry
7
+ module Metrics
8
+ class SetMetric < Metric
9
+ attr_reader :value
10
+
11
+ def initialize(value)
12
+ @value = Set[value]
13
+ end
14
+
15
+ def add(value)
16
+ @value << value
17
+ end
18
+
19
+ def serialize
20
+ value.map { |x| x.is_a?(String) ? Zlib.crc32(x) : x.to_i }
21
+ end
22
+
23
+ def weight
24
+ value.size
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ module Timing
6
+ class << self
7
+ def nanosecond
8
+ time = Sentry.utc_now
9
+ time.to_i * (10 ** 9) + time.nsec
10
+ end
11
+
12
+ def microsecond
13
+ time = Sentry.utc_now
14
+ time.to_i * (10 ** 6) + time.usec
15
+ end
16
+
17
+ def millisecond
18
+ Sentry.utc_now.to_i * (10 ** 3)
19
+ end
20
+
21
+ def second
22
+ Sentry.utc_now.to_i
23
+ end
24
+
25
+ def minute
26
+ Sentry.utc_now.to_i / 60.0
27
+ end
28
+
29
+ def hour
30
+ Sentry.utc_now.to_i / 3600.0
31
+ end
32
+
33
+ def day
34
+ Sentry.utc_now.to_i / (3600.0 * 24.0)
35
+ end
36
+
37
+ def week
38
+ Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/metrics/metric"
4
+ require "sentry/metrics/counter_metric"
5
+ require "sentry/metrics/distribution_metric"
6
+ require "sentry/metrics/gauge_metric"
7
+ require "sentry/metrics/set_metric"
8
+ require "sentry/metrics/timing"
9
+ require "sentry/metrics/aggregator"
10
+
11
+ module Sentry
12
+ module Metrics
13
+ DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
14
+ INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
15
+ FRACTIONAL_UNITS = %w[ratio percent]
16
+
17
+ OP_NAME = "metric.timing"
18
+ SPAN_ORIGIN = "auto.metric.timing"
19
+
20
+ class << self
21
+ def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
22
+ Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
23
+ end
24
+
25
+ def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
26
+ Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
27
+ end
28
+
29
+ def set(key, value, unit: "none", tags: {}, timestamp: nil)
30
+ Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
31
+ end
32
+
33
+ def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
34
+ Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
35
+ end
36
+
37
+ def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
38
+ return unless block_given?
39
+ return yield unless DURATION_UNITS.include?(unit)
40
+
41
+ result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
42
+ tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
43
+
44
+ start = Timing.send(unit.to_sym)
45
+ result = yield
46
+ value = Timing.send(unit.to_sym) - start
47
+
48
+ [result, value]
49
+ end
50
+
51
+ Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
52
+ result
53
+ end
54
+ end
55
+ end
56
+ end
@@ -2,12 +2,16 @@
2
2
 
3
3
  require "net/http"
4
4
  require "resolv"
5
+ require "sentry/utils/http_tracing"
5
6
 
6
7
  module Sentry
7
8
  # @api private
8
9
  module Net
9
10
  module HTTP
11
+ include Utils::HttpTracing
12
+
10
13
  OP_NAME = "http.client"
14
+ SPAN_ORIGIN = "auto.http.net_http"
11
15
  BREADCRUMB_CATEGORY = "net.http"
12
16
 
13
17
  # To explain how the entire thing works, we need to know how the original Net::HTTP#request works
@@ -20,8 +24,7 @@ module Sentry
20
24
  # req['connection'] ||= 'close'
21
25
  # return request(req, body, &block) # <- request will be called for the second time from the first call
22
26
  # }
23
- # end
24
- # # .....
27
+ # end # .....
25
28
  # end
26
29
  # ```
27
30
  #
@@ -30,47 +33,29 @@ module Sentry
30
33
  return super unless started? && Sentry.initialized?
31
34
  return super if from_sentry_sdk?
32
35
 
33
- Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
36
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
34
37
  request_info = extract_request_info(req)
35
38
 
36
- if propagate_trace?(request_info[:url], Sentry.configuration)
39
+ if propagate_trace?(request_info[:url])
37
40
  set_propagation_headers(req)
38
41
  end
39
42
 
40
- super.tap do |res|
41
- record_sentry_breadcrumb(request_info, res)
43
+ res = super
44
+ response_status = res.code.to_i
42
45
 
43
- if sentry_span
44
- sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
45
- sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
46
- sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
47
- sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
48
- sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
49
- end
46
+ if record_sentry_breadcrumb?
47
+ record_sentry_breadcrumb(request_info, response_status)
50
48
  end
51
- end
52
- end
53
49
 
54
- private
50
+ if sentry_span
51
+ set_span_info(sentry_span, request_info, response_status)
52
+ end
55
53
 
56
- def set_propagation_headers(req)
57
- Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
54
+ res
55
+ end
58
56
  end
59
57
 
60
- def record_sentry_breadcrumb(request_info, res)
61
- return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
62
-
63
- crumb = Sentry::Breadcrumb.new(
64
- level: :info,
65
- category: BREADCRUMB_CATEGORY,
66
- type: :info,
67
- data: {
68
- status: res.code.to_i,
69
- **request_info
70
- }
71
- )
72
- Sentry.add_breadcrumb(crumb)
73
- end
58
+ private
74
59
 
75
60
  def from_sentry_sdk?
76
61
  dsn = Sentry.configuration.dsn
@@ -81,7 +66,7 @@ module Sentry
81
66
  # IPv6 url could look like '::1/path', and that won't parse without
82
67
  # wrapping it in square brackets.
83
68
  hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
84
- uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
69
+ uri = req.uri || URI.parse(URI::DEFAULT_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}"))
85
70
  url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
86
71
 
87
72
  result = { method: req.method, url: url }
@@ -93,12 +78,6 @@ module Sentry
93
78
 
94
79
  result
95
80
  end
96
-
97
- def propagate_trace?(url, configuration)
98
- url &&
99
- configuration.propagate_traces &&
100
- configuration.trace_propagation_targets.any? { |target| url.match?(target) }
101
- end
102
81
  end
103
82
  end
104
83
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Sentry
6
+ class Profiler
7
+ module Helpers
8
+ def in_app?(abs_path)
9
+ abs_path.match?(@in_app_pattern)
10
+ end
11
+
12
+ # copied from stacktrace.rb since I don't want to touch existing code
13
+ # TODO-neel-profiler try to fetch this from stackprof once we patch
14
+ # the native extension
15
+ def compute_filename(abs_path, in_app)
16
+ return nil if abs_path.nil?
17
+
18
+ under_project_root = @project_root && abs_path.start_with?(@project_root)
19
+
20
+ prefix =
21
+ if under_project_root && in_app
22
+ @project_root
23
+ else
24
+ longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
25
+
26
+ if under_project_root
27
+ longest_load_path || @project_root
28
+ else
29
+ longest_load_path
30
+ end
31
+ end
32
+
33
+ prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
34
+ end
35
+
36
+ def split_module(name)
37
+ # last module plus class/instance method
38
+ i = name.rindex("::")
39
+ function = i ? name[(i + 2)..-1] : name
40
+ mod = i ? name[0...i] : nil
41
+
42
+ [function, mod]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
+ require_relative "profiler/helpers"
4
5
 
5
6
  module Sentry
6
7
  class Profiler
7
- VERSION = '1'
8
- PLATFORM = 'ruby'
8
+ include Profiler::Helpers
9
+
10
+ VERSION = "1"
11
+ PLATFORM = "ruby"
9
12
  # 101 Hz in microseconds
10
13
  DEFAULT_INTERVAL = 1e6 / 101
11
14
  MICRO_TO_NANO_SECONDS = 1e3
@@ -14,14 +17,14 @@ module Sentry
14
17
  attr_reader :sampled, :started, :event_id
15
18
 
16
19
  def initialize(configuration)
17
- @event_id = SecureRandom.uuid.delete('-')
20
+ @event_id = SecureRandom.uuid.delete("-")
18
21
  @started = false
19
22
  @sampled = nil
20
23
 
21
24
  @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
22
25
  @profiles_sample_rate = configuration.profiles_sample_rate
23
26
  @project_root = configuration.project_root
24
- @app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
27
+ @app_dirs_pattern = configuration.app_dirs_pattern
25
28
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
26
29
  end
27
30
 
@@ -33,7 +36,7 @@ module Sentry
33
36
  raw: true,
34
37
  aggregate: false)
35
38
 
36
- @started ? log('Started') : log('Not started since running elsewhere')
39
+ @started ? log("Started") : log("Not started since running elsewhere")
37
40
  end
38
41
 
39
42
  def stop
@@ -41,7 +44,11 @@ module Sentry
41
44
  return unless @started
42
45
 
43
46
  StackProf.stop
44
- log('Stopped')
47
+ log("Stopped")
48
+ end
49
+
50
+ def active_thread_id
51
+ "0"
45
52
  end
46
53
 
47
54
  # Sets initial sampling decision of the profile.
@@ -54,14 +61,14 @@ module Sentry
54
61
 
55
62
  unless transaction_sampled
56
63
  @sampled = false
57
- log('Discarding profile because transaction not sampled')
64
+ log("Discarding profile because transaction not sampled")
58
65
  return
59
66
  end
60
67
 
61
68
  case @profiles_sample_rate
62
69
  when 0.0
63
70
  @sampled = false
64
- log('Discarding profile because sample_rate is 0')
71
+ log("Discarding profile because sample_rate is 0")
65
72
  return
66
73
  when 1.0
67
74
  @sampled = true
@@ -70,7 +77,7 @@ module Sentry
70
77
  @sampled = Random.rand < @profiles_sample_rate
71
78
  end
72
79
 
73
- log('Discarding profile due to sampling decision') unless @sampled
80
+ log("Discarding profile due to sampling decision") unless @sampled
74
81
  end
75
82
 
76
83
  def to_hash
@@ -90,13 +97,12 @@ module Sentry
90
97
 
91
98
  frame_map = {}
92
99
 
93
- frames = results[:frames].to_enum.with_index.map do |frame, idx|
94
- frame_id, frame_data = frame
95
-
100
+ frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
96
101
  # need to map over stackprof frame ids to ours
97
102
  frame_map[frame_id] = idx
98
103
 
99
104
  file_path = frame_data[:file]
105
+ lineno = frame_data[:line]
100
106
  in_app = in_app?(file_path)
101
107
  filename = compute_filename(file_path, in_app)
102
108
  function, mod = split_module(frame_data[:name])
@@ -109,7 +115,7 @@ module Sentry
109
115
  }
110
116
 
111
117
  frame_hash[:module] = mod if mod
112
- frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
118
+ frame_hash[:lineno] = lineno if lineno && lineno >= 0
113
119
 
114
120
  frame_hash
115
121
  end
@@ -130,7 +136,7 @@ module Sentry
130
136
  num_seen << results[:raw][idx + len]
131
137
  idx += len + 1
132
138
 
133
- log('Unknown frame in stack') if stack.size != len
139
+ log("Unknown frame in stack") if stack.size != len
134
140
  end
135
141
 
136
142
  idx = 0
@@ -155,16 +161,16 @@ module Sentry
155
161
  # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
156
162
  # we're profiling is idle/sleeping/waiting for IO etc.
157
163
  # https://bugs.ruby-lang.org/issues/10602
158
- thread_id: '0',
164
+ thread_id: "0",
159
165
  elapsed_since_start_ns: elapsed_since_start_ns.to_s
160
166
  }
161
167
  end
162
168
  end
163
169
 
164
- log('Some samples thrown away') if samples.size != results[:samples]
170
+ log("Some samples thrown away") if samples.size != results[:samples]
165
171
 
166
172
  if samples.size <= MIN_SAMPLES_REQUIRED
167
- log('Not enough samples, discarding profiler')
173
+ log("Not enough samples, discarding profiler")
168
174
  record_lost_event(:insufficient_data)
169
175
  return {}
170
176
  end
@@ -189,45 +195,8 @@ module Sentry
189
195
  Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
190
196
  end
191
197
 
192
- def in_app?(abs_path)
193
- abs_path.match?(@in_app_pattern)
194
- end
195
-
196
- # copied from stacktrace.rb since I don't want to touch existing code
197
- # TODO-neel-profiler try to fetch this from stackprof once we patch
198
- # the native extension
199
- def compute_filename(abs_path, in_app)
200
- return nil if abs_path.nil?
201
-
202
- under_project_root = @project_root && abs_path.start_with?(@project_root)
203
-
204
- prefix =
205
- if under_project_root && in_app
206
- @project_root
207
- else
208
- longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
209
-
210
- if under_project_root
211
- longest_load_path || @project_root
212
- else
213
- longest_load_path
214
- end
215
- end
216
-
217
- prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
218
- end
219
-
220
- def split_module(name)
221
- # last module plus class/instance method
222
- i = name.rindex('::')
223
- function = i ? name[(i + 2)..-1] : name
224
- mod = i ? name[0...i] : nil
225
-
226
- [function, mod]
227
- end
228
-
229
198
  def record_lost_event(reason)
230
- Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
199
+ Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
231
200
  end
232
201
  end
233
202
  end
@@ -50,14 +50,15 @@ module Sentry
50
50
  if sentry_trace_data
51
51
  @trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
52
52
 
53
- @baggage = if baggage_header && !baggage_header.empty?
54
- Baggage.from_incoming_header(baggage_header)
55
- else
56
- # If there's an incoming sentry-trace but no incoming baggage header,
57
- # for instance in traces coming from older SDKs,
58
- # baggage will be empty and frozen and won't be populated as head SDK.
59
- Baggage.new({})
60
- end
53
+ @baggage =
54
+ if baggage_header && !baggage_header.empty?
55
+ Baggage.from_incoming_header(baggage_header)
56
+ else
57
+ # If there's an incoming sentry-trace but no incoming baggage header,
58
+ # for instance in traces coming from older SDKs,
59
+ # baggage will be empty and frozen and won't be populated as head SDK.
60
+ Baggage.new({})
61
+ end
61
62
 
62
63
  @baggage.freeze!
63
64
  @incoming_trace = true
@@ -107,7 +108,7 @@ module Sentry
107
108
  end
108
109
 
109
110
  # Returns the Dynamic Sampling Context from the baggage.
110
- # @return [String, nil]
111
+ # @return [Hash, nil]
111
112
  def get_dynamic_sampling_context
112
113
  get_baggage&.dynamic_sampling_context
113
114
  end
data/lib/sentry/puma.rb CHANGED
@@ -7,7 +7,7 @@ module Sentry
7
7
  module Server
8
8
  PUMA_4_AND_PRIOR = Gem::Version.new(::Puma::Const::PUMA_VERSION) < Gem::Version.new("5.0.0")
9
9
 
10
- def lowlevel_error(e, env, status=500)
10
+ def lowlevel_error(e, env, status = 500)
11
11
  result =
12
12
  if PUMA_4_AND_PRIOR
13
13
  super(e, env)
@@ -4,6 +4,8 @@ module Sentry
4
4
  module Rack
5
5
  class CaptureExceptions
6
6
  ERROR_EVENT_ID_KEY = "sentry.error_event_id"
7
+ MECHANISM_TYPE = "rack"
8
+ SPAN_ORIGIN = "auto.http.rack"
7
9
 
8
10
  def initialize(app)
9
11
  @app = app
@@ -48,21 +50,27 @@ module Sentry
48
50
  private
49
51
 
50
52
  def collect_exception(env)
51
- env['rack.exception'] || env['sinatra.error']
53
+ env["rack.exception"] || env["sinatra.error"]
52
54
  end
53
55
 
54
56
  def transaction_op
55
- "http.server".freeze
57
+ "http.server"
56
58
  end
57
59
 
58
60
  def capture_exception(exception, env)
59
- Sentry.capture_exception(exception).tap do |event|
61
+ Sentry.capture_exception(exception, hint: { mechanism: mechanism }).tap do |event|
60
62
  env[ERROR_EVENT_ID_KEY] = event.event_id if event
61
63
  end
62
64
  end
63
65
 
64
66
  def start_transaction(env, scope)
65
- options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
67
+ options = {
68
+ name: scope.transaction_name,
69
+ source: scope.transaction_source,
70
+ op: transaction_op,
71
+ origin: SPAN_ORIGIN
72
+ }
73
+
66
74
  transaction = Sentry.continue_trace(env, **options)
67
75
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
68
76
  end
@@ -74,6 +82,10 @@ module Sentry
74
82
  transaction.set_http_status(status_code)
75
83
  transaction.finish
76
84
  end
85
+
86
+ def mechanism
87
+ Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
88
+ end
77
89
  end
78
90
  end
79
91
  end
data/lib/sentry/rack.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack'
3
+ require "rack"
4
4
 
5
- require 'sentry/rack/capture_exceptions'
5
+ require "sentry/rack/capture_exceptions"