sentry-ruby-core 6.6.2 → 7.0.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/bin/test +15 -0
- data/gemfiles/ruby-2.7_rack-2_redis-4.gemfile.lock +159 -0
- data/gemfiles/ruby-2.7_rack-3.1_redis-4.gemfile.lock +159 -0
- data/gemfiles/ruby-2.7_rack-3_redis-4.gemfile.lock +159 -0
- data/gemfiles/ruby-3.0_rack-2_redis-4.gemfile.lock +173 -0
- data/gemfiles/ruby-3.0_rack-3.1_redis-4.gemfile.lock +173 -0
- data/gemfiles/ruby-3.0_rack-3_redis-4.gemfile.lock +173 -0
- data/gemfiles/ruby-3.1_rack-2_redis-4.gemfile.lock +241 -0
- data/gemfiles/ruby-3.1_rack-3.1_redis-4.gemfile.lock +241 -0
- data/gemfiles/ruby-3.1_rack-3_redis-4.gemfile.lock +241 -0
- data/gemfiles/ruby-3.2_rack-0_redis-5.gemfile.lock +263 -0
- data/gemfiles/ruby-3.2_rack-2_redis-4.gemfile.lock +260 -0
- data/gemfiles/ruby-3.2_rack-2_redis-5.gemfile.lock +266 -0
- data/gemfiles/ruby-3.2_rack-3.1_redis-4.gemfile.lock +260 -0
- data/gemfiles/ruby-3.2_rack-3_redis-4.gemfile.lock +260 -0
- data/gemfiles/ruby-3.2_rack-3_redis-5.gemfile.lock +266 -0
- data/gemfiles/ruby-3.3_rack-2_redis-4.gemfile.lock +263 -0
- data/gemfiles/ruby-3.3_rack-3.1_redis-4.gemfile.lock +263 -0
- data/gemfiles/ruby-3.3_rack-3.1_redis-5.3.gemfile.lock +269 -0
- data/gemfiles/ruby-3.3_rack-3_redis-4.gemfile.lock +263 -0
- data/gemfiles/ruby-3.4_rack-2_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-3.4_rack-3.1_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-3.4_rack-3.1_redis-5.3.gemfile.lock +260 -0
- data/gemfiles/ruby-3.4_rack-3_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-4.0_rack-2_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-4.0_rack-3.1_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-4.0_rack-3_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rack-2_redis-4.gemfile.lock +266 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rack-3.1_redis-4.gemfile.lock +266 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rack-3_redis-4.gemfile.lock +266 -0
- data/lib/sentry/client.rb +9 -8
- data/lib/sentry/configuration.rb +74 -12
- data/lib/sentry/data_collection/key_value_collection.rb +136 -0
- data/lib/sentry/data_collection.rb +203 -0
- data/lib/sentry/debug_structured_logger.rb +2 -15
- data/lib/sentry/envelope/item.rb +21 -0
- data/lib/sentry/error_event.rb +6 -1
- data/lib/sentry/event.rb +8 -4
- data/lib/sentry/excon/middleware.rb +3 -8
- data/lib/sentry/faraday.rb +3 -4
- data/lib/sentry/hub.rb +2 -2
- data/lib/sentry/interfaces/exception.rb +8 -2
- data/lib/sentry/interfaces/request.rb +42 -46
- data/lib/sentry/interfaces/single_exception.rb +2 -2
- data/lib/sentry/net/http.rb +3 -4
- data/lib/sentry/redis.rb +1 -1
- data/lib/sentry/scope.rb +42 -3
- data/lib/sentry/span.rb +1 -0
- data/lib/sentry/std_lib_logger.rb +2 -6
- data/lib/sentry/telemetry_event_buffer.rb +9 -2
- data/lib/sentry/test_helper.rb +8 -7
- data/lib/sentry/threaded_periodic_worker.rb +8 -3
- data/lib/sentry/transport.rb +14 -3
- data/lib/sentry/utils/http_tracing.rb +42 -26
- data/lib/sentry/utils/telemetry_attributes.rb +26 -14
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +94 -23
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +1 -1
- metadata +35 -3
data/lib/sentry/hub.rb
CHANGED
|
@@ -224,7 +224,7 @@ module Sentry
|
|
|
224
224
|
end
|
|
225
225
|
|
|
226
226
|
def capture_log_event(message, **options)
|
|
227
|
-
return unless current_client
|
|
227
|
+
return unless current_client
|
|
228
228
|
|
|
229
229
|
event = current_client.event_from_log(message, **options)
|
|
230
230
|
|
|
@@ -242,7 +242,7 @@ module Sentry
|
|
|
242
242
|
# @param attributes [Hash, nil] (optional) additional attributes for the metric
|
|
243
243
|
# @return [void]
|
|
244
244
|
def capture_metric(name:, type:, value:, unit: nil, attributes: nil)
|
|
245
|
-
return unless current_client
|
|
245
|
+
return unless current_client
|
|
246
246
|
|
|
247
247
|
metric = MetricEvent.new(
|
|
248
248
|
name: name,
|
|
@@ -25,15 +25,21 @@ module Sentry
|
|
|
25
25
|
# @see SingleExceptionInterface#build_with_stacktrace
|
|
26
26
|
# @see SingleExceptionInterface#initialize
|
|
27
27
|
# @param mechanism [Mechanism]
|
|
28
|
+
# @param data_collection [DataCollection]
|
|
28
29
|
# @return [ExceptionInterface]
|
|
29
|
-
def self.build(exception:, stacktrace_builder:, mechanism:)
|
|
30
|
+
def self.build(exception:, stacktrace_builder:, mechanism:, data_collection:)
|
|
30
31
|
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
|
|
31
32
|
processed_backtrace_ids = Set.new
|
|
32
33
|
|
|
33
34
|
exceptions = exceptions.map do |e|
|
|
34
35
|
if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
|
|
35
36
|
processed_backtrace_ids << e.backtrace.object_id
|
|
36
|
-
SingleExceptionInterface.build_with_stacktrace(
|
|
37
|
+
SingleExceptionInterface.build_with_stacktrace(
|
|
38
|
+
exception: e,
|
|
39
|
+
stacktrace_builder: stacktrace_builder,
|
|
40
|
+
mechanism: mechanism,
|
|
41
|
+
data_collection: data_collection
|
|
42
|
+
)
|
|
37
43
|
else
|
|
38
44
|
SingleExceptionInterface.new(exception: exception, mechanism: mechanism)
|
|
39
45
|
end
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
module Sentry
|
|
4
6
|
class RequestInterface < Interface
|
|
5
7
|
REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
|
|
6
8
|
CONTENT_HEADERS = %w[CONTENT_TYPE CONTENT_LENGTH].freeze
|
|
7
|
-
IP_HEADERS = [
|
|
8
|
-
"REMOTE_ADDR",
|
|
9
|
-
"HTTP_CLIENT_IP",
|
|
10
|
-
"HTTP_X_REAL_IP",
|
|
11
|
-
"HTTP_X_FORWARDED_FOR"
|
|
12
|
-
].freeze
|
|
13
9
|
|
|
14
10
|
# Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
|
|
15
11
|
LOWERCASE_PATTERN = /[a-z]/.freeze
|
|
@@ -27,10 +23,10 @@ module Sentry
|
|
|
27
23
|
# @return [Hash]
|
|
28
24
|
attr_accessor :data
|
|
29
25
|
|
|
30
|
-
# @return [String]
|
|
26
|
+
# @return [String, Hash]
|
|
31
27
|
attr_accessor :query_string
|
|
32
28
|
|
|
33
|
-
# @return [
|
|
29
|
+
# @return [Hash]
|
|
34
30
|
attr_accessor :cookies
|
|
35
31
|
|
|
36
32
|
# @return [Hash]
|
|
@@ -40,33 +36,22 @@ module Sentry
|
|
|
40
36
|
attr_accessor :env
|
|
41
37
|
|
|
42
38
|
# @param env [Hash]
|
|
43
|
-
# @param
|
|
39
|
+
# @param data_collection [DataCollection]
|
|
44
40
|
# @param rack_env_whitelist [Array]
|
|
45
|
-
# @see Configuration#
|
|
41
|
+
# @see Configuration#data_collection
|
|
46
42
|
# @see Configuration#rack_env_whitelist
|
|
47
|
-
def initialize(env:,
|
|
43
|
+
def initialize(env:, data_collection:, rack_env_whitelist:)
|
|
48
44
|
env = env.dup
|
|
49
|
-
|
|
50
|
-
unless send_default_pii
|
|
51
|
-
# need to completely wipe out ip addresses
|
|
52
|
-
RequestInterface::IP_HEADERS.each do |header|
|
|
53
|
-
env.delete(header)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
45
|
request = ::Rack::Request.new(env)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
67
|
-
|
|
68
|
-
self.headers = filter_and_format_headers(env, send_default_pii)
|
|
69
|
-
self.env = filter_and_format_env(env, rack_env_whitelist)
|
|
46
|
+
query = data_collection.url_query_params.filter(request.GET) rescue nil
|
|
47
|
+
|
|
48
|
+
self.method = request.request_method
|
|
49
|
+
self.url = request.scheme && request.url.split("?").first
|
|
50
|
+
self.query_string = query unless query&.empty?
|
|
51
|
+
self.cookies = data_collection.cookies.filter(request.cookies, cookie: true)
|
|
52
|
+
self.data = read_data_from(request) if data_collection.collect_incoming_http_body?
|
|
53
|
+
self.headers = filter_and_format_headers(env, data_collection.http_headers.request)
|
|
54
|
+
self.env = filter_and_format_env(env, data_collection.http_headers.request, rack_env_whitelist)
|
|
70
55
|
end
|
|
71
56
|
|
|
72
57
|
private
|
|
@@ -75,25 +60,31 @@ module Sentry
|
|
|
75
60
|
return "Skipped non-rewindable request body" unless request.body.respond_to?(:rewind)
|
|
76
61
|
|
|
77
62
|
if request.form_data?
|
|
78
|
-
request.POST
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
DataCollection.filter(request.POST)
|
|
64
|
+
else
|
|
65
|
+
body = request.body.read(MAX_BODY_LIMIT)
|
|
66
|
+
body = Utils::EncodingHelper.encode_to_utf_8(body.to_s)
|
|
67
|
+
|
|
68
|
+
if request.media_type == "application/json" || request.media_type&.end_with?("+json")
|
|
69
|
+
parsed_body = JSON.parse(body)
|
|
70
|
+
parsed_body.is_a?(Hash) ? DataCollection.filter(parsed_body) : parsed_body
|
|
71
|
+
else
|
|
72
|
+
body
|
|
73
|
+
end
|
|
84
74
|
end
|
|
85
|
-
rescue IOError => e
|
|
75
|
+
rescue JSON::ParserError, IOError => e
|
|
86
76
|
e.message
|
|
77
|
+
ensure
|
|
78
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
87
79
|
end
|
|
88
80
|
|
|
89
|
-
def filter_and_format_headers(env,
|
|
81
|
+
def filter_and_format_headers(env, collection)
|
|
90
82
|
env.each_with_object({}) do |(key, value), memo|
|
|
91
83
|
begin
|
|
92
84
|
key = key.to_s # rack env can contain symbols
|
|
93
85
|
next memo["X-Request-Id"] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
|
|
94
86
|
next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
|
|
95
87
|
next if is_skippable_header?(key)
|
|
96
|
-
next if key == "HTTP_AUTHORIZATION" && !send_default_pii
|
|
97
88
|
|
|
98
89
|
# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
|
|
99
90
|
key = key.delete_prefix("HTTP_")
|
|
@@ -107,12 +98,13 @@ module Sentry
|
|
|
107
98
|
Sentry.sdk_logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
|
|
108
99
|
next
|
|
109
100
|
end
|
|
101
|
+
end.then do |e|
|
|
102
|
+
collection.filter(e)
|
|
110
103
|
end
|
|
111
104
|
end
|
|
112
105
|
|
|
113
106
|
def is_skippable_header?(key)
|
|
114
107
|
key.match?(LOWERCASE_PATTERN) || # lower-case envs aren't real http headers
|
|
115
|
-
key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
|
|
116
108
|
!(key.start_with?("HTTP_") || CONTENT_HEADERS.include?(key))
|
|
117
109
|
end
|
|
118
110
|
|
|
@@ -134,11 +126,15 @@ module Sentry
|
|
|
134
126
|
Gem::Version.new(::Rack.release) >= Gem::Version.new("3.0")
|
|
135
127
|
end
|
|
136
128
|
|
|
137
|
-
def filter_and_format_env(env, rack_env_whitelist)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
129
|
+
def filter_and_format_env(env, collection, rack_env_whitelist)
|
|
130
|
+
if rack_env_whitelist.empty?
|
|
131
|
+
env
|
|
132
|
+
else
|
|
133
|
+
env.select do |k, _v|
|
|
134
|
+
rack_env_whitelist.include? k.to_s
|
|
135
|
+
end
|
|
136
|
+
end.then do |e|
|
|
137
|
+
collection.filter(e)
|
|
142
138
|
end
|
|
143
139
|
end
|
|
144
140
|
end
|
|
@@ -41,7 +41,7 @@ module Sentry
|
|
|
41
41
|
|
|
42
42
|
# patch this method if you want to change an exception's stacktrace frames
|
|
43
43
|
# also see `StacktraceBuilder.build`.
|
|
44
|
-
def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:)
|
|
44
|
+
def self.build_with_stacktrace(exception:, stacktrace_builder:, mechanism:, data_collection:)
|
|
45
45
|
stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
|
|
46
46
|
|
|
47
47
|
if locals = exception.instance_variable_get(:@sentry_locals)
|
|
@@ -60,7 +60,7 @@ module Sentry
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
stacktrace.frames.last&.vars = locals
|
|
63
|
+
stacktrace.frames.last&.vars = data_collection.stack_frame_variables.filter(locals)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
new(exception: exception, stacktrace: stacktrace, mechanism: mechanism)
|
data/lib/sentry/net/http.rb
CHANGED
|
@@ -72,10 +72,9 @@ module Sentry
|
|
|
72
72
|
|
|
73
73
|
result = { method: req.method, url: url }
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
end
|
|
75
|
+
query = filter_query_params(uri.query)
|
|
76
|
+
result[:query] = query if query
|
|
77
|
+
result[:body] = req.body if Sentry.configuration.data_collection.collect_outgoing_http_body?
|
|
79
78
|
|
|
80
79
|
result
|
|
81
80
|
end
|
data/lib/sentry/redis.rb
CHANGED
|
@@ -62,7 +62,7 @@ module Sentry
|
|
|
62
62
|
command_set = { command: command.to_s.upcase }
|
|
63
63
|
command_set[:key] = key if Utils::EncodingHelper.valid_utf_8?(key)
|
|
64
64
|
|
|
65
|
-
if Sentry.configuration.
|
|
65
|
+
if Sentry.configuration.data_collection.database_query_data
|
|
66
66
|
command_set[:arguments] = arguments
|
|
67
67
|
.select { |a| Utils::EncodingHelper.valid_utf_8?(a) }
|
|
68
68
|
.join(" ")
|
data/lib/sentry/scope.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
3
4
|
require "sentry/breadcrumb_buffer"
|
|
4
5
|
require "sentry/propagation_context"
|
|
5
6
|
require "sentry/attachment"
|
|
@@ -24,7 +25,8 @@ module Sentry
|
|
|
24
25
|
:span,
|
|
25
26
|
:session,
|
|
26
27
|
:attachments,
|
|
27
|
-
:propagation_context
|
|
28
|
+
:propagation_context,
|
|
29
|
+
:attributes
|
|
28
30
|
]
|
|
29
31
|
|
|
30
32
|
attr_reader(*ATTRIBUTES)
|
|
@@ -84,7 +86,15 @@ module Sentry
|
|
|
84
86
|
# @param telemetry [MetricEvent, LogEvent] the telemetry event to apply scope context to
|
|
85
87
|
# @return [MetricEvent, LogEvent] the telemetry event with scope context applied
|
|
86
88
|
def apply_to_telemetry(telemetry)
|
|
87
|
-
#
|
|
89
|
+
# Compare as strings since String and Symbol keys serialize to the same wire key.
|
|
90
|
+
existing_keys = telemetry.attributes.keys.map(&:to_s).to_set
|
|
91
|
+
|
|
92
|
+
attributes.each do |key, value|
|
|
93
|
+
next if existing_keys.include?(key)
|
|
94
|
+
|
|
95
|
+
telemetry.attributes[key] = value
|
|
96
|
+
end
|
|
97
|
+
|
|
88
98
|
trace_context = get_trace_context
|
|
89
99
|
telemetry.trace_id = trace_context[:trace_id]
|
|
90
100
|
telemetry.span_id = trace_context[:span_id]
|
|
@@ -136,6 +146,7 @@ module Sentry
|
|
|
136
146
|
copy.propagation_context = propagation_context.deep_dup
|
|
137
147
|
copy.attachments = attachments.dup
|
|
138
148
|
copy.event_processors = event_processors.dup
|
|
149
|
+
copy.attributes = attributes.deep_dup
|
|
139
150
|
copy
|
|
140
151
|
end
|
|
141
152
|
|
|
@@ -154,11 +165,12 @@ module Sentry
|
|
|
154
165
|
self.span = scope.span
|
|
155
166
|
self.propagation_context = scope.propagation_context
|
|
156
167
|
self.attachments = scope.attachments
|
|
168
|
+
self.attributes = scope.attributes
|
|
157
169
|
end
|
|
158
170
|
|
|
159
171
|
# Updates the scope's data from the given options.
|
|
160
172
|
# @param contexts [Hash]
|
|
161
|
-
# @param
|
|
173
|
+
# @param extra [Hash]
|
|
162
174
|
# @param tags [Hash]
|
|
163
175
|
# @param user [Hash]
|
|
164
176
|
# @param level [String, Symbol]
|
|
@@ -256,6 +268,32 @@ module Sentry
|
|
|
256
268
|
set_contexts(key => value)
|
|
257
269
|
end
|
|
258
270
|
|
|
271
|
+
# Updates the scope's attributes by merging with the old value.
|
|
272
|
+
# @param attributes_hash [Hash]
|
|
273
|
+
# @return [Hash]
|
|
274
|
+
def set_attributes(attributes_hash)
|
|
275
|
+
check_argument_type!(attributes_hash, Hash)
|
|
276
|
+
attributes_hash.each { |key, value| @attributes[key.to_s] = value }
|
|
277
|
+
@attributes
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Sets a single attribute on the scope.
|
|
281
|
+
# @param key [String, Symbol]
|
|
282
|
+
# @param value [Object]
|
|
283
|
+
# @param unit [String, Symbol, nil] an optional measurement unit for the value
|
|
284
|
+
# @return [Hash]
|
|
285
|
+
def set_attribute(key, value, unit: nil)
|
|
286
|
+
value = { value: value, unit: unit } unless unit.nil?
|
|
287
|
+
set_attributes(key => value)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Removes a single attribute from the scope. No-op if the attribute is not set.
|
|
291
|
+
# @param key [String, Symbol]
|
|
292
|
+
# @return [void]
|
|
293
|
+
def remove_attribute(key)
|
|
294
|
+
@attributes.delete(key.to_s)
|
|
295
|
+
end
|
|
296
|
+
|
|
259
297
|
# Sets the scope's level attribute.
|
|
260
298
|
# @param level [String, Symbol]
|
|
261
299
|
# @return [void]
|
|
@@ -361,6 +399,7 @@ module Sentry
|
|
|
361
399
|
@span = nil
|
|
362
400
|
@session = nil
|
|
363
401
|
@attachments = []
|
|
402
|
+
@attributes = {}
|
|
364
403
|
generate_propagation_context
|
|
365
404
|
set_new_breadcrumb_buffer
|
|
366
405
|
end
|
data/lib/sentry/span.rb
CHANGED
|
@@ -45,6 +45,7 @@ module Sentry
|
|
|
45
45
|
FUNCTION = "code.function"
|
|
46
46
|
NAMESPACE = "code.namespace"
|
|
47
47
|
|
|
48
|
+
MESSAGING_SYSTEM = "messaging.system"
|
|
48
49
|
MESSAGING_MESSAGE_ID = "messaging.message.id"
|
|
49
50
|
MESSAGING_DESTINATION_NAME = "messaging.destination.name"
|
|
50
51
|
MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
|
|
@@ -50,10 +50,6 @@ module Sentry
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
Sentry.register_patch(:logger) do
|
|
54
|
-
|
|
55
|
-
::Logger.prepend(Sentry::StdLibLogger)
|
|
56
|
-
else
|
|
57
|
-
config.sdk_logger.warn(":logger patch enabled but `enable_logs` is turned off - skipping applying patch")
|
|
58
|
-
end
|
|
53
|
+
Sentry.register_patch(:logger) do
|
|
54
|
+
::Logger.prepend(Sentry::StdLibLogger)
|
|
59
55
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
3
4
|
require "sentry/threaded_periodic_worker"
|
|
4
5
|
require "sentry/envelope"
|
|
5
6
|
|
|
@@ -58,7 +59,11 @@ module Sentry
|
|
|
58
59
|
|
|
59
60
|
if size >= @max_items_before_drop
|
|
60
61
|
log_debug("[#{self.class}] exceeded max capacity, dropping event")
|
|
61
|
-
@client.transport.record_lost_event(
|
|
62
|
+
@client.transport.record_lost_event(
|
|
63
|
+
:queue_overflow,
|
|
64
|
+
@data_category,
|
|
65
|
+
num_bytes: JSON.generate(item.to_h).bytesize
|
|
66
|
+
)
|
|
62
67
|
else
|
|
63
68
|
@pending_items << item
|
|
64
69
|
end
|
|
@@ -92,6 +97,7 @@ module Sentry
|
|
|
92
97
|
)
|
|
93
98
|
|
|
94
99
|
discarded_count = 0
|
|
100
|
+
discarded_bytes = 0
|
|
95
101
|
envelope_items = []
|
|
96
102
|
|
|
97
103
|
if @before_send
|
|
@@ -102,6 +108,7 @@ module Sentry
|
|
|
102
108
|
envelope_items << processed_item.to_h
|
|
103
109
|
else
|
|
104
110
|
discarded_count += 1
|
|
111
|
+
discarded_bytes += JSON.generate(item.to_h).bytesize
|
|
105
112
|
end
|
|
106
113
|
end
|
|
107
114
|
else
|
|
@@ -109,7 +116,7 @@ module Sentry
|
|
|
109
116
|
end
|
|
110
117
|
|
|
111
118
|
unless discarded_count.zero?
|
|
112
|
-
@client.transport.record_lost_event(:before_send, @data_category, num: discarded_count)
|
|
119
|
+
@client.transport.record_lost_event(:before_send, @data_category, num: discarded_count, num_bytes: discarded_bytes)
|
|
113
120
|
end
|
|
114
121
|
|
|
115
122
|
return if envelope_items.empty?
|
data/lib/sentry/test_helper.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Sentry
|
|
|
33
33
|
dummy_config.background_worker_threads = 0
|
|
34
34
|
|
|
35
35
|
# user can overwrite some of the configs, with a few exceptions like:
|
|
36
|
-
# -
|
|
36
|
+
# - data_collection
|
|
37
37
|
# - auto_session_tracking
|
|
38
38
|
block&.call(dummy_config)
|
|
39
39
|
|
|
@@ -52,10 +52,10 @@ module Sentry
|
|
|
52
52
|
test_client = Sentry::Client.new(dummy_config.dup)
|
|
53
53
|
main_hub.bind_client(test_client)
|
|
54
54
|
|
|
55
|
-
# Realign the current
|
|
55
|
+
# Realign the current execution context's hub with the main hub so direct
|
|
56
56
|
# `sentry_events` reads and any hub the Rack middleware clones from the
|
|
57
57
|
# main hub all observe the same DummyTransport.
|
|
58
|
-
|
|
58
|
+
Sentry.set_current_hub_internal(main_hub)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Clears all stored events and envelopes.
|
|
@@ -90,9 +90,7 @@ module Sentry
|
|
|
90
90
|
transport.clear if transport.respond_to?(:clear)
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
sentry_logger.clear
|
|
95
|
-
end
|
|
93
|
+
sentry_logger.clear if sentry_logger.respond_to?(:clear)
|
|
96
94
|
end
|
|
97
95
|
|
|
98
96
|
# @return [Sentry::StructuredLogger, Sentry::DebugStructuredLogger]
|
|
@@ -159,10 +157,13 @@ module Sentry
|
|
|
159
157
|
# Don't check initialized? because sometimes we stub it in tests
|
|
160
158
|
if Sentry.instance_variable_defined?(:@main_hub)
|
|
161
159
|
Sentry::GLOBALS.each do |var|
|
|
160
|
+
worker = Sentry.instance_variable_get(:"@#{var}")
|
|
161
|
+
worker.kill if worker.respond_to?(:kill)
|
|
162
|
+
|
|
162
163
|
Sentry.instance_variable_set(:"@#{var}", nil)
|
|
163
164
|
end
|
|
164
165
|
|
|
165
|
-
|
|
166
|
+
Sentry.set_current_hub_internal(nil)
|
|
166
167
|
end
|
|
167
168
|
end
|
|
168
169
|
end
|
|
@@ -30,10 +30,15 @@ module Sentry
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def kill
|
|
33
|
-
log_debug("[#{self.class.name}] thread killed")
|
|
34
|
-
|
|
35
33
|
@exited = true
|
|
36
|
-
|
|
34
|
+
|
|
35
|
+
# Only a started worker has a thread to kill (and to log about).
|
|
36
|
+
# Guarding here keeps a never-started worker's teardown silent, so
|
|
37
|
+
# killing one during test reset can't emit a stray debug line.
|
|
38
|
+
return unless @thread
|
|
39
|
+
|
|
40
|
+
log_debug("[#{self.class.name}] thread killed")
|
|
41
|
+
@thread.kill
|
|
37
42
|
end
|
|
38
43
|
end
|
|
39
44
|
end
|
data/lib/sentry/transport.rb
CHANGED
|
@@ -64,7 +64,7 @@ module Sentry
|
|
|
64
64
|
end
|
|
65
65
|
rescue Sentry::SizeExceededError
|
|
66
66
|
serialized_items&.each do |item|
|
|
67
|
-
record_lost_event(:send_error, item.data_category)
|
|
67
|
+
record_lost_event(:send_error, item.data_category, num: item.item_count, num_bytes: item.lost_event_byte_size)
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -167,11 +167,16 @@ module Sentry
|
|
|
167
167
|
envelope
|
|
168
168
|
end
|
|
169
169
|
|
|
170
|
-
def record_lost_event(reason, data_category, num: 1)
|
|
170
|
+
def record_lost_event(reason, data_category, num: 1, num_bytes: nil)
|
|
171
171
|
return unless @send_client_reports
|
|
172
172
|
return unless CLIENT_REPORT_REASONS.include?(reason)
|
|
173
173
|
|
|
174
174
|
@discarded_events[[reason, data_category]] += num
|
|
175
|
+
|
|
176
|
+
return unless num_bytes
|
|
177
|
+
|
|
178
|
+
byte_category = Envelope::Item.byte_data_category(data_category)
|
|
179
|
+
@discarded_events[[reason, byte_category]] += num_bytes if byte_category
|
|
175
180
|
end
|
|
176
181
|
|
|
177
182
|
def flush
|
|
@@ -211,7 +216,13 @@ module Sentry
|
|
|
211
216
|
envelope.items.reject! do |item|
|
|
212
217
|
if is_rate_limited?(item.data_category)
|
|
213
218
|
log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
|
|
214
|
-
|
|
219
|
+
|
|
220
|
+
record_lost_event(
|
|
221
|
+
:ratelimit_backoff,
|
|
222
|
+
item.data_category,
|
|
223
|
+
num: item.item_count,
|
|
224
|
+
num_bytes: item.lost_event_byte_size
|
|
225
|
+
)
|
|
215
226
|
|
|
216
227
|
true
|
|
217
228
|
else
|
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
3
5
|
module Sentry
|
|
4
6
|
module Utils
|
|
5
7
|
module HttpTracing
|
|
8
|
+
def filter_query_params(query)
|
|
9
|
+
return nil unless query
|
|
10
|
+
return nil unless query.is_a?(String) || query.is_a?(Hash)
|
|
11
|
+
|
|
12
|
+
query_hash = if query.is_a?(String)
|
|
13
|
+
URI.decode_www_form(query).each_with_object({}) do |(key, value), params|
|
|
14
|
+
params[key] = params.key?(key) ? Array(params[key]) + [value] : value
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
query
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
filtered_query_hash = Sentry.configuration.data_collection.url_query_params.filter(query_hash)
|
|
21
|
+
return nil if filtered_query_hash.empty?
|
|
22
|
+
|
|
23
|
+
HttpTracing.format_query(filtered_query_hash)
|
|
24
|
+
rescue
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.format_query(query)
|
|
29
|
+
query.flat_map do |key, value|
|
|
30
|
+
Array(value).map { |item| "#{key}=#{item}" }
|
|
31
|
+
end.join("&")
|
|
32
|
+
end
|
|
33
|
+
|
|
6
34
|
def set_span_info(sentry_span, request_info, response_status)
|
|
7
35
|
sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
|
8
|
-
sentry_span.set_data(Span::DataConventions::URL, request_info
|
|
36
|
+
sentry_span.set_data(Span::DataConventions::URL, url_with_query(request_info))
|
|
9
37
|
sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
|
|
10
38
|
sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
|
|
11
39
|
sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, response_status)
|
|
@@ -43,37 +71,25 @@ module Sentry
|
|
|
43
71
|
Sentry.configuration.trace_propagation_targets.any? { |target| url.match?(target) }
|
|
44
72
|
end
|
|
45
73
|
|
|
46
|
-
# Kindly borrowed from Rack::Utils
|
|
47
|
-
def build_nested_query(value, prefix = nil)
|
|
48
|
-
case value
|
|
49
|
-
when Array
|
|
50
|
-
value.map { |v|
|
|
51
|
-
build_nested_query(v, "#{prefix}[]")
|
|
52
|
-
}.join("&")
|
|
53
|
-
when Hash
|
|
54
|
-
value.map { |k, v|
|
|
55
|
-
build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
|
|
56
|
-
}.delete_if(&:empty?).join("&")
|
|
57
|
-
when nil
|
|
58
|
-
URI.encode_www_form_component(prefix)
|
|
59
|
-
else
|
|
60
|
-
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
|
61
|
-
"#{URI.encode_www_form_component(prefix)}=#{URI.encode_www_form_component(value)}"
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
74
|
|
|
65
75
|
private
|
|
66
76
|
|
|
77
|
+
def url_with_query(request_info)
|
|
78
|
+
return request_info[:url] unless request_info[:query]
|
|
79
|
+
|
|
80
|
+
"#{request_info[:url]}?#{request_info[:query]}"
|
|
81
|
+
end
|
|
82
|
+
|
|
67
83
|
def get_level(status)
|
|
68
84
|
return :info unless status && status.is_a?(Integer)
|
|
69
85
|
|
|
70
|
-
if status >= 500
|
|
71
|
-
|
|
72
|
-
elsif status >= 400
|
|
73
|
-
|
|
74
|
-
else
|
|
75
|
-
|
|
76
|
-
end
|
|
86
|
+
if status >= 500
|
|
87
|
+
:error
|
|
88
|
+
elsif status >= 400
|
|
89
|
+
:warning
|
|
90
|
+
else
|
|
91
|
+
:info
|
|
92
|
+
end
|
|
77
93
|
end
|
|
78
94
|
end
|
|
79
95
|
end
|
|
@@ -7,23 +7,35 @@ module Sentry
|
|
|
7
7
|
module TelemetryAttributes
|
|
8
8
|
private
|
|
9
9
|
|
|
10
|
-
def attribute_hash(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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" }
|
|
10
|
+
def attribute_hash(raw_value)
|
|
11
|
+
if raw_value.is_a?(Hash) && (raw_value.key?(:value) || raw_value.key?("value"))
|
|
12
|
+
value = raw_value.key?(:value) ? raw_value[:value] : raw_value["value"]
|
|
13
|
+
unit = raw_value.key?(:unit) ? raw_value[:unit] : raw_value["unit"]
|
|
20
14
|
else
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
value = raw_value
|
|
16
|
+
unit = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
result =
|
|
20
|
+
case value
|
|
21
|
+
when String
|
|
24
22
|
{ value: value, type: "string" }
|
|
23
|
+
when TrueClass, FalseClass
|
|
24
|
+
{ value: value, type: "boolean" }
|
|
25
|
+
when Integer
|
|
26
|
+
{ value: value, type: "integer" }
|
|
27
|
+
when Float
|
|
28
|
+
{ value: value, type: "double" }
|
|
29
|
+
else
|
|
30
|
+
begin
|
|
31
|
+
{ value: JSON.generate(value), type: "string" }
|
|
32
|
+
rescue
|
|
33
|
+
{ value: value, type: "string" }
|
|
34
|
+
end
|
|
25
35
|
end
|
|
26
|
-
|
|
36
|
+
|
|
37
|
+
result[:unit] = unit.to_s if unit.is_a?(String) || unit.is_a?(Symbol)
|
|
38
|
+
result
|
|
27
39
|
end
|
|
28
40
|
end
|
|
29
41
|
end
|
data/lib/sentry/version.rb
CHANGED