sentry-ruby 5.16.1 → 5.20.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +20 -10
  4. data/Rakefile +1 -1
  5. data/bin/console +1 -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 +8 -6
  10. data/lib/sentry/baggage.rb +6 -6
  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 +53 -25
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/monitor_check_ins.rb +1 -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.rb +19 -2
  21. data/lib/sentry/error_event.rb +2 -2
  22. data/lib/sentry/event.rb +20 -18
  23. data/lib/sentry/faraday.rb +77 -0
  24. data/lib/sentry/graphql.rb +9 -0
  25. data/lib/sentry/hub.rb +23 -3
  26. data/lib/sentry/integrable.rb +4 -0
  27. data/lib/sentry/interface.rb +1 -0
  28. data/lib/sentry/interfaces/exception.rb +5 -3
  29. data/lib/sentry/interfaces/mechanism.rb +20 -0
  30. data/lib/sentry/interfaces/request.rb +7 -7
  31. data/lib/sentry/interfaces/single_exception.rb +7 -5
  32. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  33. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  34. data/lib/sentry/logger.rb +1 -1
  35. data/lib/sentry/metrics/aggregator.rb +248 -0
  36. data/lib/sentry/metrics/configuration.rb +47 -0
  37. data/lib/sentry/metrics/counter_metric.rb +25 -0
  38. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  39. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  40. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  41. data/lib/sentry/metrics/metric.rb +19 -0
  42. data/lib/sentry/metrics/set_metric.rb +28 -0
  43. data/lib/sentry/metrics/timing.rb +43 -0
  44. data/lib/sentry/metrics.rb +56 -0
  45. data/lib/sentry/net/http.rb +18 -39
  46. data/lib/sentry/profiler.rb +19 -20
  47. data/lib/sentry/propagation_context.rb +10 -9
  48. data/lib/sentry/puma.rb +1 -1
  49. data/lib/sentry/rack/capture_exceptions.rb +15 -3
  50. data/lib/sentry/rack.rb +2 -2
  51. data/lib/sentry/rake.rb +4 -2
  52. data/lib/sentry/redis.rb +2 -1
  53. data/lib/sentry/release_detector.rb +4 -4
  54. data/lib/sentry/scope.rb +36 -26
  55. data/lib/sentry/session.rb +2 -2
  56. data/lib/sentry/session_flusher.rb +7 -39
  57. data/lib/sentry/span.rb +46 -5
  58. data/lib/sentry/test_helper.rb +3 -2
  59. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  60. data/lib/sentry/transaction.rb +17 -15
  61. data/lib/sentry/transaction_event.rb +6 -1
  62. data/lib/sentry/transport/configuration.rb +0 -1
  63. data/lib/sentry/transport/http_transport.rb +12 -12
  64. data/lib/sentry/transport.rb +18 -26
  65. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  66. data/lib/sentry/utils/env_helper.rb +21 -0
  67. data/lib/sentry/utils/http_tracing.rb +41 -0
  68. data/lib/sentry/utils/logging_helper.rb +0 -4
  69. data/lib/sentry/utils/real_ip.rb +2 -2
  70. data/lib/sentry/utils/request_id.rb +1 -1
  71. data/lib/sentry/version.rb +1 -1
  72. data/lib/sentry-ruby.rb +34 -3
  73. data/sentry-ruby-core.gemspec +1 -1
  74. data/sentry-ruby.gemspec +13 -6
  75. metadata +40 -7
@@ -1,58 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- class SessionFlusher
5
- include LoggingHelper
6
-
4
+ class SessionFlusher < ThreadedPeriodicWorker
7
5
  FLUSH_INTERVAL = 60
8
6
 
9
7
  def initialize(configuration, client)
10
- @thread = nil
11
- @exited = false
8
+ super(configuration.logger, FLUSH_INTERVAL)
12
9
  @client = client
13
10
  @pending_aggregates = {}
14
11
  @release = configuration.release
15
12
  @environment = configuration.environment
16
- @logger = configuration.logger
17
13
 
18
14
  log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
19
15
  end
20
16
 
21
17
  def flush
22
18
  return if @pending_aggregates.empty?
23
- envelope = pending_envelope
24
-
25
- Sentry.background_worker.perform do
26
- @client.transport.send_envelope(envelope)
27
- end
28
19
 
20
+ @client.capture_envelope(pending_envelope)
29
21
  @pending_aggregates = {}
30
22
  end
31
23
 
