sentry-ruby 5.13.0 → 5.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d89549da043b049e2dd6f5c73ac6ec0c3dd98ded247be2035316e45110bde88
4
- data.tar.gz: 38844c5d5521bcf40a749d77667e91fa5b819d98951e4a02ebc807724b91c3a9
3
+ metadata.gz: 8ad04d5359f4cc44d2a2e398d1083b72f11c96efabc4a3b2148327261e327a20
4
+ data.tar.gz: 2800581dce649c81f5a17464f3b5619158900d8c5eb23e35347507dddb594b04
5
5
  SHA512:
6
- metadata.gz: 1cc2ac4f8394b8dcafd30d2aa4bbd863e313a04953d5b072997783c54fa76285bff16b77e23ca7f6b22644f9d1a69ad2484c844b7e6bf6deefbf3cbcd874ffbf
7
- data.tar.gz: c30a0fd380e11f93edc599320a3884464e9d0568327512e514d1586f120b217091f67912531167738efb64ffd55cec960c6083dc894556796a00f2f5c79e13f9
6
+ metadata.gz: d8bce6cfd5f48acb743b6018e5d34e4832de9eb05e68a0c114fd90bb13e8c80fcf6da97cb8e80a64d78e930b3ee6b8ee22711c2efa2e018f43b33d0ff09cf68b
7
+ data.tar.gz: 1f9749bf1ccee5a541c4949dbf2b0f79da9508f6a86aa3d603811b252a49f4ca3054e91dfa7a0e93dac723965e8e316f56c8433ddf796c7d8f38069075b80268
data/lib/sentry/client.rb CHANGED
@@ -10,6 +10,10 @@ module Sentry
10
10
  # @return [Transport]
11
11
  attr_reader :transport
12
12
 
13
+ # The Transport object that'll send events for the client.
14
+ # @return [SpotlightTransport, nil]
15
+ attr_reader :spotlight_transport
16
+
13
17
  # @!macro configuration
14
18
  attr_reader :configuration
15
19
 
@@ -32,6 +36,8 @@ module Sentry
32
36
  DummyTransport.new(configuration)
33
37
  end
34
38
  end
39
+
40
+ @spotlight_transport = SpotlightTransport.new(configuration) if configuration.spotlight
35
41
  end
36
42
 
37
43
  # Applies the given scope's data to the event and sends it to Sentry.
@@ -167,6 +173,7 @@ module Sentry
167
173
  end
168
174
 
169
175
  transport.send_event(event)
176
+ spotlight_transport&.send_event(event)
170
177
 
171
178
  event
172
179
  rescue => e
@@ -142,6 +142,14 @@ module Sentry
142
142
  # @return [Boolean]
143
143
  attr_accessor :include_local_variables
144
144
 
145
+ # Whether to capture events and traces into Spotlight. Default is false.
146
+ # If you set this to true, Sentry will send events and traces to the local
147
+ # Sidecar proxy at http://localhost:8969/stream.
148
+ # If you want to use a different Sidecar proxy address, set this to String
149
+ # with the proxy URL.
150
+ # @return [Boolean, String]
151
+ attr_accessor :spotlight
152
+
145
153
  # @deprecated Use {#include_local_variables} instead.
146
154
  alias_method :capture_exception_frame_locals, :include_local_variables
147
155
 
@@ -344,6 +352,7 @@ module Sentry
344
352
  self.auto_session_tracking = true
345
353
  self.trusted_proxies = []
346
354
  self.dsn = ENV['SENTRY_DSN']
355
+ self.spotlight = false
347
356
  self.server_name = server_name_from_env
348
357
  self.instrumenter = :sentry
349
358
  self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
@@ -451,7 +460,7 @@ module Sentry
451
460
  def sending_allowed?
452
461
  @errors = []
453
462
 
454
- valid? && capture_in_environment?
463
+ spotlight || (valid? && capture_in_environment?)
455
464
  end
456
465
 
457
466
  def sample_allowed?
@@ -1,9 +1,11 @@
1
1
  module Sentry
