sentry-ruby 5.9.0 → 5.16.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.
- checksums.yaml +4 -4
- data/Gemfile +2 -10
- data/README.md +9 -9
- data/lib/sentry/background_worker.rb +8 -1
- data/lib/sentry/backpressure_monitor.rb +75 -0
- data/lib/sentry/breadcrumb.rb +8 -2
- data/lib/sentry/check_in_event.rb +60 -0
- data/lib/sentry/client.rb +48 -10
- data/lib/sentry/configuration.rb +89 -17
- data/lib/sentry/cron/configuration.rb +23 -0
- data/lib/sentry/cron/monitor_check_ins.rb +75 -0
- data/lib/sentry/cron/monitor_config.rb +53 -0
- data/lib/sentry/cron/monitor_schedule.rb +42 -0
- data/lib/sentry/envelope.rb +1 -1
- data/lib/sentry/event.rb +6 -28
- data/lib/sentry/hub.rb +74 -2
- data/lib/sentry/integrable.rb +6 -0
- data/lib/sentry/interfaces/single_exception.rb +5 -3
- data/lib/sentry/net/http.rb +26 -20
- data/lib/sentry/profiler.rb +18 -7
- data/lib/sentry/propagation_context.rb +134 -0
- data/lib/sentry/puma.rb +11 -4
- data/lib/sentry/rack/capture_exceptions.rb +1 -4
- data/lib/sentry/rake.rb +0 -13
- data/lib/sentry/redis.rb +9 -3
- data/lib/sentry/release_detector.rb +1 -1
- data/lib/sentry/scope.rb +29 -13
- data/lib/sentry/span.rb +39 -2
- data/lib/sentry/test_helper.rb +18 -12
- data/lib/sentry/transaction.rb +18 -19
- data/lib/sentry/transaction_event.rb +0 -3
- data/lib/sentry/transport/configuration.rb +74 -1
- data/lib/sentry/transport/http_transport.rb +68 -37
- data/lib/sentry/transport/spotlight_transport.rb +50 -0
- data/lib/sentry/transport.rb +21 -17
- data/lib/sentry/utils/argument_checking_helper.rb +9 -3
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +83 -25
- metadata +10 -3
- data/CODE_OF_CONDUCT.md +0 -74
data/lib/sentry/puma.rb
CHANGED
@@ -1,10 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
return unless defined?(Puma::Server)
|
4
|
+
|
3
5
|
module Sentry
|
4
6
|
module Puma
|
5
7
|
module Server
|
8
|
+
PUMA_4_AND_PRIOR = Gem::Version.new(::Puma::Const::PUMA_VERSION) < Gem::Version.new("5.0.0")
|
9
|
+
|
6
10
|
def lowlevel_error(e, env, status=500)
|
7
|
-
result =
|
11
|
+
result =
|
12
|
+
if PUMA_4_AND_PRIOR
|
13
|
+
super(e, env)
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
8
17
|
|
9
18
|
begin
|
10
19
|
Sentry.capture_exception(e) do |scope|
|
@@ -20,6 +29,4 @@ module Sentry
|
|
20
29
|
end
|
21
30
|
end
|
22
31
|
|
23
|
-
|
24
|
-
Sentry.register_patch(Sentry::Puma::Server, Puma::Server)
|
25
|
-
end
|
32
|
+
Sentry.register_patch(:puma, Sentry::Puma::Server, Puma::Server)
|
@@ -62,11 +62,8 @@ module Sentry
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def start_transaction(env, scope)
|
65
|
-
sentry_trace = env["HTTP_SENTRY_TRACE"]
|
66
|
-
baggage = env["HTTP_BAGGAGE"]
|
67
|
-
|
68
65
|
options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
|
69
|
-
transaction = Sentry
|
66
|
+
transaction = Sentry.continue_trace(env, **options)
|
70
67
|
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
71
68
|
end
|
72
69
|
|
data/lib/sentry/rake.rb
CHANGED
@@ -17,15 +17,6 @@ module Sentry
|
|
17
17
|
super
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
21
|
-
module Task
|
22
|
-
# @api private
|
23
|
-
def execute(args=nil)
|
24
|
-
return super unless Sentry.initialized? && Sentry.get_current_hub
|
25
|
-
|
26
|
-
super
|
27
|
-
end
|
28
|
-
end
|
29
20
|
end
|
30
21
|
end
|
31
22
|
|
@@ -34,8 +25,4 @@ module Rake
|
|
34
25
|
class Application
|
35
26
|
prepend(Sentry::Rake::Application)
|
36
27
|
end
|
37
|
-
|
38
|
-
class Task
|
39
|
-
prepend(Sentry::Rake::Task)
|
40
|
-
end
|
41
28
|
end
|
data/lib/sentry/redis.rb
CHANGED
@@ -19,7 +19,10 @@ module Sentry
|
|
19
19
|
|
20
20
|
if span
|
21
21
|
span.set_description(commands_description)
|
22
|
-
span.set_data(
|
22
|
+
span.set_data(Span::DataConventions::DB_SYSTEM, "redis")
|
23
|
+
span.set_data(Span::DataConventions::DB_NAME, db)
|
24
|
+
span.set_data(Span::DataConventions::SERVER_ADDRESS, host)
|
25
|
+
span.set_data(Span::DataConventions::SERVER_PORT, port)
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
@@ -30,6 +33,7 @@ module Sentry
|
|
30
33
|
attr_reader :commands, :host, :port, :db
|
31
34
|
|
32
35
|
def record_breadcrumb
|
36
|
+
return unless Sentry.initialized?
|
33
37
|
return unless Sentry.configuration.breadcrumbs_logger.include?(LOGGER_NAME)
|
34
38
|
|
35
39
|
Sentry.add_breadcrumb(
|
@@ -95,8 +99,10 @@ end
|
|
95
99
|
|
96
100
|
if defined?(::Redis::Client)
|
97
101
|
if Gem::Version.new(::Redis::VERSION) < Gem::Version.new("5.0")
|
98
|
-
Sentry.register_patch(Sentry::Redis::OldClientPatch, ::Redis::Client)
|
102
|
+
Sentry.register_patch(:redis, Sentry::Redis::OldClientPatch, ::Redis::Client)
|
99
103
|
elsif defined?(RedisClient)
|
100
|
-
|
104
|
+
Sentry.register_patch(:redis) do
|
105
|
+
RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
|
106
|
+
end
|
101
107
|
end
|
102
108
|
end
|
data/lib/sentry/scope.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sentry/breadcrumb_buffer"
|
4
|
+
require "sentry/propagation_context"
|
4
5
|
require "etc"
|
5
6
|
|
6
7
|
module Sentry
|
@@ -20,7 +21,8 @@ module Sentry
|
|
20
21
|
:event_processors,
|
21
22
|
:rack_env,
|
22
23
|
:span,
|
23
|
-
:session
|
24
|
+
:session,
|
25
|
+
:propagation_context
|
24
26
|
]
|
25
27
|
|
26
28
|
attr_reader(*ATTRIBUTES)
|
@@ -42,22 +44,26 @@ module Sentry
|
|
42
44
|
# @param hint [Hash] the hint data that'll be passed to event processors.
|
43
45
|
# @return [Event]
|
44
46
|
def apply_to_event(event, hint = nil)
|
45
|
-
event.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
unless event.is_a?(CheckInEvent)
|
48
|
+
event.tags = tags.merge(event.tags)
|
49
|
+
event.user = user.merge(event.user)
|
50
|
+
event.extra = extra.merge(event.extra)
|
51
|
+
event.contexts = contexts.merge(event.contexts)
|
52
|
+
event.transaction = transaction_name if transaction_name
|
53
|
+
event.transaction_info = { source: transaction_source } if transaction_source
|
54
|
+
event.fingerprint = fingerprint
|
55
|
+
event.level = level
|
56
|
+
event.breadcrumbs = breadcrumbs
|
57
|
+
event.rack_env = rack_env if rack_env
|
58
|
+
end
|
51
59
|
|
52
60
|
if span
|
53
|
-
event.contexts[:trace]
|
61
|
+
event.contexts[:trace] ||= span.get_trace_context
|
62
|
+
else
|
63
|
+
event.contexts[:trace] ||= propagation_context.get_trace_context
|
64
|
+
event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
|
54
65
|
end
|
55
66
|
|
56
|
-
event.fingerprint = fingerprint
|
57
|
-
event.level = level
|
58
|
-
event.breadcrumbs = breadcrumbs
|
59
|
-
event.rack_env = rack_env if rack_env
|
60
|
-
|
61
67
|
all_event_processors = self.class.global_event_processors + @event_processors
|
62
68
|
|
63
69
|
unless all_event_processors.empty?
|
@@ -95,6 +101,7 @@ module Sentry
|
|
95
101
|
copy.fingerprint = fingerprint.deep_dup
|
96
102
|
copy.span = span.deep_dup
|
97
103
|
copy.session = session.deep_dup
|
104
|
+
copy.propagation_context = propagation_context.deep_dup
|
98
105
|
copy
|
99
106
|
end
|
100
107
|
|
@@ -111,6 +118,7 @@ module Sentry
|
|
111
118
|
self.transaction_sources = scope.transaction_sources
|
112
119
|
self.fingerprint = scope.fingerprint
|
113
120
|
self.span = scope.span
|
121
|
+
self.propagation_context = scope.propagation_context
|
114
122
|
end
|
115
123
|
|
116
124
|
# Updates the scope's data from the given options.
|
@@ -272,6 +280,13 @@ module Sentry
|
|
272
280
|
@event_processors << block
|
273
281
|
end
|
274
282
|
|
283
|
+
# Generate a new propagation context either from the incoming env headers or from scratch.
|
284
|
+
# @param env [Hash, nil]
|
285
|
+
# @return [void]
|
286
|
+
def generate_propagation_context(env = nil)
|
287
|
+
@propagation_context = PropagationContext.new(self, env)
|
288
|
+
end
|
289
|
+
|
275
290
|
protected
|
276
291
|
|
277
292
|
# for duplicating scopes internally
|
@@ -292,6 +307,7 @@ module Sentry
|
|
292
307
|
@rack_env = {}
|
293
308
|
@span = nil
|
294
309
|
@session = nil
|
310
|
+
generate_propagation_context
|
295
311
|
set_new_breadcrumb_buffer
|
296
312
|
end
|
297
313
|
|
data/lib/sentry/span.rb
CHANGED
@@ -4,6 +4,43 @@ require "securerandom"
|
|
4
4
|
|
5
5
|
module Sentry
|
6
6
|
class Span
|
7
|
+
|
8
|
+
# We will try to be consistent with OpenTelemetry on this front going forward.
|
9
|
+
# https://develop.sentry.dev/sdk/performance/span-data-conventions/
|
10
|
+
module DataConventions
|
11
|
+
URL = "url"
|
12
|
+
HTTP_STATUS_CODE = "http.response.status_code"
|
13
|
+
HTTP_QUERY = "http.query"
|
14
|
+
HTTP_METHOD = "http.request.method"
|
15
|
+
|
16
|
+
# An identifier for the database management system (DBMS) product being used.
|
17
|
+
# Example: postgresql
|
18
|
+
DB_SYSTEM = "db.system"
|
19
|
+
|
20
|
+
# The name of the database being accessed.
|
21
|
+
# For commands that switch the database, this should be set to the target database
|
22
|
+
# (even if the command fails).
|
23
|
+
# Example: myDatabase
|
24
|
+
DB_NAME = "db.name"
|
25
|
+
|
26
|
+
# Name of the database host.
|
27
|
+
# Example: example.com
|
28
|
+
SERVER_ADDRESS = "server.address"
|
29
|
+
|
30
|
+
# Logical server port number
|
31
|
+
# Example: 80; 8080; 443
|
32
|
+
SERVER_PORT = "server.port"
|
33
|
+
|
34
|
+
# Physical server IP address or Unix socket address.
|
35
|
+
# Example: 10.5.3.2
|
36
|
+
SERVER_SOCKET_ADDRESS = "server.socket.address"
|
37
|
+
|
38
|
+
# Physical server port.
|
39
|
+
# Recommended: If different than server.port.
|
40
|
+
# Example: 16456
|
41
|
+
SERVER_SOCKET_PORT = "server.socket.port"
|
42
|
+
end
|
43
|
+
|
7
44
|
STATUS_MAP = {
|
8
45
|
400 => "invalid_argument",
|
9
46
|
401 => "unauthenticated",
|
@@ -75,7 +112,7 @@ module Sentry
|
|
75
112
|
timestamp: nil
|
76
113
|
)
|
77
114
|
@trace_id = trace_id || SecureRandom.uuid.delete("-")
|
78
|
-
@span_id = span_id || SecureRandom.
|
115
|
+
@span_id = span_id || SecureRandom.uuid.delete("-").slice(0, 16)
|
79
116
|
@parent_span_id = parent_span_id
|
80
117
|
@sampled = sampled
|
81
118
|
@start_timestamp = start_timestamp || Sentry.utc_now.to_f
|
@@ -208,7 +245,7 @@ module Sentry
|
|
208
245
|
# @param status_code [String] example: "500".
|
209
246
|
def set_http_status(status_code)
|
210
247
|
status_code = status_code.to_i
|
211
|
-
set_data(
|
248
|
+
set_data(DataConventions::HTTP_STATUS_CODE, status_code)
|
212
249
|
|
213
250
|
status =
|
214
251
|
if status_code >= 200 && status_code < 299
|
data/lib/sentry/test_helper.rb
CHANGED
@@ -14,24 +14,28 @@ module Sentry
|
|
14
14
|
# @return [void]
|
15
15
|
def setup_sentry_test(&block)
|
16
16
|
raise "please make sure the SDK is initialized for testing" unless Sentry.initialized?
|
17
|
-
|
17
|
+
dummy_config = Sentry.configuration.dup
|
18
18
|
# configure dummy DSN, so the events will not be sent to the actual service
|
19
|
-
|
19
|
+
dummy_config.dsn = DUMMY_DSN
|
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
|
-
|
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
|
|
27
27
|
# user can overwrite some of the configs, with a few exceptions like:
|
28
28
|
# - include_local_variables
|
29
29
|
# - auto_session_tracking
|
30
|
-
block&.call(
|
30
|
+
block&.call(dummy_config)
|
31
31
|
|
32
|
-
|
32
|
+
# the base layer's client should already use the dummy config so nothing will be sent by accident
|
33
|
+
base_client = Sentry::Client.new(dummy_config)
|
34
|
+
Sentry.get_current_hub.bind_client(base_client)
|
35
|
+
# create a new layer so mutations made to the testing scope or configuration could be simply popped later
|
36
|
+
Sentry.get_current_hub.push_scope
|
37
|
+
test_client = Sentry::Client.new(dummy_config.dup)
|
33
38
|
Sentry.get_current_hub.bind_client(test_client)
|
34
|
-
Sentry.get_current_scope.clear
|
35
39
|
end
|
36
40
|
|
37
41
|
# Clears all stored events and envelopes.
|
@@ -40,9 +44,12 @@ module Sentry
|
|
40
44
|
def teardown_sentry_test
|
41
45
|
return unless Sentry.initialized?
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
# pop testing layer created by `setup_sentry_test`
|
48
|
+
# but keep the base layer to avoid nil-pointer errors
|
49
|
+
# TODO: find a way to notify users if they somehow popped the test layer before calling this method
|
50
|
+
if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
|
51
|
+
Sentry.get_current_hub.pop_scope
|
52
|
+
end
|
46
53
|
end
|
47
54
|
|
48
55
|
# @return [Transport]
|
@@ -75,4 +82,3 @@ module Sentry
|
|
75
82
|
end
|
76
83
|
end
|
77
84
|
end
|
78
|
-
|
data/lib/sentry/transaction.rb
CHANGED
@@ -2,16 +2,13 @@
|
|
2
2
|
|
3
3
|
require "sentry/baggage"
|
4
4
|
require "sentry/profiler"
|
5
|
+
require "sentry/propagation_context"
|
5
6
|
|
6
7
|
module Sentry
|
7
8
|
class Transaction < Span
|
8
|
-
SENTRY_TRACE_REGEXP
|
9
|
-
|
10
|
-
|
11
|
-
"-?([0-9a-f]{16})?" + # span_id
|
12
|
-
"-?([01])?" + # sampled
|
13
|
-
"[ \t]*$" # whitespace
|
14
|
-
)
|
9
|
+
# @deprecated Use Sentry::PropagationContext::SENTRY_TRACE_REGEXP instead.
|
10
|
+
SENTRY_TRACE_REGEXP = PropagationContext::SENTRY_TRACE_REGEXP
|
11
|
+
|
15
12
|
UNLABELD_NAME = "<unlabeled transaction>".freeze
|
16
13
|
MESSAGE_PREFIX = "[Tracing]"
|
17
14
|
|
@@ -92,6 +89,8 @@ module Sentry
|
|
92
89
|
init_span_recorder
|
93
90
|
end
|
94
91
|
|
92
|
+
# @deprecated use Sentry.continue_trace instead.
|
93
|
+
#
|
95
94
|
# Initalizes a Transaction instance with a Sentry trace string from another transaction (usually from an external request).
|
96
95
|
#
|
97
96
|
# The original transaction will become the parent of the new Transaction instance. And they will share the same `trace_id`.
|
@@ -132,18 +131,10 @@ module Sentry
|
|
132
131
|
)
|
133
132
|
end
|
134
133
|
|
135
|
-
#
|
136
|
-
#
|
137
|
-
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
134
|
+
# @deprecated Use Sentry::PropagationContext.extract_sentry_trace instead.
|
138
135
|
# @return [Array, nil]
|
139
136
|
def self.extract_sentry_trace(sentry_trace)
|
140
|
-
|
141
|
-
return nil if match.nil?
|
142
|
-
|
143
|
-
trace_id, parent_span_id, sampled_flag = match[1..3]
|
144
|
-
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
145
|
-
|
146
|
-
[trace_id, parent_span_id, parent_sampled]
|
137
|
+
PropagationContext.extract_sentry_trace(sentry_trace)
|
147
138
|
end
|
148
139
|
|
149
140
|
# @return [Hash]
|
@@ -227,7 +218,12 @@ module Sentry
|
|
227
218
|
if sample_rate == true
|
228
219
|
@sampled = true
|
229
220
|
else
|
230
|
-
|
221
|
+
if Sentry.backpressure_monitor
|
222
|
+
factor = Sentry.backpressure_monitor.downsample_factor
|
223
|
+
@effective_sample_rate /= 2**factor
|
224
|
+
end
|
225
|
+
|
226
|
+
@sampled = Random.rand < @effective_sample_rate
|
231
227
|
end
|
232
228
|
|
233
229
|
if @sampled
|
@@ -266,7 +262,9 @@ module Sentry
|
|
266
262
|
event = hub.current_client.event_from_transaction(self)
|
267
263
|
hub.capture_event(event)
|
268
264
|
else
|
269
|
-
|
265
|
+
is_backpressure = Sentry.backpressure_monitor&.downsample_factor&.positive?
|
266
|
+
reason = is_backpressure ? :backpressure : :sample_rate
|
267
|
+
hub.current_client.transport.record_lost_event(reason, 'transaction')
|
270
268
|
end
|
271
269
|
end
|
272
270
|
|
@@ -323,6 +321,7 @@ module Sentry
|
|
323
321
|
items = {
|
324
322
|
"trace_id" => trace_id,
|
325
323
|
"sample_rate" => effective_sample_rate&.to_s,
|
324
|
+
"sampled" => sampled&.to_s,
|
326
325
|
"environment" => @environment,
|
327
326
|
"release" => @release,
|
328
327
|
"public_key" => @dsn&.public_key
|
@@ -3,7 +3,80 @@
|
|
3
3
|
module Sentry
|
4
4
|
class Transport
|
5
5
|
class Configuration
|
6
|
-
|
6
|
+
|
7
|
+
# The timeout in seconds to open a connection to Sentry, in seconds.
|
8
|
+
# Default value is 2.
|
9
|
+
#
|
10
|
+
# @return [Integer]
|
11
|
+
attr_accessor :timeout
|
12
|
+
|
13
|
+
# The timeout in seconds to read data from Sentry, in seconds.
|
14
|
+
# Default value is 1.
|
15
|
+
#
|
16
|
+
# @return [Integer]
|
17
|
+
attr_accessor :open_timeout
|
18
|
+
|
19
|
+
# The proxy configuration to use to connect to Sentry.
|
20
|
+
# Accepts either a URI formatted string, URI, or a hash with the `uri`,
|
21
|
+
# `user`, and `password` keys.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# # setup proxy using a string:
|
25
|
+
# config.transport.proxy = "https://user:password@proxyhost:8080"
|
26
|
+
#
|
27
|
+
# # setup proxy using a URI:
|
28
|
+
# config.transport.proxy = URI("https://user:password@proxyhost:8080")
|
29
|
+
#
|
30
|
+
# # setup proxy using a hash:
|
31
|
+
# config.transport.proxy = {
|
32
|
+
# uri: URI("https://proxyhost:8080"),
|
33
|
+
# user: "user",
|
34
|
+
# password: "password"
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# If you're using the default transport (`Sentry::HTTPTransport`),
|
38
|
+
# proxy settings will also automatically be read from tne environment
|
39
|
+
# variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`).
|
40
|
+
#
|
41
|
+
# @return [String, URI, Hash, nil]
|
42
|
+
attr_accessor :proxy
|
43
|
+
|
44
|
+
# The SSL configuration to use to connect to Sentry.
|
45
|
+
# You can either pass a `Hash` containing `ca_file` and `verification` keys,
|
46
|
+
# or you can set those options directly on the `Sentry::HTTPTransport::Configuration` object:
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# config.transport.ssl = {
|
50
|
+
# ca_file: "/path/to/ca_file",
|
51
|
+
# verification: true
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# @return [Hash, nil]
|
55
|
+
attr_accessor :ssl
|
56
|
+
|
57
|
+
# The path to the CA file to use to verify the SSL connection.
|
58
|
+
# Default value is `nil`.
|
59
|
+
#
|
60
|
+
# @return [String, nil]
|
61
|
+
attr_accessor :ssl_ca_file
|
62
|
+
|
63
|
+
# Whether to verify that the peer certificate is valid in SSL connections.
|
64
|
+
# Default value is `true`.
|
65
|
+
#
|
66
|
+
# @return [Boolean]
|
67
|
+
attr_accessor :ssl_verification
|
68
|
+
|
69
|
+
# The encoding to use to compress the request body.
|
70
|
+
# Default value is `Sentry::HTTPTransport::GZIP_ENCODING`.
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
attr_accessor :encoding
|
74
|
+
|
75
|
+
# The class to use as a transport to connect to Sentry.
|
76
|
+
# If this option not set, it will return `nil`, and Sentry will use
|
77
|
+
# `Sentry::HTTPTransport` by default.
|
78
|
+
#
|
79
|
+
# @return [Class, nil]
|
7
80
|
attr_reader :transport_class
|
8
81
|
|
9
82
|
def initialize
|
@@ -14,11 +14,19 @@ module Sentry
|
|
14
14
|
RATE_LIMIT_HEADER = "x-sentry-rate-limits"
|
15
15
|
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
|
16
16
|
|
17
|
+
# The list of errors ::Net::HTTP is known to raise
|
18
|
+
# See https://github.com/ruby/ruby/blob/b0c639f249165d759596f9579fa985cb30533de6/lib/bundler/fetcher.rb#L281-L286
|
19
|
+
HTTP_ERRORS = [
|
20
|
+
Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH,
|
21
|
+
Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
|
22
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
23
|
+
Zlib::BufError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
|
17
27
|
def initialize(*args)
|
18
28
|
super
|
19
|
-
@
|
20
|
-
|
21
|
-
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}")
|
29
|
+
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}") if @dsn
|
22
30
|
end
|
23
31
|
|
24
32
|
def send_data(data)
|
@@ -32,34 +40,76 @@ module Sentry
|
|
32
40
|
headers = {
|
33
41
|
'Content-Type' => CONTENT_TYPE,
|
34
42
|
'Content-Encoding' => encoding,
|
35
|
-
'X-Sentry-Auth' => generate_auth_header,
|
36
43
|
'User-Agent' => USER_AGENT
|
37
44
|
}
|
38
45
|
|
46
|
+
auth_header = generate_auth_header
|
47
|
+
headers['X-Sentry-Auth'] = auth_header if auth_header
|
48
|
+
|
39
49
|
response = conn.start do |http|
|
40
|
-
request = ::Net::HTTP::Post.new(
|
50
|
+
request = ::Net::HTTP::Post.new(endpoint, headers)
|
41
51
|
request.body = data
|
42
52
|
http.request(request)
|
43
53
|
end
|
44
54
|
|
45
55
|
if response.code.match?(/\A2\d{2}/)
|
46
|
-
if has_rate_limited_header?(response)
|
47
|
-
|
48
|
-
|
56
|
+
handle_rate_limited_response(response) if has_rate_limited_header?(response)
|
57
|
+
elsif response.code == "429"
|
58
|
+
log_debug("the server responded with status 429")
|
59
|
+
handle_rate_limited_response(response)
|
49
60
|
else
|
50
61
|
error_info = "the server responded with status #{response.code}"
|
62
|
+
error_info += "\nbody: #{response.body}"
|
63
|
+
error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
|
64
|
+
|
65
|
+
raise Sentry::ExternalError, error_info
|
66
|
+
end
|
67
|
+
rescue SocketError, *HTTP_ERRORS => e
|
68
|
+
on_error if respond_to?(:on_error)
|
69
|
+
raise Sentry::ExternalError.new(e&.message)
|
70
|
+
end
|
71
|
+
|
72
|
+
def endpoint
|
73
|
+
@dsn.envelope_endpoint
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_auth_header
|
77
|
+
return nil unless @dsn
|
78
|
+
|
79
|
+
now = Sentry.utc_now.to_i
|
80
|
+
fields = {
|
81
|
+
'sentry_version' => PROTOCOL_VERSION,
|
82
|
+
'sentry_client' => USER_AGENT,
|
83
|
+
'sentry_timestamp' => now,
|
84
|
+
'sentry_key' => @dsn.public_key
|
85
|
+
}
|
86
|
+
fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
|
87
|
+
'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
|
88
|
+
end
|
51
89
|
|
52
|
-
|
53
|
-
|
90
|
+
def conn
|
91
|
+
server = URI(@dsn.server)
|
92
|
+
|
93
|
+
# connection respects proxy setting from @transport_configuration, or environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
|
94
|
+
# Net::HTTP will automatically read the env vars.
|
95
|
+
# See https://ruby-doc.org/3.2.2/stdlibs/net/Net/HTTP.html#class-Net::HTTP-label-Proxies
|
96
|
+
connection =
|
97
|
+
if proxy = normalize_proxy(@transport_configuration.proxy)
|
98
|
+
::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
|
54
99
|
else
|
55
|
-
|
56
|
-
error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
|
100
|
+
::Net::HTTP.new(server.hostname, server.port)
|
57
101
|
end
|
58
102
|
|
59
|
-
|
103
|
+
connection.use_ssl = server.scheme == "https"
|
104
|
+
connection.read_timeout = @transport_configuration.timeout
|
105
|
+
connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
|
106
|
+
connection.open_timeout = @transport_configuration.open_timeout
|
107
|
+
|
108
|
+
ssl_configuration.each do |key, value|
|
109
|
+
connection.send("#{key}=", value)
|
60
110
|
end
|
61
|
-
|
62
|
-
|
111
|
+
|
112
|
+
connection
|
63
113
|
end
|
64
114
|
|
65
115
|
private
|
@@ -126,28 +176,9 @@ module Sentry
|
|
126
176
|
@transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
|
127
177
|
end
|
128
178
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
connection =
|
133
|
-
if proxy = normalize_proxy(@transport_configuration.proxy)
|
134
|
-
::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
|
135
|
-
else
|
136
|
-
::Net::HTTP.new(server.hostname, server.port, nil)
|
137
|
-
end
|
138
|
-
|
139
|
-
connection.use_ssl = server.scheme == "https"
|
140
|
-
connection.read_timeout = @transport_configuration.timeout
|
141
|
-
connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
|
142
|
-
connection.open_timeout = @transport_configuration.open_timeout
|
143
|
-
|
144
|
-
ssl_configuration.each do |key, value|
|
145
|
-
connection.send("#{key}=", value)
|
146
|
-
end
|
147
|
-
|
148
|
-
connection
|
149
|
-
end
|
150
|
-
|
179
|
+
# @param proxy [String, URI, Hash] Proxy config value passed into `config.transport`.
|
180
|
+
# Accepts either a URI formatted string, URI, or a hash with the `uri`, `user`, and `password` keys.
|
181
|
+
# @return [Hash] Normalized proxy config that will be passed into `Net::HTTP`
|
151
182
|
def normalize_proxy(proxy)
|
152
183
|
return proxy unless proxy
|
153
184
|
|