24
+ alias_method :run, :flush
25
+
32
26
  def add_session(session)
33
- return if @exited
34
27
  return unless @release
35
28
 
36
- begin
37
- ensure_thread
38
- rescue ThreadError
39
- log_debug("Session flusher thread creation failed")
40
- @exited = true
41
- return
42
- end
29
+ return unless ensure_thread
43
30
 
44
31
  return unless Session::AGGREGATE_STATUSES.include?(session.status)
45
32
  @pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
46
33
  @pending_aggregates[session.aggregation_key][session.status] += 1
47
34
  end
48
35
 
49
- def kill
50
- log_debug("Killing session flusher")
51
-
52
- @exited = true
53
- @thread&.kill
54
- end
55
-
56
36
  private
57
37
 
58
38
  def init_aggregates(aggregation_key)
@@ -64,7 +44,7 @@ module Sentry
64
44
  def pending_envelope
65
45
  envelope = Envelope.new
66
46
 
67
- header = { type: 'sessions' }
47
+ header = { type: "sessions" }
68
48
  payload = { attrs: attrs, aggregates: @pending_aggregates.values }
69
49
 
70
50
  envelope.add_item(header, payload)
@@ -74,17 +54,5 @@ module Sentry
74
54
  def attrs
75
55
  { release: @release, environment: @environment }
76
56
  end
77
-
78
- def ensure_thread
79
- return if @thread&.alive?
80
-
81
- @thread = Thread.new do
82
- loop do
83
- sleep(FLUSH_INTERVAL)
84
- flush
85
- end
86
- end
87
- end
88
-
89
57
  end
90
58
  end
data/lib/sentry/span.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "sentry/metrics/local_aggregator"
4
5
 
5
6
  module Sentry
6
7
  class Span
7
-
8
8
  # We will try to be consistent with OpenTelemetry on this front going forward.
9
9
  # https://develop.sentry.dev/sdk/performance/span-data-conventions/
10
10
  module DataConventions
@@ -39,6 +39,11 @@ module Sentry
39
39
  # Recommended: If different than server.port.
40
40
  # Example: 16456
41
41
  SERVER_SOCKET_PORT = "server.socket.port"
42
+
43
+ FILEPATH = "code.filepath"
44
+ LINENO = "code.lineno"
45
+ FUNCTION = "code.function"
46
+ NAMESPACE = "code.namespace"
42
47
  end
43
48
 
44
49
  STATUS_MAP = {
@@ -55,6 +60,8 @@ module Sentry
55
60
  504 => "deadline_exceeded"
56
61
  }
57
62
 
63
+ DEFAULT_SPAN_ORIGIN = "manual"
64
+
58
65
  # An uuid that can be used to identify a trace.
59
66
  # @return [String]
60
67
  attr_reader :trace_id
@@ -88,6 +95,9 @@ module Sentry
88
95
  # Span data
89
96
  # @return [Hash]
90
97
  attr_reader :data
98
+ # Span origin that tracks what kind of instrumentation created a span
99
+ # @return [String]
100
+ attr_reader :origin
91
101
 
92
102
  # The SpanRecorder the current span belongs to.
93
103
  # SpanRecorder holds all spans under the same Transaction object (including the Transaction itself).
@@ -109,7 +119,8 @@ module Sentry
109
119
  parent_span_id: nil,
110
120
  sampled: nil,
111
121
  start_timestamp: nil,
112
- timestamp: nil
122
+ timestamp: nil,
123
+ origin: nil
113
124
  )
114
125
  @trace_id = trace_id || SecureRandom.uuid.delete("-")
115
126
  @span_id = span_id || SecureRandom.uuid.delete("-").slice(0, 16)
@@ -123,6 +134,7 @@ module Sentry
123
134
  @status = status
124
135
  @data = {}
125
136
  @tags = {}
137
+ @origin = origin || DEFAULT_SPAN_ORIGIN
126
138
  end
127
139
 
128
140
  # Finishes the span by adding a timestamp.
@@ -148,9 +160,15 @@ module Sentry
148
160
  transaction.get_baggage&.serialize
149
161
  end
150
162
 
163
+ # Returns the Dynamic Sampling Context from the transaction baggage.
164
+ # @return [Hash, nil]
165
+ def get_dynamic_sampling_context
166
+ transaction.get_baggage&.dynamic_sampling_context
167
+ end
168
+
151
169
  # @return [Hash]
152
170
  def to_hash