2
2
  module Cron
3
3
  module MonitorCheckIns
4
+ MAX_SLUG_LENGTH = 50
5
+
4
6
  module Patch
5
7
  def perform(*args)
6
- slug = self.class.sentry_monitor_slug || self.class.name
8
+ slug = self.class.sentry_monitor_slug
7
9
  monitor_config = self.class.sentry_monitor_config
8
10
 
9
11
  check_in_id = Sentry.capture_check_in(slug,
@@ -42,8 +44,11 @@ module Sentry
42
44
  prepend Patch
43
45
  end
44
46
 
45
- def sentry_monitor_slug
46
- @sentry_monitor_slug
47
+ def sentry_monitor_slug(name: self.name)
48
+ @sentry_monitor_slug ||= begin
49
+ slug = name.gsub('::', '-').downcase
50
+ slug[-MAX_SLUG_LENGTH..-1] || slug
51
+ end
47
52
  end
48
53
 
49
54
  def sentry_monitor_config
@@ -22,6 +22,7 @@ module Sentry
22
22
  else
23
23
  exception.message || ""
24
24
  end
25
+ exception_message = exception_message.inspect unless exception_message.is_a?(String)
25
26
 
26
27
  @value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
27
28
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "net/http"
4
+ require "resolv"
4
5
 
5
6
  module Sentry
6
7
  # @api private
@@ -77,7 +78,10 @@ module Sentry
77
78
  end
78
79
 
79
80
  def extract_request_info(req)
80
- uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{address}#{req.path}")
81
+ # IPv6 url could look like '::1/path', and that won't parse without
82
+ # wrapping it in square brackets.
83
+ hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
84
+ uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
81
85
  url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
82
86
 
83
87
  result = { method: req.method, url: url }
@@ -28,7 +28,7 @@ module Sentry
28
28
  end
29
29
 
30
30
  def detect_release_from_git
31
- Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
31
+ Sentry.sys_command("git rev-parse HEAD") if File.directory?(".git")
32
32
  end
33
33
 
34
34
  def detect_release_from_env
@@ -3,7 +3,80 @@
3
3
  module Sentry
4
4
  class Transport
5
5
  class Configuration
6
- attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :encoding
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
- @endpoint = @dsn.envelope_endpoint
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,12 +40,14 @@ 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(@endpoint, headers)
50
+ request = ::Net::HTTP::Post.new(endpoint, headers)
41
51
  request.body = data
42
52
  http.request(request)
43
53
  end
@@ -58,8 +68,52 @@ module Sentry
58
68
 
59
69
  raise Sentry::ExternalError, error_info
60
70
  end
61
- rescue SocketError => e
62
- raise Sentry::ExternalError.new(e.message)
71
+ rescue SocketError, *HTTP_ERRORS => e
72
+ on_error if respond_to?(:on_error)
73
+ raise Sentry::ExternalError.new(e&.message)
74
+ end
75
+
76
+ def endpoint
77
+ @dsn.envelope_endpoint
78
+ end
79
+
80
+ def generate_auth_header
81
+ return nil unless @dsn
82
+
83
+ now = Sentry.utc_now.to_i
84
+ fields = {
85
+ 'sentry_version' => PROTOCOL_VERSION,
86
+ 'sentry_client' => USER_AGENT,
87
+ 'sentry_timestamp' => now,
88
+ 'sentry_key' => @dsn.public_key
89
+ }
90
+ fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
91
+ 'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
92
+ end
93
+
94
+ def conn
95
+ server = URI(@dsn.server)
96
+
97
+ # connection respects proxy setting from @transport_configuration, or environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
98
+ # Net::HTTP will automatically read the env vars.
99
+ # See https://ruby-doc.org/3.2.2/stdlibs/net/Net/HTTP.html#class-Net::HTTP-label-Proxies
100
+ connection =
101
+ if proxy = normalize_proxy(@transport_configuration.proxy)
102
+ ::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
103
+ else
104
+ ::Net::HTTP.new(server.hostname, server.port)
105
+ end
106
+
107
+ connection.use_ssl = server.scheme == "https"
108
+ connection.read_timeout = @transport_configuration.timeout
109
+ connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
110
+ connection.open_timeout = @transport_configuration.open_timeout
111
+
112
+ ssl_configuration.each do |key, value|
113
+ connection.send("#{key}=", value)
114
+ end
115
+
116
+ connection
63
117
  end
64
118
 
65
119
  private
@@ -126,28 +180,9 @@ module Sentry
126
180
  @transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
127
181
  end
128
182
 
129
- def conn
130
- server = URI(@dsn.server)
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
-
183
+ # @param proxy [String, URI, Hash] Proxy config value passed into `config.transport`.
184
+ # Accepts either a URI formatted string, URI, or a hash with the `uri`, `user`, and `password` keys.
185
+ # @return [Hash] Normalized proxy config that will be passed into `Net::HTTP`
151
186
  def normalize_proxy(proxy)
152
187
  return proxy unless proxy
153
188
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "zlib"
5
+
6
+ module Sentry
7
+ # Designed to just report events to Spotlight in development.
8
+ class SpotlightTransport < HTTPTransport
9
+ DEFAULT_SIDECAR_URL = "http://localhost:8969/stream"
10
+ MAX_FAILED_REQUESTS = 3
11
+
12
+ def initialize(configuration)
13
+ super
14
+ @sidecar_url = configuration.spotlight.is_a?(String) ? configuration.spotlight : DEFAULT_SIDECAR_URL
15
+ @failed = 0
16
+ @logged = false
17
+
18
+ log_debug("[Spotlight] initialized for url #{@sidecar_url}")
19
+ end
20
+
21
+ def endpoint
22
+ "/stream"
23
+ end
24
+
25
+ def send_data(data)
26
+ if @failed >= MAX_FAILED_REQUESTS
27
+ unless @logged
28
+ log_debug("[Spotlight] disabling because of too many request failures")
29
+ @logged = true
30
+ end
31
+
32
+ return
33
+ end
34
+
35
+ super
36
+ end
37
+
38
+ def on_error
39
+ @failed += 1
40
+ end
41
+
42
+ # Similar to HTTPTransport connection, but does not support Proxy and SSL
43
+ def conn
44
+ sidecar = URI(@sidecar_url)
45
+ connection = ::Net::HTTP.new(sidecar.hostname, sidecar.port, nil)
46
+ connection.use_ssl = false
47
+ connection
48
+ end
49
+ end
50
+ end
@@ -119,18 +119,6 @@ module Sentry
119
119
  !!delay && delay > Time.now
120
120
  end
121
121
 
122
- def generate_auth_header
123
- now = Sentry.utc_now.to_i
124
- fields = {
125
- 'sentry_version' => PROTOCOL_VERSION,
126
- 'sentry_client' => USER_AGENT,
127
- 'sentry_timestamp' => now,
128
- 'sentry_key' => @dsn.public_key
129
- }
130
- fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
131
- 'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
132
- end
133
-
134
122
  def envelope_from_event(event)
135
123
  # Convert to hash
136
124
  event_payload = event.to_hash
@@ -220,3 +208,4 @@ end
220
208
 
221
209
  require "sentry/transport/dummy_transport"
222
210
  require "sentry/transport/http_transport"
211
+ require "sentry/transport/spotlight_transport"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.13.0"
4
+ VERSION = "5.15.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.13.0
4
+ version: 5.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-09 00:00:00.000000000 Z
11
+ date: 2023-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -100,6 +100,7 @@ files:
100
100
  - lib/sentry/transport/configuration.rb
101
101
  - lib/sentry/transport/dummy_transport.rb
102
102
  - lib/sentry/transport/http_transport.rb
103
+ - lib/sentry/transport/spotlight_transport.rb
103
104
  - lib/sentry/utils/argument_checking_helper.rb
104
105
  - lib/sentry/utils/custom_inspection.rb
105
106
  - lib/sentry/utils/encoding_helper.rb