sentry-ruby 5.16.1 → 5.21.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.
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"