153
- {
171
+ hash = {
154
172
  trace_id: @trace_id,
155
173
  span_id: @span_id,
156
174
  parent_span_id: @parent_span_id,
@@ -160,8 +178,14 @@ module Sentry
160
178
  op: @op,
161
179
  status: @status,
162
180
  tags: @tags,
163
- data: @data
181
+ data: @data,
182
+ origin: @origin
164
183
  }
184
+
185
+ summary = metrics_summary
186
+ hash[:_metrics_summary] = summary if summary
187
+
188
+ hash
165
189
  end
166
190
 
167
191
  # Returns the span's context that can be used to embed in an Event.
@@ -173,7 +197,9 @@ module Sentry
173
197
  parent_span_id: @parent_span_id,
174
198
  description: @description,
175
199
  op: @op,
176
- status: @status
200
+ status: @status,
201
+ origin: @origin,
202
+ data: @data
177
203
  }
178
204
  end
179
205
 
@@ -269,5 +295,20 @@ module Sentry
269
295
  def set_tag(key, value)
270
296
  @tags[key] = value
271
297
  end
298
+
299
+ # Sets the origin of the span.
300
+ # @param origin [String]
301
+ def set_origin(origin)
302
+ @origin = origin
303
+ end
304
+
305
+ # Collects gauge metrics on the span for metric summaries.
306
+ def metrics_local_aggregator
307
+ @metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
308
+ end
309
+
310
+ def metrics_summary
311
+ @metrics_local_aggregator&.to_hash
312
+ end
272
313
  end
273
314
  end
@@ -1,6 +1,6 @@
1
1
  module Sentry
2
2
  module TestHelper
3
- DUMMY_DSN = 'http://12345:67890@sentry.localdomain/sentry/42'
3
+ DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
4
4
 
5
5
  # Alters the existing SDK configuration with test-suitable options. Mainly:
6
6
  # - Sets a dummy DSN instead of `nil` or an actual DSN.
@@ -20,7 +20,7 @@ module Sentry
20
20
  # set transport to DummyTransport, so we can easily intercept the captured events
21
21
  dummy_config.transport.transport_class = Sentry::DummyTransport
22
22
  # make sure SDK allows sending under the current environment
23
- dummy_config.enabled_environments << dummy_config.environment unless dummy_config.enabled_environments.include?(dummy_config.environment)
23
+ dummy_config.enabled_environments += [dummy_config.environment] unless dummy_config.enabled_environments.include?(dummy_config.environment)
24
24
  # disble async event sending
25
25
  dummy_config.background_worker_threads = 0
26
26
 
@@ -50,6 +50,7 @@ module Sentry
50
50
  if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
51
51
  Sentry.get_current_hub.pop_scope
52
52
  end
53
+ Sentry::Scope.global_event_processors.clear
53
54
  end
54
55
 
55
56
  # @return [Transport]
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class ThreadedPeriodicWorker
5
+ include LoggingHelper
6
+
7
+ def initialize(logger, internal)
8
+ @thread = nil
9
+ @exited = false
10
+ @interval = internal
11
+ @logger = logger
12
+ end
13
+
14
+ def ensure_thread
15
+ return false if @exited
16
+ return true if @thread&.alive?
17
+
18
+ @thread = Thread.new do
19
+ loop do
20
+ sleep(@interval)
21
+ run
22
+ end
23
+ end
24
+
25
+ true
26
+ rescue ThreadError
27
+ log_debug("[#{self.class.name}] thread creation failed")
28
+ @exited = true
29
+ false
30
+ end
31
+
32
+ def kill
33
+ log_debug("[#{self.class.name}] thread killed")
34
+
35
+ @exited = true
36
+ @thread&.kill
37
+ end
38
+ end
39
+ end
@@ -13,7 +13,7 @@ module Sentry
13
13
  MESSAGE_PREFIX = "[Tracing]"
14
14
 
15
15
  # https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
16
- SOURCES = %i(custom url route view component task)
16
+ SOURCES = %i[custom url route view component task]
17
17
 
18
18
  include LoggingHelper
19
19
 
@@ -110,14 +110,15 @@ module Sentry
110
110
 
111
111
  trace_id, parent_span_id, parent_sampled = sentry_trace_data
112
112
 
