sentry-ruby 5.28.1 → 6.3.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +25 -1
  3. data/README.md +2 -2
  4. data/lib/sentry/background_worker.rb +1 -4
  5. data/lib/sentry/backtrace/line.rb +99 -0
  6. data/lib/sentry/backtrace.rb +44 -76
  7. data/lib/sentry/breadcrumb.rb +1 -1
  8. data/lib/sentry/breadcrumb_buffer.rb +2 -2
  9. data/lib/sentry/check_in_event.rb +2 -2
  10. data/lib/sentry/client.rb +57 -135
  11. data/lib/sentry/configuration.rb +117 -75
  12. data/lib/sentry/cron/monitor_check_ins.rb +3 -3
  13. data/lib/sentry/cron/monitor_config.rb +2 -2
  14. data/lib/sentry/cron/monitor_schedule.rb +2 -2
  15. data/lib/sentry/envelope/item.rb +3 -3
  16. data/lib/sentry/error_event.rb +3 -3
  17. data/lib/sentry/event.rb +4 -10
  18. data/lib/sentry/hub.rb +26 -4
  19. data/lib/sentry/interface.rb +1 -1
  20. data/lib/sentry/interfaces/exception.rb +2 -2
  21. data/lib/sentry/interfaces/request.rb +2 -0
  22. data/lib/sentry/interfaces/single_exception.rb +4 -4
  23. data/lib/sentry/interfaces/stacktrace.rb +3 -3
  24. data/lib/sentry/interfaces/stacktrace_builder.rb +0 -8
  25. data/lib/sentry/interfaces/threads.rb +2 -2
  26. data/lib/sentry/log_event.rb +24 -142
  27. data/lib/sentry/log_event_buffer.rb +13 -60
  28. data/lib/sentry/metric_event.rb +49 -0
  29. data/lib/sentry/metric_event_buffer.rb +28 -0
  30. data/lib/sentry/metrics.rb +47 -54
  31. data/lib/sentry/profiler.rb +4 -5
  32. data/lib/sentry/rack/capture_exceptions.rb +5 -1
  33. data/lib/sentry/rspec.rb +1 -1
  34. data/lib/sentry/scope.rb +50 -18
  35. data/lib/sentry/sequel.rb +35 -0
  36. data/lib/sentry/span.rb +2 -17
  37. data/lib/sentry/std_lib_logger.rb +4 -0
  38. data/lib/sentry/telemetry_event_buffer.rb +130 -0
  39. data/lib/sentry/test_helper.rb +8 -0
  40. data/lib/sentry/transaction.rb +52 -103
  41. data/lib/sentry/transaction_event.rb +4 -9
  42. data/lib/sentry/transport.rb +2 -5
  43. data/lib/sentry/utils/encoding_helper.rb +6 -0
  44. data/lib/sentry/utils/logging_helper.rb +25 -9
  45. data/lib/sentry/utils/telemetry_attributes.rb +30 -0
  46. data/lib/sentry/vernier/profiler.rb +4 -3
  47. data/lib/sentry/version.rb +1 -1
  48. data/lib/sentry-ruby.rb +53 -30
  49. data/sentry-ruby-core.gemspec +1 -1
  50. data/sentry-ruby.gemspec +2 -1
  51. metadata +27 -16
  52. data/lib/sentry/metrics/aggregator.rb +0 -248
  53. data/lib/sentry/metrics/configuration.rb +0 -57
  54. data/lib/sentry/metrics/counter_metric.rb +0 -25
  55. data/lib/sentry/metrics/distribution_metric.rb +0 -25
  56. data/lib/sentry/metrics/gauge_metric.rb +0 -35
  57. data/lib/sentry/metrics/local_aggregator.rb +0 -53
  58. data/lib/sentry/metrics/metric.rb +0 -19
  59. data/lib/sentry/metrics/set_metric.rb +0 -28
  60. data/lib/sentry/metrics/timing.rb +0 -51
data/lib/sentry/span.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
- require "sentry/metrics/local_aggregator"
5
4
  require "sentry/utils/uuid"
