sentry-ruby 5.28.1 → 6.5.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.
- checksums.yaml +4 -4
- data/Gemfile +26 -2
- data/README.md +3 -3
- data/lib/sentry/background_worker.rb +1 -4
- data/lib/sentry/backtrace/line.rb +99 -0
- data/lib/sentry/backtrace.rb +44 -76
- data/lib/sentry/baggage.rb +2 -2
- data/lib/sentry/breadcrumb.rb +1 -1
- data/lib/sentry/breadcrumb_buffer.rb +2 -2
- data/lib/sentry/check_in_event.rb +2 -2
- data/lib/sentry/client.rb +57 -135
- data/lib/sentry/configuration.rb +155 -75
- data/lib/sentry/cron/monitor_check_ins.rb +3 -3
- data/lib/sentry/cron/monitor_config.rb +2 -2
- data/lib/sentry/cron/monitor_schedule.rb +2 -2
- data/lib/sentry/dsn.rb +33 -1
- data/lib/sentry/envelope/item.rb +3 -3
- data/lib/sentry/error_event.rb +3 -3
- data/lib/sentry/event.rb +4 -10
- data/lib/sentry/exceptions.rb +3 -0
- data/lib/sentry/hub.rb +26 -4
- data/lib/sentry/interface.rb +1 -1
- data/lib/sentry/interfaces/exception.rb +2 -2
- data/lib/sentry/interfaces/request.rb +2 -0
- data/lib/sentry/interfaces/single_exception.rb +4 -4
- data/lib/sentry/interfaces/stacktrace.rb +3 -3
- data/lib/sentry/interfaces/stacktrace_builder.rb +0 -8
- data/lib/sentry/interfaces/threads.rb +2 -2
- data/lib/sentry/log_event.rb +24 -142
- data/lib/sentry/log_event_buffer.rb +13 -60
- data/lib/sentry/metric_event.rb +49 -0
- data/lib/sentry/metric_event_buffer.rb +28 -0
- data/lib/sentry/metrics.rb +47 -54
- data/lib/sentry/profiler.rb +4 -5
- data/lib/sentry/propagation_context.rb +48 -8
- data/lib/sentry/rack/capture_exceptions.rb +90 -2
- data/lib/sentry/release_detector.rb +1 -1
- data/lib/sentry/rspec.rb +1 -1
- data/lib/sentry/scope.rb +51 -18
- data/lib/sentry/sequel.rb +35 -0
- data/lib/sentry/span.rb +5 -17
- data/lib/sentry/std_lib_logger.rb +4 -0
- data/lib/sentry/telemetry_event_buffer.rb +130 -0
- data/lib/sentry/test_helper.rb +8 -0
- data/lib/sentry/transaction.rb +53 -103
- data/lib/sentry/transaction_event.rb +4 -9
- data/lib/sentry/transport/http_transport.rb +7 -11
- data/lib/sentry/transport.rb +9 -7
- data/lib/sentry/utils/encoding_helper.rb +6 -0
- data/lib/sentry/utils/logging_helper.rb +25 -9
- data/lib/sentry/utils/telemetry_attributes.rb +30 -0
- data/lib/sentry/vernier/profiler.rb +4 -3
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +53 -30
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +2 -1
- metadata +27 -16
- data/lib/sentry/metrics/aggregator.rb +0 -248
- data/lib/sentry/metrics/configuration.rb +0 -57
- data/lib/sentry/metrics/counter_metric.rb +0 -25
- data/lib/sentry/metrics/distribution_metric.rb +0 -25
- data/lib/sentry/metrics/gauge_metric.rb +0 -35
- data/lib/sentry/metrics/local_aggregator.rb +0 -53
- data/lib/sentry/metrics/metric.rb +0 -19
- data/lib/sentry/metrics/set_metric.rb +0 -28
- data/lib/sentry/metrics/timing.rb +0 -51
data/lib/sentry/transaction.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
222
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
199
|
+
hub = Sentry.get_current_hub
|
|
200
|
+
return unless hub
|
|
289
201
|
|
|
290
|
-
|
|
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,17 @@ 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" =>
|
|
364
|
-
"release" =>
|
|
365
|
-
"public_key" =>
|
|
296
|
+
"environment" => configuration&.environment,
|
|
297
|
+
"release" => configuration&.release,
|
|
298
|
+
"public_key" => configuration&.dsn&.public_key,
|
|
299
|
+
"org_id" => configuration&.effective_org_id
|
|
366
300
|
}
|
|
367
301
|
|
|
368
302
|
items["transaction"] = name unless source_low_quality?
|
|
@@ -371,6 +305,22 @@ module Sentry
|
|
|
371
305
|
@baggage = Baggage.new(items, mutable: false)
|
|
372
306
|
end
|
|
373
307
|
|
|
308
|
+
def ignore_status_code?
|
|
309
|
+
trace_ignore_status_codes = Sentry.configuration&.trace_ignore_status_codes
|
|
310
|
+
return false unless trace_ignore_status_codes
|
|
311
|
+
|
|
312
|
+
status_code = get_http_status_code
|
|
313
|
+
return false unless status_code
|
|
314
|
+
|
|
315
|
+
trace_ignore_status_codes.any? do |ignored|
|
|
316
|
+
ignored.is_a?(Range) ? ignored.include?(status_code) : status_code == ignored
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def get_http_status_code
|
|
321
|
+
@data && @data[Span::DataConventions::HTTP_STATUS_CODE]
|
|
322
|
+
end
|
|
323
|
+
|
|
374
324
|
class SpanRecorder
|
|
375
325
|
attr_reader :max_length, :spans
|
|
376
326
|
|
|
@@ -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(&:
|
|
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
|
|
47
|
+
def to_h
|
|
52
48
|
data = super
|
|
53
|
-
data[:spans] = @spans.map(&:
|
|
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&.
|
|
60
|
+
profile_hash = transaction.profiler&.to_h || EMPTY_PROFILE
|
|
66
61
|
|
|
67
62
|
return if profile_hash.empty?
|
|
68
63
|
|
|
@@ -49,6 +49,12 @@ module Sentry
|
|
|
49
49
|
|
|
50
50
|
if response.code.match?(/\A2\d{2}/)
|
|
51
51
|
handle_rate_limited_response(response) if has_rate_limited_header?(response)
|
|
52
|
+
elsif response.code == "413"
|
|
53
|
+
error_message = "HTTP 413: Envelope dropped due to exceeded size limit"
|
|
54
|
+
error_message += " (body: #{response.body})" if response.body && !response.body.empty?
|
|
55
|
+
log_warn(error_message)
|
|
56
|
+
|
|
57
|
+
raise Sentry::SizeExceededError, error_message
|
|
52
58
|
elsif response.code == "429"
|
|
53
59
|
log_debug("the server responded with status 429")
|
|
54
60
|
handle_rate_limited_response(response)
|
|
@@ -69,17 +75,7 @@ module Sentry
|
|
|
69
75
|
end
|
|
70
76
|
|
|
71
77
|
def generate_auth_header
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
now = Sentry.utc_now.to_i
|
|
75
|
-
fields = {
|
|
76
|
-
"sentry_version" => PROTOCOL_VERSION,
|
|
77
|
-
"sentry_client" => USER_AGENT,
|
|
78
|
-
"sentry_timestamp" => now,
|
|
79
|
-
"sentry_key" => @dsn.public_key
|
|
80
|
-
}
|
|
81
|
-
fields["sentry_secret"] = @dsn.secret_key if @dsn.secret_key
|
|
82
|
-
"Sentry " + fields.map { |key, value| "#{key}=#{value}" }.join(", ")
|
|
78
|
+
@dsn&.generate_auth_header(client: USER_AGENT)
|
|
83
79
|
end
|
|
84
80
|
|
|
85
81
|
def conn
|
data/lib/sentry/transport.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "sentry/envelope"
|
|
|
5
5
|
|
|
6
6
|
module Sentry
|
|
7
7
|
class Transport
|
|
8
|
-
PROTOCOL_VERSION =
|
|
8
|
+
PROTOCOL_VERSION = DSN::PROTOCOL_VERSION
|
|
9
9
|
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
|
|
10
10
|
CLIENT_REPORT_INTERVAL = 30
|
|
11
11
|
|
|
@@ -19,7 +19,8 @@ module Sentry
|
|
|
19
19
|
:before_send,
|
|
20
20
|
:event_processor,
|
|
21
21
|
:insufficient_data,
|
|
22
|
-
:backpressure
|
|
22
|
+
:backpressure,
|
|
23
|
+
:send_error
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
include LoggingHelper
|
|
@@ -61,6 +62,10 @@ module Sentry
|
|
|
61
62
|
log_debug("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
|
|
62
63
|
send_data(data)
|
|
63
64
|
end
|
|
65
|
+
rescue Sentry::SizeExceededError
|
|
66
|
+
serialized_items&.each do |item|
|
|
67
|
+
record_lost_event(:send_error, item.data_category)
|
|
68
|
+
end
|
|
64
69
|
end
|
|
65
70
|
|
|
66
71
|
def serialize_envelope(envelope)
|
|
@@ -113,7 +118,7 @@ module Sentry
|
|
|
113
118
|
|
|
114
119
|
def envelope_from_event(event)
|
|
115
120
|
# Convert to hash
|
|
116
|
-
event_payload = event.
|
|
121
|
+
event_payload = event.to_h
|
|
117
122
|
event_id = event_payload[:event_id] || event_payload["event_id"]
|
|
118
123
|
item_type = event_payload[:type] || event_payload["type"]
|
|
119
124
|
|
|
@@ -124,10 +129,7 @@ module Sentry
|
|
|
124
129
|
sent_at: Sentry.utc_now.iso8601
|
|
125
130
|
}
|
|
126
131
|
|
|
127
|
-
|
|
128
|
-
envelope_headers[:trace] = event.dynamic_sampling_context
|
|
129
|
-
end
|
|
130
|
-
|
|
132
|
+
envelope_headers[:trace] = event.dynamic_sampling_context if event.dynamic_sampling_context
|
|
131
133
|
envelope = Envelope.new(envelope_headers)
|
|
132
134
|
|
|
133
135
|
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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
data/lib/sentry/version.rb
CHANGED
data/lib/sentry-ruby.rb
CHANGED
|
@@ -26,8 +26,8 @@ require "sentry/threaded_periodic_worker"
|
|
|
26
26
|
require "sentry/session_flusher"
|
|
27
27
|
require "sentry/backpressure_monitor"
|
|
28
28
|
require "sentry/cron/monitor_check_ins"
|
|
29
|
-
require "sentry/metrics"
|
|
30
29
|
require "sentry/vernier/profiler"
|
|
30
|
+
require "sentry/metrics"
|
|
31
31
|
|
|
32
32
|
[
|
|
33
33
|
"sentry/rake",
|
|
@@ -59,7 +59,6 @@ module Sentry
|
|
|
59
59
|
logger
|
|
60
60
|
session_flusher
|
|
61
61
|
backpressure_monitor
|
|
62
|
-
metrics_aggregator
|
|
63
62
|
exception_locals_tp
|
|
64
63
|
].freeze
|
|
65
64
|
|
|
@@ -93,10 +92,6 @@ module Sentry
|
|
|
93
92
|
# @return [BackpressureMonitor, nil]
|
|
94
93
|
attr_reader :backpressure_monitor
|
|
95
94
|
|
|
96
|
-
# @!attribute [r] metrics_aggregator
|
|
97
|
-
# @return [Metrics::Aggregator, nil]
|
|
98
|
-
attr_reader :metrics_aggregator
|
|
99
|
-
|
|
100
95
|
##### Patch Registration #####
|
|
101
96
|
|
|
102
97
|
# @!visibility private
|
|
@@ -252,7 +247,6 @@ module Sentry
|
|
|
252
247
|
@background_worker = Sentry::BackgroundWorker.new(config)
|
|
253
248
|
@session_flusher = config.session_tracking? ? Sentry::SessionFlusher.new(config, client) : nil
|
|
254
249
|
@backpressure_monitor = config.enable_backpressure_handling ? Sentry::BackpressureMonitor.new(config, client) : nil
|
|
255
|
-
@metrics_aggregator = config.metrics.enabled ? Sentry::Metrics::Aggregator.new(config, client) : nil
|
|
256
250
|
exception_locals_tp.enable if config.include_local_variables
|
|
257
251
|
at_exit { close }
|
|
258
252
|
end
|
|
@@ -273,12 +267,6 @@ module Sentry
|
|
|
273
267
|
@backpressure_monitor = nil
|
|
274
268
|
end
|
|
275
269
|
|
|
276
|
-
if @metrics_aggregator
|
|
277
|
-
@metrics_aggregator.flush(force: true)
|
|
278
|
-
@metrics_aggregator.kill
|
|
279
|
-
@metrics_aggregator = nil
|
|
280
|
-
end
|
|
281
|
-
|
|
282
270
|
if client = get_current_client
|
|
283
271
|
client.flush
|
|
284
272
|
|
|
@@ -635,24 +623,27 @@ module Sentry
|
|
|
635
623
|
#
|
|
636
624
|
# @see https://develop.sentry.dev/sdk/telemetry/logs/ Sentry SDK Telemetry Logs Protocol
|
|
637
625
|
#
|
|
638
|
-
# @return [StructuredLogger
|
|
626
|
+
# @return [StructuredLogger] The structured logger instance or nil if logs are disabled
|
|
639
627
|
def logger
|
|
640
|
-
@logger ||=
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
628
|
+
@logger ||= configuration.structured_logging.logger_class.new(configuration)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
# Returns the metrics API for capturing custom metrics.
|
|
632
|
+
#
|
|
633
|
+
# @example Enable metrics
|
|
634
|
+
# Sentry.init do |config|
|
|
635
|
+
# config.dsn = "YOUR_DSN"
|
|
636
|
+
# config.enable_metrics = true
|
|
637
|
+
# end
|
|
638
|
+
#
|
|
639
|
+
# @example Usage
|
|
640
|
+
# Sentry.metrics.count("button.click", 1, attributes: { button_id: "submit" })
|
|
641
|
+
# Sentry.metrics.distribution("response.time", 120.5, unit: "millisecond")
|
|
642
|
+
# Sentry.metrics.gauge("cpu.usage", 75.2, unit: "percent")
|
|
643
|
+
#
|
|
644
|
+
# @return [Metrics] The metrics API
|
|
645
|
+
def metrics
|
|
646
|
+
Metrics
|
|
656
647
|
end
|
|
657
648
|
|
|
658
649
|
##### Helpers #####
|
|
@@ -675,6 +666,38 @@ module Sentry
|
|
|
675
666
|
META
|
|
676
667
|
end
|
|
677
668
|
|
|
669
|
+
# Registers a callback function that retrieves the current external propagation context.
|
|
670
|
+
# This is used by OpenTelemetry integration to provide trace_id and span_id from OTel context.
|
|
671
|
+
#
|
|
672
|
+
# @param callback [Proc, nil] A callable that returns [trace_id, span_id] or nil
|
|
673
|
+
# @return [void]
|
|
674
|
+
#
|
|
675
|
+
# @example
|
|
676
|
+
# Sentry.register_external_propagation_context do
|
|
677
|
+
# span_context = OpenTelemetry::Trace.current_span.context
|
|
678
|
+
# return nil unless span_context.valid?
|
|
679
|
+
# [span_context.hex_trace_id, span_context.hex_span_id]
|
|
680
|
+
# end
|
|
681
|
+
def register_external_propagation_context(&callback)
|
|
682
|
+
@external_propagation_context_callback = callback
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
# Returns the external propagation context (trace_id, span_id) if a callback is registered.
|
|
686
|
+
#
|
|
687
|
+
# @return [Array<String>, nil] A tuple of [trace_id, span_id] or nil if no context is available
|
|
688
|
+
def get_external_propagation_context
|
|
689
|
+
return nil unless @external_propagation_context_callback
|
|
690
|
+
|
|
691
|
+
@external_propagation_context_callback.call
|
|
692
|
+
rescue => e
|
|
693
|
+
sdk_logger&.debug(LOGGER_PROGNAME) { "Error getting external propagation context: #{e.message}" } if initialized?
|
|
694
|
+
nil
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def clear_external_propagation_context
|
|
698
|
+
@external_propagation_context_callback = nil
|
|
699
|
+
end
|
|
700
|
+
|
|
678
701
|
# @!visibility private
|
|
679
702
|
def utc_now
|
|
680
703
|
Time.now.utc
|
data/sentry-ruby-core.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.homepage = "https://github.com/getsentry/sentry-ruby"
|
|
13
13
|
|
|
14
14
|
spec.platform = Gem::Platform::RUBY
|
|
15
|
-
spec.required_ruby_version = '>= 2.
|
|
15
|
+
spec.required_ruby_version = '>= 2.7'
|
|
16
16
|
spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
|
|
17
17
|
spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples|\.rubocop\.yml)'`.split("\n")
|
|
18
18
|
|
data/sentry-ruby.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.license = 'MIT'
|
|
12
12
|
|
|
13
13
|
spec.platform = Gem::Platform::RUBY
|
|
14
|
-
spec.required_ruby_version = '>= 2.
|
|
14
|
+
spec.required_ruby_version = '>= 2.7'
|
|
15
15
|
spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
|
|
16
16
|
spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples|\.rubocop\.yml)'`.split("\n")
|
|
17
17
|
|
|
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
|
|
31
31
|
spec.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
|
|
32
32
|
spec.add_dependency "bigdecimal"
|
|
33
|
+
spec.add_dependency "logger"
|
|
33
34
|
end
|