113
- baggage = if baggage && !baggage.empty?
114
- Baggage.from_incoming_header(baggage)
115
- else
116
- # If there's an incoming sentry-trace but no incoming baggage header,
117
- # for instance in traces coming from older SDKs,
118
- # baggage will be empty and frozen and won't be populated as head SDK.
119
- Baggage.new({})
120
- end
113
+ baggage =
114
+ if baggage && !baggage.empty?
115
+ Baggage.from_incoming_header(baggage)
116
+ else
117
+ # If there's an incoming sentry-trace but no incoming baggage header,
118
+ # for instance in traces coming from older SDKs,
119
+ # baggage will be empty and frozen and won't be populated as head SDK.
120
+ Baggage.new({})
121
+ end
121
122
 
122
123
  baggage.freeze!
123
124
 
@@ -264,7 +265,8 @@ module Sentry
264
265
  else
265
266
  is_backpressure = Sentry.backpressure_monitor&.downsample_factor&.positive?
266
267
  reason = is_backpressure ? :backpressure : :sample_rate
267
- hub.current_client.transport.record_lost_event(reason, 'transaction')
268
+ hub.current_client.transport.record_lost_event(reason, "transaction")
269
+ hub.current_client.transport.record_lost_event(reason, "span")
268
270
  end
269
271
  end
270
272
 
@@ -301,6 +303,11 @@ module Sentry
301
303
  profiler.start
302
304
  end
303
305
 
306
+ # These are high cardinality and thus bad
307
+ def source_low_quality?
308
+ source == :url
309
+ end
310
+
304
311
  protected
305
312
 
306
313
  def init_span_recorder(limit = 1000)
@@ -336,11 +343,6 @@ module Sentry
336
343
  @baggage = Baggage.new(items, mutable: false)
337
344
  end
338
345
 
339
- # These are high cardinality and thus bad
340
- def source_low_quality?
341
- source == :url
342
- end
343
-
344
346
  class SpanRecorder
345
347
  attr_reader :max_length, :spans
346
348
 
@@ -17,6 +17,9 @@ module Sentry
17
17
  # @return [Hash, nil]
18
18
  attr_accessor :profile
19
19
 
20
+ # @return [Hash, nil]
21
+ attr_accessor :metrics_summary
22
+
20
23
  def initialize(transaction:, **options)
21
24
  super(**options)
22
25
 
@@ -29,6 +32,7 @@ module Sentry
29
32
  self.tags = transaction.tags
30
33
  self.dynamic_sampling_context = transaction.get_baggage.dynamic_sampling_context
31
34
  self.measurements = transaction.measurements
35
+ self.metrics_summary = transaction.metrics_summary
32
36
 
33
37
  finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
34
38
  self.spans = finished_spans.map(&:to_hash)
@@ -49,6 +53,7 @@ module Sentry
49
53
  data[:spans] = @spans.map(&:to_hash) if @spans
50
54
  data[:start_timestamp] = @start_timestamp
51
55
  data[:measurements] = @measurements
56
+ data[:_metrics_summary] = @metrics_summary if @metrics_summary
52
57
  data
53
58
  end
54
59
 
@@ -70,7 +75,7 @@ module Sentry
70
75
  name: transaction.name,
71
76
  trace_id: transaction.trace_id,
72
77
  # TODO-neel-profiler stubbed for now, see thread_id note in profiler.rb
73
- active_thead_id: '0'
78
+ active_thead_id: "0"
74
79
  }
75
80
  )
76
81
 
@@ -3,7 +3,6 @@
3
3
  module Sentry
4
4
  class Transport
5
5
  class Configuration
6
-
7
6
  # The timeout in seconds to open a connection to Sentry, in seconds.
8
7
  # Default value is 2.
9
8
  #
@@ -7,7 +7,7 @@ module Sentry
7
7
  class HTTPTransport < Transport
8
8
  GZIP_ENCODING = "gzip"
9
9
  GZIP_THRESHOLD = 1024 * 30
10
- CONTENT_TYPE = 'application/x-sentry-envelope'
10
+ CONTENT_TYPE = "application/x-sentry-envelope"
11
11
 
12
12
  DEFAULT_DELAY = 60
13
13
  RETRY_AFTER_HEADER = "retry-after"
@@ -38,13 +38,13 @@ module Sentry
38
38
  end
39
39
 
40
40
  headers = {
41
- 'Content-Type' => CONTENT_TYPE,
42
- 'Content-Encoding' => encoding,
43
- 'User-Agent' => USER_AGENT
41
+ "Content-Type" => CONTENT_TYPE,
42
+ "Content-Encoding" => encoding,
43
+ "User-Agent" => USER_AGENT
44
44
  }
45
45
 
46
46
  auth_header = generate_auth_header
