sentry-ruby 5.16.1 → 5.20.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/README.md +20 -10
- data/Rakefile +1 -1
- data/bin/console +1 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +1 -1
- data/lib/sentry/backpressure_monitor.rb +2 -32
- data/lib/sentry/backtrace.rb +8 -6
- data/lib/sentry/baggage.rb +6 -6
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/check_in_event.rb +5 -5
- data/lib/sentry/client.rb +61 -11
- data/lib/sentry/configuration.rb +53 -25
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/monitor_check_ins.rb +1 -1
- data/lib/sentry/cron/monitor_config.rb +1 -1
- data/lib/sentry/cron/monitor_schedule.rb +1 -1
- data/lib/sentry/dsn.rb +4 -4
- data/lib/sentry/envelope.rb +19 -2
- data/lib/sentry/error_event.rb +2 -2
- data/lib/sentry/event.rb +20 -18
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +9 -0
- data/lib/sentry/hub.rb +23 -3
- data/lib/sentry/integrable.rb +4 -0
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +5 -3
- data/lib/sentry/interfaces/mechanism.rb +20 -0
- data/lib/sentry/interfaces/request.rb +7 -7
- data/lib/sentry/interfaces/single_exception.rb +7 -5
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +248 -0
- data/lib/sentry/metrics/configuration.rb +47 -0
- data/lib/sentry/metrics/counter_metric.rb +25 -0
- data/lib/sentry/metrics/distribution_metric.rb +25 -0
- data/lib/sentry/metrics/gauge_metric.rb +35 -0
- data/lib/sentry/metrics/local_aggregator.rb +53 -0
- data/lib/sentry/metrics/metric.rb +19 -0
- data/lib/sentry/metrics/set_metric.rb +28 -0
- data/lib/sentry/metrics/timing.rb +43 -0
- data/lib/sentry/metrics.rb +56 -0
- data/lib/sentry/net/http.rb +18 -39
- data/lib/sentry/profiler.rb +19 -20
- data/lib/sentry/propagation_context.rb +10 -9
- data/lib/sentry/puma.rb +1 -1
- data/lib/sentry/rack/capture_exceptions.rb +15 -3
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +4 -2
- data/lib/sentry/redis.rb +2 -1
- data/lib/sentry/release_detector.rb +4 -4
- data/lib/sentry/scope.rb +36 -26
- data/lib/sentry/session.rb +2 -2
- data/lib/sentry/session_flusher.rb +7 -39
- data/lib/sentry/span.rb +46 -5
- data/lib/sentry/test_helper.rb +3 -2
- data/lib/sentry/threaded_periodic_worker.rb +39 -0
- data/lib/sentry/transaction.rb +17 -15
- data/lib/sentry/transaction_event.rb +6 -1
- data/lib/sentry/transport/configuration.rb +0 -1
- data/lib/sentry/transport/http_transport.rb +12 -12
- data/lib/sentry/transport.rb +18 -26
- data/lib/sentry/utils/argument_checking_helper.rb +6 -0
- data/lib/sentry/utils/env_helper.rb +21 -0
- data/lib/sentry/utils/http_tracing.rb +41 -0
- data/lib/sentry/utils/logging_helper.rb +0 -4
- data/lib/sentry/utils/real_ip.rb +2 -2
- data/lib/sentry/utils/request_id.rb +1 -1
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +34 -3
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +13 -6
- 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
|
-
|
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
|
-
|
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:
|
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
|
data/lib/sentry/test_helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Sentry
|
2
2
|
module TestHelper
|
3
|
-
DUMMY_DSN =
|
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
|
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
|
data/lib/sentry/transaction.rb
CHANGED
@@ -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
|
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 =
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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,
|
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:
|
78
|
+
active_thead_id: "0"
|
74
79
|
}
|
75
80
|
)
|
76
81
|
|
@@ -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 =
|
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
|
-
|
42
|
-
|
43
|
-
|
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[
|
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[
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
"sentry_version" => PROTOCOL_VERSION,
|
82
|
+
"sentry_client" => USER_AGENT,
|
83
|
+
"sentry_timestamp" => now,
|
84
|
+
"sentry_key" => @dsn.public_key
|
85
85
|
}
|
86
|
-
fields[
|
87
|
-
|
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
|
data/lib/sentry/transport.rb
CHANGED
@@ -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 =
|
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
|
-
|
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?(
|
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:
|
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:
|
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,
|
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,
|
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,
|
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:
|
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.
|
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.
|
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
|
data/lib/sentry/utils/real_ip.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
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"
|
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
|
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)
|
data/lib/sentry/version.rb
CHANGED