6
5
 
7
6
  module Sentry
@@ -173,8 +172,8 @@ module Sentry
173
172
  end
174
173
 
175
174
  # @return [Hash]
176
- def to_hash
177
- hash = {
175
+ def to_h
176
+ {
178
177
  trace_id: @trace_id,
179
178
  span_id: @span_id,
180
179
  parent_span_id: @parent_span_id,
@@ -187,11 +186,6 @@ module Sentry
187
186
  data: @data,
188
187
  origin: @origin
189
188
  }
190
-
191
- summary = metrics_summary
192
- hash[:_metrics_summary] = summary if summary
193
-
194
- hash
195
189
  end
196
190
 
197
191
  # Returns the span's context that can be used to embed in an Event.
@@ -307,14 +301,5 @@ module Sentry
307
301
  def set_origin(origin)
308
302
  @origin = origin
309
303
  end
310
-
311
- # Collects gauge metrics on the span for metric summaries.
312
- def metrics_local_aggregator
313
- @metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
314
- end
315
-
316
- def metrics_summary
317
- @metrics_local_aggregator&.to_hash
318
- end
319
304
  end
320
305
  end
@@ -37,6 +37,10 @@ module Sentry
37
37
  message = message.to_s.strip
38
38
 
39
39
  if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
40
+ if (filter = Sentry.configuration.std_lib_logger_filter) && !filter.call(self, message, method)
41
+ return result
42
+ end
43
+
40
44
  Sentry.logger.send(method, message, origin: ORIGIN)
41
45
  end
42
46
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/threaded_periodic_worker"
4
+ require "sentry/envelope"
5
+
6
+ module Sentry
7
+ # TelemetryEventBuffer is a base class for buffering telemetry events (logs, metrics, etc.)
8
+ # and sending them to Sentry in a single envelope.
9
+ #
10
+ # This is used internally by the `Sentry::Client`.
11
+ #
12
+ # @!visibility private
13
+ class TelemetryEventBuffer < ThreadedPeriodicWorker
14
+ FLUSH_INTERVAL = 5 # seconds
15
+
16
+ # @!visibility private
17
+ attr_reader :pending_items, :envelope_type, :data_category, :thread
18
+
19
+ def initialize(configuration, client, event_class:, max_items:, max_items_before_drop:, envelope_type:, envelope_content_type:, before_send:)
20
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
21
+
22
+ @client = client
23
+ @dsn = configuration.dsn
24
+ @debug = configuration.debug
25
+ @event_class = event_class
26
+ @max_items = max_items
27
+ @max_items_before_drop = max_items_before_drop
28
+ @envelope_type = envelope_type
29
+ @data_category = Sentry::Envelope::Item.data_category(@envelope_type)
30
+ @envelope_content_type = envelope_content_type
31
+ @before_send = before_send
32
+
33
+ @pending_items = []
34
+ @mutex = Mutex.new
35
+
36
+ log_debug("[#{self.class}] Initialized buffer with max_items=#{@max_items}, flush_interval=#{FLUSH_INTERVAL}s")
37
+ end
38
+
39
+ def flush
40
+ @mutex.synchronize do
41
+ return if empty?
42
+
43
+ log_debug("[#{self.class}] flushing #{size} #{@event_class}")
44
+
45
+ send_items
46
+ end
47
+
48
+ self
49
+ end
50
+ alias_method :run, :flush
51
+
52
+ def add_item(item)
53
+ @mutex.synchronize do
54
+ return unless ensure_thread
55
+
56
+ if size >= @max_items_before_drop
57
+ log_debug("[#{self.class}] exceeded max capacity, dropping event")
58
+ @client.transport.record_lost_event(:queue_overflow, @data_category)
59
+ else
60
+ @pending_items << item
61
+ end
62
+
63
+ send_items if size >= @max_items
64
+ end
65
+
66
+ self
67
+ end
68
+
69
+ def empty?
70
+ @pending_items.empty?
71
+ end
72
+
73
+ def size
74
+ @pending_items.size
75
+ end
76
+
77
+ def clear!
78
+ @pending_items.clear
79
+ end
80
+
81
+ private
82
+
83
+ def send_items
84
+ envelope = Envelope.new(
85
+ event_id: Sentry::Utils.uuid,
86
+ sent_at: Sentry.utc_now.iso8601,
87
+ dsn: @dsn,
88
+ sdk: Sentry.sdk_meta
89
+ )
90
+
91
+ discarded_count = 0
92
+ envelope_items = []
93
+
94
+ if @before_send
95
+ @pending_items.each do |item|
96
+ processed_item = @before_send.call(item)
97
+
98
+ if processed_item
99
+ envelope_items << processed_item.to_h
100
+ else
101
+ discarded_count += 1
102
+ end
103
+ end
104
+ else
105
+ envelope_items = @pending_items.map(&:to_h)
106
+ end
107
+
108
+ unless discarded_count.zero?
109
+ @client.transport.record_lost_event(:before_send, @data_category, num: discarded_count)
110
+ end
111
+
112
+ return if envelope_items.empty?
113
+
114
+ envelope.add_item(
115
+ {
116
+ type: @envelope_type,
117
+ item_count: envelope_items.size,
118
+ content_type: @envelope_content_type
119
+ },
120
+ { items: envelope_items }
121
+ )
122
+
123
+ @client.send_envelope(envelope)
124
+ rescue => e
125
+ log_error("[#{self.class}] Failed to send #{@event_class}", e, debug: @debug)
126
+ ensure
127
+ clear!
128
+ end
129
+ end
130
+ end
@@ -27,6 +27,7 @@ module Sentry
27
27
  # set transport to DummyTransport, so we can easily intercept the captured events
28
28
  dummy_config.transport.transport_class = Sentry::DummyTransport
29
29
  # make sure SDK allows sending under the current environment
30
+ dummy_config.enabled_environments ||= []
30
31
  dummy_config.enabled_environments += [dummy_config.environment] unless dummy_config.enabled_environments.include?(dummy_config.environment)
31
32
  # disble async event sending
32
33
  dummy_config.background_worker_threads = 0
@@ -101,6 +102,13 @@ module Sentry
101
102
  .flat_map { |item| item.payload[:items] }
102
103
  end
103
104
 
105
+ def sentry_metrics
106
+ sentry_envelopes
107
+ .flat_map(&:items)
108
+ .select { |item| item.headers[:type] == "trace_metric" }
109
+ .flat_map { |item| item.payload[:items] }
110
+ end
111
+
104
112
  # Returns the last captured event object.
105
113
  # @return [Event, nil]
106
114
  def last_sentry_event
@@ -7,9 +7,6 @@ require "sentry/propagation_context"
7
7
 
8
8
  module Sentry
9
9
  class Transaction < Span
10
- # @deprecated Use Sentry::PropagationContext::SENTRY_TRACE_REGEXP instead.
11
- SENTRY_TRACE_REGEXP = PropagationContext::SENTRY_TRACE_REGEXP
12
-
13
10
  UNLABELD_NAME = "<unlabeled transaction>"
14
11
  MESSAGE_PREFIX = "[Tracing]"
15
12
 
@@ -40,12 +37,6 @@ module Sentry
40
37
  # @return [Hash]
41
38
  attr_reader :measurements
42
39
 
43
- # @deprecated Use Sentry.get_current_hub instead.
44
- attr_reader :hub
45
-
46
- # @deprecated Use Sentry.configuration instead.
47
- attr_reader :configuration
48
-
49
40
  # The effective sample rate at which this transaction was sampled.
50
41
  # @return [Float, nil]
51
42
  attr_reader :effective_sample_rate
@@ -63,7 +54,6 @@ module Sentry
63
54
  attr_reader :sample_rand
64
55
 
65
56
  def initialize(
66
- hub:,
67
57
  name: nil,
68
58
  source: :custom,
69
59
  parent_sampled: nil,
@@ -75,26 +65,14 @@ module Sentry
75
65
 
76
66
  set_name(name, source: source)
77
67
  @parent_sampled = parent_sampled
78
- @hub = hub
79
68
  @baggage = baggage
80
- @configuration = hub.configuration # to be removed
81
- @tracing_enabled = hub.configuration.tracing_enabled?
82
- @traces_sampler = hub.configuration.traces_sampler
83
- @traces_sample_rate = hub.configuration.traces_sample_rate
84
- @sdk_logger = hub.configuration.sdk_logger
85
- @release = hub.configuration.release
86
- @environment = hub.configuration.environment
87
- @dsn = hub.configuration.dsn
88
69
  @effective_sample_rate = nil
89
70
  @contexts = {}
90
71
  @measurements = {}
91
72
  @sample_rand = sample_rand
92
73
 
93
- unless @hub.profiler_running?
94
- @profiler = @configuration.profiler_class.new(@configuration)
95
- end
96
-
97
74
  init_span_recorder
75
+ init_profiler
98
76
 
99
77
  unless @sample_rand
100
78
  generator = Utils::SampleRand.new(trace_id: @trace_id)
@@ -102,65 +80,8 @@ module Sentry
102
80
  end
103
81
  end
104
82
 
105
- # @deprecated use Sentry.continue_trace instead.
106
- #
107
- # Initalizes a Transaction instance with a Sentry trace string from another transaction (usually from an external request).
108
- #
109
- # The original transaction will become the parent of the new Transaction instance. And they will share the same `trace_id`.
110
- #
111
- # The child transaction will also store the parent's sampling decision in its `parent_sampled` attribute.
112
- # @param sentry_trace [String] the trace string from the previous transaction.
113
- # @param baggage [String, nil] the incoming baggage header string.
114
- # @param hub [Hub] the hub that'll be responsible for sending this transaction when it's finished.
115
- # @param options [Hash] the options you want to use to initialize a Transaction instance.
116
- # @return [Transaction, nil]
117
- def self.from_sentry_trace(sentry_trace, baggage: nil, hub: Sentry.get_current_hub, **options)
118
- return unless hub.configuration.tracing_enabled?
119
- return unless sentry_trace
120
-
121
- sentry_trace_data = extract_sentry_trace(sentry_trace)
122
- return unless sentry_trace_data
123
-
124
- trace_id, parent_span_id, parent_sampled = sentry_trace_data
125
-
126
- baggage =
127
- if baggage && !baggage.empty?
128
- Baggage.from_incoming_header(baggage)
129
- else
130
- # If there's an incoming sentry-trace but no incoming baggage header,
131
- # for instance in traces coming from older SDKs,
132
- # baggage will be empty and frozen and won't be populated as head SDK.
133
- Baggage.new({})
134
- end
135
-
136
- baggage.freeze!
137
-
138
- sample_rand = extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
139
-
140
- new(
141
- trace_id: trace_id,
142
- parent_span_id: parent_span_id,
143
- parent_sampled: parent_sampled,
144
- hub: hub,
145
- baggage: baggage,
146
- sample_rand: sample_rand,
147
- **options
148
- )
149
- end
150
-
151
- # @deprecated Use Sentry::PropagationContext.extract_sentry_trace instead.
152
- # @return [Array, nil]
153
- def self.extract_sentry_trace(sentry_trace)
154
- PropagationContext.extract_sentry_trace(sentry_trace)
155
- end
156
-
157
- def self.extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
158
- PropagationContext.extract_sample_rand_from_baggage(baggage, trace_id) ||
159
- PropagationContext.generate_sample_rand(baggage, trace_id, parent_sampled)
160
- end
161
-
162
83
  # @return [Hash]
163
- def to_hash
84
+ def to_h
164
85
  hash = super
165
86
 
166
87
  hash.merge!(
@@ -207,7 +128,9 @@ module Sentry
207
128
  # @param sampling_context [Hash] a context Hash that'll be passed to `traces_sampler` (if provided).
208
129
  # @return [void]
209
130
  def set_initial_sample_decision(sampling_context:)
210
- unless @tracing_enabled
131
+ configuration = Sentry.configuration
132
+
133
+ unless configuration && configuration.tracing_enabled?
211
134
  @sampled = false
212
135
  return
213
136
  end
@@ -218,12 +141,12 @@ module Sentry
218
141
  end
219
142
 
220
143
  sample_rate =
221
- if @traces_sampler.is_a?(Proc)
222
- @traces_sampler.call(sampling_context)
144
+ if configuration.traces_sampler.is_a?(Proc)
145
+ configuration.traces_sampler.call(sampling_context)
223
146
  elsif !sampling_context[:parent_sampled].nil?
224
147
  sampling_context[:parent_sampled]
225
148
  else
226
- @traces_sample_rate
149
+ configuration.traces_sample_rate
227
150
  end
228
151
 
229
152
  transaction_description = generate_transaction_description
@@ -265,29 +188,28 @@ module Sentry
265
188
  end
266
189
 
267
190
  # Finishes the transaction's recording and send it to Sentry.
268
- # @param hub [Hub] the hub that'll send this transaction. (Deprecated)
269
191
  # @return [TransactionEvent]
270
- def finish(hub: nil, end_timestamp: nil)
271
- if hub
272
- log_warn(
273
- <<~MSG
274
- Specifying a different hub in `Transaction#finish` will be deprecated in version 5.0.
275
- Please use `Hub#start_transaction` with the designated hub.
276
- MSG
277
- )
278
- end
279
-
280
- hub ||= @hub
281
-
192
+ def finish(end_timestamp: nil)
282
193
  super(end_timestamp: end_timestamp)
283
194
 
284
195
  if @name.nil?
285
196
  @name = UNLABELD_NAME
286
197
  end
287
198
 
288
- @hub.stop_profiler!(self)
199
+ hub = Sentry.get_current_hub
200
+ return unless hub
289
201
 
290
- if @sampled
202
+ hub.stop_profiler!(self)
203
+
204
+ if @sampled && ignore_status_code?
205
+ @sampled = false
206
+
207
+ status_code = get_http_status_code
208
+ log_debug("#{MESSAGE_PREFIX} Discarding #{generate_transaction_description} due to ignored HTTP status code: #{status_code}")
209
+
210
+ hub.current_client.transport.record_lost_event(:event_processor, "transaction")
211
+ hub.current_client.transport.record_lost_event(:event_processor, "span")
212
+ elsif @sampled
291
213
  event = hub.current_client.event_from_transaction(self)
292
214
  hub.capture_event(event)
293
215
  else
@@ -345,6 +267,15 @@ module Sentry
345
267
  @span_recorder.add(self)
346
268
  end
347
269
 
270
+ def init_profiler
271
+ hub = Sentry.get_current_hub
272
+ return unless hub
273
+
274
+ unless hub.profiler_running?
275
+ @profiler = hub.configuration.profiler_class.new(hub.configuration)
276
+ end
277
+ end
278
+
348
279
  private
349
280
 
350
281
  def generate_transaction_description
@@ -355,14 +286,16 @@ module Sentry
355
286
  end
356
287
 
357
288
  def populate_head_baggage
289
+ configuration = Sentry.configuration
290
+
358
291
  items = {
359
292
  "trace_id" => trace_id,
360
293
  "sample_rate" => effective_sample_rate&.to_s,
361
294
  "sample_rand" => Utils::SampleRand.format(@sample_rand),
362
295
  "sampled" => sampled&.to_s,
363
- "environment" => @environment,
364
- "release" => @release,
365
- "public_key" => @dsn&.public_key
296
+ "environment" => configuration&.environment,
297
+ "release" => configuration&.release,
298
+ "public_key" => configuration&.dsn&.public_key
366
299
  }
367
300
 
368
301
  items["transaction"] = name unless source_low_quality?
@@ -371,6 +304,22 @@ module Sentry
371
304
  @baggage = Baggage.new(items, mutable: false)
372
305
  end
373
306
 
307
+ def ignore_status_code?
308
+ trace_ignore_status_codes = Sentry.configuration&.trace_ignore_status_codes
309
+ return false unless trace_ignore_status_codes
310
+
311
+ status_code = get_http_status_code
312
+ return false unless status_code
313
+
314
+ trace_ignore_status_codes.any? do |ignored|
315
+ ignored.is_a?(Range) ? ignored.include?(status_code) : status_code == ignored
316
+ end
317
+ end
318
+
319
+ def get_http_status_code
320
+ @data && @data[Span::DataConventions::HTTP_STATUS_CODE]
321
+ end
322
+
374
323
  class SpanRecorder
375
324
  attr_reader :max_length, :spans
376
325
 
@@ -17,9 +17,6 @@ module Sentry
17
17
  # @return [Hash, nil]
18
18
  attr_accessor :profile
19
19
 
20
- # @return [Hash, nil]
21
- attr_accessor :metrics_summary
22
-
23
20
  def initialize(transaction:, **options)
24
21
  super(**options)
25
22
 
@@ -32,10 +29,9 @@ module Sentry
32
29
  self.tags = transaction.tags
33
30
  self.dynamic_sampling_context = transaction.get_baggage.dynamic_sampling_context
34
31
  self.measurements = transaction.measurements
35
- self.metrics_summary = transaction.metrics_summary
36
32
 
37
33
  finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
38
- self.spans = finished_spans.map(&:to_hash)
34
+ self.spans = finished_spans.map(&:to_h)
39
35
 
40
36
  populate_profile(transaction)
41
37
  end
@@ -48,12 +44,11 @@ module Sentry
48
44
  end
49
45
 
50
46
  # @return [Hash]
51
- def to_hash
47
+ def to_h
52
48
  data = super
53
- data[:spans] = @spans.map(&:to_hash) if @spans
49
+ data[:spans] = @spans.map(&:to_h) if @spans
54
50
  data[:start_timestamp] = @start_timestamp
55
51
  data[:measurements] = @measurements
56
- data[:_metrics_summary] = @metrics_summary if @metrics_summary
57
52
  data
58
53
  end
59
54
 
@@ -62,7 +57,7 @@ module Sentry
62
57
  EMPTY_PROFILE = {}.freeze
63
58
 
64
59
  def populate_profile(transaction)
65
- profile_hash = transaction.profiler&.to_hash || EMPTY_PROFILE
60
+ profile_hash = transaction.profiler&.to_h || EMPTY_PROFILE
66
61
 
67
62
  return if profile_hash.empty?
68
63
 
@@ -113,7 +113,7 @@ module Sentry
113
113
 
114
114
  def envelope_from_event(event)
115
115
  # Convert to hash
116
- event_payload = event.to_hash
116
+ event_payload = event.to_h
117
117
  event_id = event_payload[:event_id] || event_payload["event_id"]
118
118
  item_type = event_payload[:type] || event_payload["type"]
119
119
 
@@ -124,10 +124,7 @@ module Sentry
124
124
  sent_at: Sentry.utc_now.iso8601
125
125
  }
126
126
 
127
- if event.is_a?(Event) && event.dynamic_sampling_context
128
- envelope_headers[:trace] = event.dynamic_sampling_context
129
- end
130
-
127
+ envelope_headers[:trace] = event.dynamic_sampling_context if event.dynamic_sampling_context
131
128
  envelope = Envelope.new(envelope_headers)
132
129
 
133
130
  if event.is_a?(LogEvent)
@@ -3,6 +3,8 @@
3
3
  module Sentry
4
4
  module Utils
5
5
  module EncodingHelper
6
+ MALFORMED_STRING = "<malformed-string>"
7
+
6
8
  def self.encode_to_utf_8(value)
7
9
  if value.encoding != Encoding::UTF_8 && value.respond_to?(:force_encoding)
8
10
  value = value.dup.force_encoding(Encoding::UTF_8)
@@ -17,6 +19,10 @@ module Sentry
17
19
 
18
20
  value.dup.force_encoding(Encoding::UTF_8).valid_encoding?
19
21
  end
22
+
23
+ def self.safe_utf_8_string(value)
24
+ valid_utf_8?(value) ? value : MALFORMED_STRING
25
+ end
20
26
  end
21
27
  end
22
28
  end
@@ -3,27 +3,43 @@
3
3
  module Sentry
4
4
  # @private
5
5
  module LoggingHelper
6
- # @!visibility private
7
- attr_reader :sdk_logger
8
-
9
6
  # @!visibility private
10
7
  def log_error(message, exception, debug: false)
11
8
  message = "#{message}: #{exception.message}"
12
- message += "\n#{exception.backtrace.join("\n")}" if debug
9
+ message += "\n#{exception.backtrace.join("\n")}" if debug && exception.backtrace
13
10
 
14
- sdk_logger.error(LOGGER_PROGNAME) do
15
- message
16
- end
11
+ sdk_logger&.error(LOGGER_PROGNAME) { message }
12
+ rescue StandardError => e
13
+ log_to_stderr(e, message)
17
14
  end
18
15
 
19
16
  # @!visibility private
20
17
  def log_debug(message)
21
- sdk_logger.debug(LOGGER_PROGNAME) { message }
18
+ sdk_logger&.debug(LOGGER_PROGNAME) { message }
19
+ rescue StandardError => e
20
+ log_to_stderr(e, message)
22
21
  end
23
22
 
24
23
  # @!visibility private
25
24
  def log_warn(message)
26
- sdk_logger.warn(LOGGER_PROGNAME) { message }
25
+ sdk_logger&.warn(LOGGER_PROGNAME) { message }
26
+ rescue StandardError => e
27
+ log_to_stderr(e, message)
28
+ end
29
+
30
+ # @!visibility private
31
+ def sdk_logger
32
+ @sdk_logger ||= Sentry.sdk_logger
33
+ end
34
+
35
+ # @!visibility private
36
+ def log_to_stderr(error, message)
37
+ error_msg = "Sentry SDK logging failed (#{error.class}: #{error.message}): #{message}".scrub(%q(<?>))
38
+ error_msg += "\n#{error.backtrace.map { |line| line.scrub(%q(<?>)) }.join("\n")}" if error.backtrace
39
+
40
+ $stderr.puts(error_msg)
41
+ rescue StandardError
42
+ # swallow everything – logging must never crash the app
27
43
  end
28
44
  end
29
45
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Sentry
6
+ module Utils
7
+ module TelemetryAttributes
8
+ private
9
+
10
+ def attribute_hash(value)
11
+ case value
12
+ when String
13
+ { value: value, type: "string" }
14
+ when TrueClass, FalseClass
15
+ { value: value, type: "boolean" }
16
+ when Integer
17
+ { value: value, type: "integer" }
18
+ when Float
19
+ { value: value, type: "double" }
20
+ else
21
+ begin
22
+ { value: JSON.generate(value), type: "string" }
23
+ rescue
24
+ { value: value, type: "string" }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -20,6 +20,7 @@ module Sentry
20
20
 
21
21
  @profiling_enabled = defined?(Vernier) && configuration.profiling_enabled?
22
22
  @profiles_sample_rate = configuration.profiles_sample_rate
23
+ @profiles_sample_interval = configuration.profiles_sample_interval
23
24
  @project_root = configuration.project_root
24
25
  @app_dirs_pattern = configuration.app_dirs_pattern
25
26
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
@@ -56,7 +57,7 @@ module Sentry
56
57
  return unless @sampled
57
58
  return if @started
58
59
 
59
- @started = ::Vernier.start_profile
60
+ @started = ::Vernier.start_profile(interval: @profiles_sample_interval)
60
61
 
61
62
  log("Started")
62
63
 
@@ -90,9 +91,9 @@ module Sentry
90
91
  Thread.current.object_id
91
92
  end
92
93
 
93
- def to_hash
94
+ def to_h
94
95
  unless @sampled
95
- record_lost_event(:sample_rate)
96
+ record_lost_event(:sample_rate) if @profiling_enabled
96
97
  return EMPTY_RESULT
97
98
  end
98
99
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.28.1"
4
+ VERSION = "6.3.1"
5
5
  end