47
- headers['X-Sentry-Auth'] = auth_header if auth_header
47
+ headers["X-Sentry-Auth"] = auth_header if auth_header
48
48
 
49
49
  response = conn.start do |http|
50
50
  request = ::Net::HTTP::Post.new(endpoint, headers)
@@ -60,7 +60,7 @@ module Sentry
60
60
  else
61
61
  error_info = "the server responded with status #{response.code}"
62
62
  error_info += "\nbody: #{response.body}"
63
- error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
63
+ error_info += " Error in headers is: #{response['x-sentry-error']}" if response["x-sentry-error"]
64
64
 
65
65
  raise Sentry::ExternalError, error_info
66
66
  end
@@ -78,13 +78,13 @@ module Sentry
78
78
 
79
79
  now = Sentry.utc_now.to_i
80
80
  fields = {
81
- 'sentry_version' => PROTOCOL_VERSION,
82
- 'sentry_client' => USER_AGENT,
83
- 'sentry_timestamp' => now,
84
- 'sentry_key' => @dsn.public_key
81
+ "sentry_version" => PROTOCOL_VERSION,
82
+ "sentry_client" => USER_AGENT,
83
+ "sentry_timestamp" => now,
84
+ "sentry_key" => @dsn.public_key
85
85
  }
86
- fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
87
- 'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
86
+ fields["sentry_secret"] = @dsn.secret_key if @dsn.secret_key
87
+ "Sentry " + fields.map { |key, value| "#{key}=#{value}" }.join(", ")
88
88
  end
89
89
 
90
90
  def conn
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
- require "base64"
5
4
  require "sentry/envelope"
6
5
 
7
6
  module Sentry
8
7
  class Transport
9
- PROTOCOL_VERSION = '7'
8
+ PROTOCOL_VERSION = "7"
10
9
  USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
11
10
  CLIENT_REPORT_INTERVAL = 30
12
11
 
@@ -62,7 +61,7 @@ module Sentry
62
61
  data, serialized_items = serialize_envelope(envelope)
63
62
 
64
63
  if data
65
- log_info("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
64
+ log_debug("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
66
65
  send_data(data)
67
66
  end
68
67
  end
@@ -89,18 +88,9 @@ module Sentry
89
88
  [data, serialized_items]
90
89
  end
91
90
 
92
- def is_rate_limited?(item_type)
91
+ def is_rate_limited?(data_category)
93
92
  # check category-specific limit
94
- category_delay =
95
- case item_type
96
- when "transaction"
97
- @rate_limits["transaction"]
98
- when "sessions"
99
- @rate_limits["session"]
100
- else
101
- @rate_limits["error"]
102
- end
103
-
93
+ category_delay = @rate_limits[data_category]
104
94
  # check universal limit if not category limit
105
95
  universal_delay = @rate_limits[nil]
106
96
 
@@ -144,28 +134,34 @@ module Sentry
144
134
  envelope = Envelope.new(envelope_headers)
145
135
 
146
136
  envelope.add_item(
147
- { type: item_type, content_type: 'application/json' },
137
+ { type: item_type, content_type: "application/json" },
148
138
  event_payload
149
139
  )
150
140
 
151
141
  if event.is_a?(TransactionEvent) && event.profile
152
142
  envelope.add_item(
153
- { type: 'profile', content_type: 'application/json' },
143
+ { type: "profile", content_type: "application/json" },
154
144
  event.profile
155
145
  )
156
146
  end
157
147
 
148
+ if event.is_a?(Event) && event.attachments.any?
149
+ event.attachments.each do |attachment|
150
+ envelope.add_item(attachment.to_envelope_headers, attachment.payload)
151
+ end
152
+ end
153
+
158
154
  client_report_headers, client_report_payload = fetch_pending_client_report
159
155
  envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
160
156
 
161
157
  envelope
162
158
  end
163
159
 
164
- def record_lost_event(reason, item_type)
160
+ def record_lost_event(reason, data_category, num: 1)
165
161
  return unless @send_client_reports
166
162
  return unless CLIENT_REPORT_REASONS.include?(reason)
167
163
 
168
- @discarded_events[[reason, item_type]] += 1
164
+ @discarded_events[[reason, data_category]] += num
169
165
  end
170
166
 
171
167
  def flush
@@ -185,15 +181,11 @@ module Sentry
185
181
  return nil if @discarded_events.empty?
186
182
 
187
183
  discarded_events_hash = @discarded_events.map do |key, val|
188
- reason, type = key
189
-
190
- # 'event' has to be mapped to 'error'
191
- category = type == 'event' ? 'error' : type
192
-
184
+ reason, category = key
193
185
  { reason: reason, category: category, quantity: val }
194
186
  end
195
187
 
196
- item_header = { type: 'client_report' }
188
+ item_header = { type: "client_report" }
197
189
  item_payload = {
198
190
  timestamp: Sentry.utc_now.iso8601,
199
191
  discarded_events: discarded_events_hash
@@ -207,9 +199,9 @@ module Sentry
207
199
 
208
200
  def reject_rate_limited_items(envelope)
209
201
  envelope.items.reject! do |item|
210
- if is_rate_limited?(item.type)
202
+ if is_rate_limited?(item.data_category)
211
203
  log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
212
- record_lost_event(:ratelimit_backoff, item.type)
204
+ record_lost_event(:ratelimit_backoff, item.data_category)
213
205
 
214
206
  true
215
207
  else
@@ -15,5 +15,11 @@ module Sentry
15
15
  raise ArgumentError, "expect the argument to be one of #{values.map(&:inspect).join(' or ')}, got #{argument.inspect}"
16
16
  end
17
17
  end
18
+
19
+ def check_callable!(name, value)
20
+ unless value == nil || value.respond_to?(:call)
21
+ raise ArgumentError, "#{name} must be callable (or nil to disable)"
22
+ end
23
+ end
18
24
  end
19
25
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Utils
5
+ module EnvHelper
6
+ TRUTHY_ENV_VALUES = %w[t true yes y 1 on].freeze
7
+ FALSY_ENV_VALUES = %w[f false no n 0 off].freeze
8
+
9
+ def self.env_to_bool(value, strict: false)
10
+ value = value.to_s
11
+ normalized = value.downcase
12
+
13
+ return false if FALSY_ENV_VALUES.include?(normalized)
14
+
15
+ return true if TRUTHY_ENV_VALUES.include?(normalized)
16
+
17
+ strict ? nil : !(value.nil? || value.empty?)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Utils
5
+ module HttpTracing
6
+ def set_span_info(sentry_span, request_info, response_status)
7
+ sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
8
+ sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
9
+ sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
10
+ sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
11
+ sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, response_status)
12
+ end
13
+
14
+ def set_propagation_headers(req)
15
+ Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
16
+ end
17
+
18
+ def record_sentry_breadcrumb(request_info, response_status)
19
+ crumb = Sentry::Breadcrumb.new(
20
+ level: :info,
21
+ category: self.class::BREADCRUMB_CATEGORY,
22
+ type: :info,
23
+ data: { status: response_status, **request_info }
24
+ )
25
+
26
+ Sentry.add_breadcrumb(crumb)
27
+ end
28
+
29
+ def record_sentry_breadcrumb?
30
+ Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
31
+ end
32
+
33
+ def propagate_trace?(url)
34
+ url &&
35
+ Sentry.initialized? &&
36
+ Sentry.configuration.propagate_traces &&
37
+ Sentry.configuration.trace_propagation_targets.any? { |target| url.match?(target) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -11,10 +11,6 @@ module Sentry
11
11
  end
12
12
  end
13
13
 
14
- def log_info(message)
15
- @logger.info(LOGGER_PROGNAME) { message }
16
- end
17
-
18
14
  def log_debug(message)
19
15
  @logger.debug(LOGGER_PROGNAME) { message }
20
16
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ipaddr'
3
+ require "ipaddr"
4
4
 
5
5
  # Based on ActionDispatch::RemoteIp. All security-related precautions from that
6
6
  # middleware have been removed, because the Event IP just needs to be accurate,
@@ -15,7 +15,7 @@ module Sentry
15
15
  "fc00::/7", # private IPv6 range fc00::/7
16
16
  "10.0.0.0/8", # private IPv4 range 10.x.x.x
17
17
  "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
18
- "192.168.0.0/16", # private IPv4 range 192.168.x.x
18
+ "192.168.0.0/16" # private IPv4 range 192.168.x.x
19
19
  ]
20
20
 
21
21
  attr_reader :ip
@@ -3,7 +3,7 @@
3
3
  module Sentry
4
4
  module Utils
5
5
  module RequestId
6
- REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
6
+ REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
7
7
 
8
8
  # Request ID based on ActionDispatch::RequestId
9
9
  def self.read_from(env)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.16.1"
4
+ VERSION = "5.20.1"
5
5
  end