sentry-ruby-core 4.1.6 → 4.5.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/.craft.yml +3 -3
- data/CHANGELOG.md +171 -1
- data/Gemfile +6 -3
- data/README.md +39 -49
- data/lib/sentry-ruby.rb +30 -9
- data/lib/sentry/background_worker.rb +8 -4
- data/lib/sentry/breadcrumb.rb +7 -2
- data/lib/sentry/breadcrumb/sentry_logger.rb +2 -1
- data/lib/sentry/breadcrumb_buffer.rb +3 -2
- data/lib/sentry/client.rb +62 -28
- data/lib/sentry/configuration.rb +80 -17
- data/lib/sentry/event.rb +33 -45
- data/lib/sentry/exceptions.rb +7 -0
- data/lib/sentry/hub.rb +24 -4
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +19 -1
- data/lib/sentry/interfaces/request.rb +10 -10
- data/lib/sentry/interfaces/single_exception.rb +16 -4
- data/lib/sentry/interfaces/stacktrace.rb +9 -26
- data/lib/sentry/interfaces/stacktrace_builder.rb +50 -0
- data/lib/sentry/interfaces/threads.rb +32 -0
- data/lib/sentry/net/http.rb +126 -0
- data/lib/sentry/rack/capture_exceptions.rb +18 -14
- data/lib/sentry/rake.rb +1 -1
- data/lib/sentry/scope.rb +12 -6
- data/lib/sentry/span.rb +21 -4
- data/lib/sentry/transaction.rb +55 -43
- data/lib/sentry/transaction_event.rb +3 -1
- data/lib/sentry/transport.rb +58 -27
- data/lib/sentry/transport/configuration.rb +3 -1
- data/lib/sentry/transport/http_transport.rb +92 -6
- data/lib/sentry/utils/logging_helper.rb +24 -0
- data/lib/sentry/utils/real_ip.rb +13 -7
- data/lib/sentry/version.rb +1 -1
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +1 -1
- metadata +9 -4
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class TransactionEvent < Event
|
5
|
+
TYPE = "transaction"
|
6
|
+
|
5
7
|
ATTRIBUTES = %i(
|
6
8
|
event_id level timestamp start_timestamp
|
7
9
|
release environment server_name modules
|
@@ -17,7 +19,7 @@ module Sentry
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def type
|
20
|
-
|
22
|
+
TYPE
|
21
23
|
end
|
22
24
|
|
23
25
|
def to_hash
|
data/lib/sentry/transport.rb
CHANGED
@@ -3,15 +3,20 @@ require "base64"
|
|
3
3
|
|
4
4
|
module Sentry
|
5
5
|
class Transport
|
6
|
-
PROTOCOL_VERSION = '
|
6
|
+
PROTOCOL_VERSION = '7'
|
7
7
|
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
|
8
8
|
|
9
|
+
include LoggingHelper
|
10
|
+
|
9
11
|
attr_accessor :configuration
|
12
|
+
attr_reader :logger, :rate_limits
|
10
13
|
|
11
14
|
def initialize(configuration)
|
12
15
|
@configuration = configuration
|
16
|
+
@logger = configuration.logger
|
13
17
|
@transport_configuration = configuration.transport
|
14
18
|
@dsn = configuration.dsn
|
19
|
+
@rate_limits = {}
|
15
20
|
end
|
16
21
|
|
17
22
|
def send_data(data, options = {})
|
@@ -19,21 +24,57 @@ module Sentry
|
|
19
24
|
end
|
20
25
|
|
21
26
|
def send_event(event)
|
27
|
+
event_hash = event.to_hash
|
28
|
+
item_type = get_item_type(event_hash)
|
29
|
+
|
22
30
|
unless configuration.sending_allowed?
|
23
|
-
|
31
|
+
log_debug("Envelope [#{item_type}] not sent: #{configuration.error_messages}")
|
32
|
+
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
if is_rate_limited?(item_type)
|
37
|
+
log_info("Envelope [#{item_type}] not sent: rate limiting")
|
38
|
+
|
24
39
|
return
|
25
40
|
end
|
26
41
|
|
27
|
-
encoded_data =
|
42
|
+
encoded_data = encode(event)
|
28
43
|
|
29
44
|
return nil unless encoded_data
|
30
45
|
|
31
46
|
send_data(encoded_data)
|
32
47
|
|
33
48
|
event
|
34
|
-
|
35
|
-
|
36
|
-
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_rate_limited?(item_type)
|
52
|
+
# check category-specific limit
|
53
|
+
category_delay =
|
54
|
+
case item_type
|
55
|
+
when "transaction"
|
56
|
+
@rate_limits["transaction"]
|
57
|
+
else
|
58
|
+
@rate_limits["error"]
|
59
|
+
end
|
60
|
+
|
61
|
+
# check universal limit if not category limit
|
62
|
+
universal_delay = @rate_limits[nil]
|
63
|
+
|
64
|
+
delay =
|
65
|
+
if category_delay && universal_delay
|
66
|
+
if category_delay > universal_delay
|
67
|
+
category_delay
|
68
|
+
else
|
69
|
+
universal_delay
|
70
|
+
end
|
71
|
+
elsif category_delay
|
72
|
+
category_delay
|
73
|
+
else
|
74
|
+
universal_delay
|
75
|
+
end
|
76
|
+
|
77
|
+
!!delay && delay > Time.now
|
37
78
|
end
|
38
79
|
|
39
80
|
def generate_auth_header
|
@@ -48,38 +89,28 @@ module Sentry
|
|
48
89
|
'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
|
49
90
|
end
|
50
91
|
|
51
|
-
def encode(
|
52
|
-
|
53
|
-
|
92
|
+
def encode(event)
|
93
|
+
# Convert to hash
|
94
|
+
event_hash = event.to_hash
|
95
|
+
|
96
|
+
event_id = event_hash[:event_id] || event_hash["event_id"]
|
97
|
+
item_type = get_item_type(event_hash)
|
54
98
|
|
55
99
|
envelope = <<~ENVELOPE
|
56
100
|
{"event_id":"#{event_id}","dsn":"#{configuration.dsn.to_s}","sdk":#{Sentry.sdk_meta.to_json},"sent_at":"#{Sentry.utc_now.iso8601}"}
|
57
|
-
{"type":"#{
|
101
|
+
{"type":"#{item_type}","content_type":"application/json"}
|
58
102
|
#{JSON.generate(event_hash)}
|
59
103
|
ENVELOPE
|
60
104
|
|
105
|
+
log_info("Sending envelope [#{item_type}] #{event_id} to Sentry")
|
106
|
+
|
61
107
|
envelope
|
62
108
|
end
|
63
109
|
|
64
110
|
private
|
65
111
|
|
66
|
-
def
|
67
|
-
|
68
|
-
event_hash = event.to_hash
|
69
|
-
|
70
|
-
event_id = event_hash[:event_id] || event_hash["event_id"]
|
71
|
-
event_type = event_hash[:type] || event_hash["type"]
|
72
|
-
configuration.logger.info(LOGGER_PROGNAME) { "Sending #{event_type} #{event_id} to Sentry" }
|
73
|
-
encode(event_hash)
|
74
|
-
end
|
75
|
-
|
76
|
-
def failed_for_exception(e, event)
|
77
|
-
configuration.logger.warn(LOGGER_PROGNAME) { "Unable to record event with remote Sentry server (#{e.class} - #{e.message}):\n#{e.backtrace[0..10].join("\n")}" }
|
78
|
-
log_not_sending(event)
|
79
|
-
end
|
80
|
-
|
81
|
-
def log_not_sending(event)
|
82
|
-
configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event. Unreported Event: #{Event.get_log_message(event.to_hash)}" }
|
112
|
+
def get_item_type(event_hash)
|
113
|
+
event_hash[:type] || event_hash["type"] || "event"
|
83
114
|
end
|
84
115
|
end
|
85
116
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
module Sentry
|
2
2
|
class Transport
|
3
3
|
class Configuration
|
4
|
-
attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :http_adapter, :faraday_builder,
|
4
|
+
attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :http_adapter, :faraday_builder,
|
5
|
+
:transport_class, :encoding
|
5
6
|
|
6
7
|
def initialize
|
7
8
|
@ssl_verification = true
|
8
9
|
@open_timeout = 1
|
9
10
|
@timeout = 2
|
11
|
+
@encoding = HTTPTransport::GZIP_ENCODING
|
10
12
|
end
|
11
13
|
|
12
14
|
def transport_class=(klass)
|
@@ -1,8 +1,16 @@
|
|
1
1
|
require 'faraday'
|
2
|
+
require 'zlib'
|
2
3
|
|
3
4
|
module Sentry
|
4
5
|
class HTTPTransport < Transport
|
5
|
-
|
6
|
+
GZIP_ENCODING = "gzip"
|
7
|
+
GZIP_THRESHOLD = 1024 * 30
|
8
|
+
CONTENT_TYPE = 'application/x-sentry-envelope'
|
9
|
+
|
10
|
+
DEFAULT_DELAY = 60
|
11
|
+
RETRY_AFTER_HEADER = "retry-after"
|
12
|
+
RATE_LIMIT_HEADER = "x-sentry-rate-limits"
|
13
|
+
|
6
14
|
attr_reader :conn, :adapter
|
7
15
|
|
8
16
|
def initialize(*args)
|
@@ -13,28 +21,106 @@ module Sentry
|
|
13
21
|
end
|
14
22
|
|
15
23
|
def send_data(data)
|
16
|
-
|
24
|
+
encoding = ""
|
25
|
+
|
26
|
+
if should_compress?(data)
|
27
|
+
data = Zlib.gzip(data)
|
28
|
+
encoding = GZIP_ENCODING
|
29
|
+
end
|
30
|
+
|
31
|
+
response = conn.post @endpoint do |req|
|
17
32
|
req.headers['Content-Type'] = CONTENT_TYPE
|
33
|
+
req.headers['Content-Encoding'] = encoding
|
18
34
|
req.headers['X-Sentry-Auth'] = generate_auth_header
|
19
35
|
req.body = data
|
20
36
|
end
|
37
|
+
|
38
|
+
if has_rate_limited_header?(response.headers)
|
39
|
+
handle_rate_limited_response(response.headers)
|
40
|
+
end
|
21
41
|
rescue Faraday::Error => e
|
22
42
|
error_info = e.message
|
23
43
|
|
24
44
|
if e.response
|
25
|
-
|
26
|
-
|
45
|
+
if e.response[:status] == 429
|
46
|
+
handle_rate_limited_response(e.response[:headers])
|
47
|
+
else
|
48
|
+
error_info += "\nbody: #{e.response[:body]}"
|
49
|
+
error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error']
|
50
|
+
end
|
27
51
|
end
|
28
52
|
|
29
|
-
raise Sentry::
|
53
|
+
raise Sentry::ExternalError, error_info
|
30
54
|
end
|
31
55
|
|
32
56
|
private
|
33
57
|
|
58
|
+
def has_rate_limited_header?(headers)
|
59
|
+
headers[RETRY_AFTER_HEADER] || headers[RATE_LIMIT_HEADER]
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_rate_limited_response(headers)
|
63
|
+
rate_limits =
|
64
|
+
if rate_limits = headers[RATE_LIMIT_HEADER]
|
65
|
+
parse_rate_limit_header(rate_limits)
|
66
|
+
elsif retry_after = headers[RETRY_AFTER_HEADER]
|
67
|
+
# although Sentry doesn't send a date string back
|
68
|
+
# based on HTTP specification, this could be a date string (instead of an integer)
|
69
|
+
retry_after = retry_after.to_i
|
70
|
+
retry_after = DEFAULT_DELAY if retry_after == 0
|
71
|
+
|
72
|
+
{ nil => Time.now + retry_after }
|
73
|
+
else
|
74
|
+
{ nil => Time.now + DEFAULT_DELAY }
|
75
|
+
end
|
76
|
+
|
77
|
+
rate_limits.each do |category, limit|
|
78
|
+
if current_limit = @rate_limits[category]
|
79
|
+
if current_limit < limit
|
80
|
+
@rate_limits[category] = limit
|
81
|
+
end
|
82
|
+
else
|
83
|
+
@rate_limits[category] = limit
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_rate_limit_header(rate_limit_header)
|
89
|
+
time = Time.now
|
90
|
+
|
91
|
+
result = {}
|
92
|
+
|
93
|
+
limits = rate_limit_header.split(",")
|
94
|
+
limits.each do |limit|
|
95
|
+
next if limit.nil? || limit.empty?
|
96
|
+
|
97
|
+
begin
|
98
|
+
retry_after, categories = limit.strip.split(":").first(2)
|
99
|
+
retry_after = time + retry_after.to_i
|
100
|
+
categories = categories.split(";")
|
101
|
+
|
102
|
+
if categories.empty?
|
103
|
+
result[nil] = retry_after
|
104
|
+
else
|
105
|
+
categories.each do |category|
|
106
|
+
result[category] = retry_after
|
107
|
+
end
|
108
|
+
end
|
109
|
+
rescue StandardError
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def should_compress?(data)
|
117
|
+
@transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
|
118
|
+
end
|
119
|
+
|
34
120
|
def set_conn
|
35
121
|
server = @dsn.server
|
36
122
|
|
37
|
-
|
123
|
+
log_debug("Sentry HTTP Transport connecting to #{server}")
|
38
124
|
|
39
125
|
Faraday.new(server, :ssl => ssl_configuration, :proxy => @transport_configuration.proxy) do |builder|
|
40
126
|
@transport_configuration.faraday_builder&.call(builder)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sentry
|
2
|
+
module LoggingHelper
|
3
|
+
def log_error(message, exception, debug: false)
|
4
|
+
message = "#{message}: #{exception.message}"
|
5
|
+
message += "\n#{exception.backtrace.join("\n")}" if debug
|
6
|
+
|
7
|
+
logger.error(LOGGER_PROGNAME) do
|
8
|
+
message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def log_info(message)
|
13
|
+
logger.info(LOGGER_PROGNAME) { message }
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_debug(message)
|
17
|
+
logger.debug(LOGGER_PROGNAME) { message }
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_warn(message)
|
21
|
+
logger.warn(LOGGER_PROGNAME) { message }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/sentry/utils/real_ip.rb
CHANGED
@@ -13,8 +13,8 @@ module Sentry
|
|
13
13
|
"fc00::/7", # private IPv6 range fc00::/7
|
14
14
|
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
15
15
|
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
16
|
-
"192.168.0.0/16" # private IPv4 range 192.168.x.x
|
17
|
-
]
|
16
|
+
"192.168.0.0/16", # private IPv4 range 192.168.x.x
|
17
|
+
]
|
18
18
|
|
19
19
|
attr_reader :ip
|
20
20
|
|
@@ -22,12 +22,14 @@ module Sentry
|
|
22
22
|
remote_addr: nil,
|
23
23
|
client_ip: nil,
|
24
24
|
real_ip: nil,
|
25
|
-
forwarded_for: nil
|
25
|
+
forwarded_for: nil,
|
26
|
+
trusted_proxies: []
|
26
27
|
)
|
27
28
|
@remote_addr = remote_addr
|
28
29
|
@client_ip = client_ip
|
29
30
|
@real_ip = real_ip
|
30
31
|
@forwarded_for = forwarded_for
|
32
|
+
@trusted_proxies = (LOCAL_ADDRESSES + Array(trusted_proxies)).map { |proxy| IPAddr.new(proxy.to_s) }.uniq
|
31
33
|
end
|
32
34
|
|
33
35
|
def calculate_ip
|
@@ -37,12 +39,16 @@ module Sentry
|
|
37
39
|
# Could be a CSV list and/or repeated headers that were concatenated.
|
38
40
|
client_ips = ips_from(@client_ip)
|
39
41
|
real_ips = ips_from(@real_ip)
|
40
|
-
|
42
|
+
|
43
|
+
# The first address in this list is the original client, followed by
|
44
|
+
# the IPs of successive proxies. We want to search starting from the end
|
45
|
+
# until we find the first proxy that we do not trust.
|
46
|
+
forwarded_ips = ips_from(@forwarded_for).reverse
|
41
47
|
|
42
48
|
ips = [client_ips, real_ips, forwarded_ips, remote_addr].flatten.compact
|
43
49
|
|
44
50
|
# If every single IP option is in the trusted list, just return REMOTE_ADDR
|
45
|
-
@ip =
|
51
|
+
@ip = filter_trusted_proxy_addresses(ips).first || remote_addr
|
46
52
|
end
|
47
53
|
|
48
54
|
protected
|
@@ -62,8 +68,8 @@ module Sentry
|
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
65
|
-
def
|
66
|
-
ips.reject { |ip|
|
71
|
+
def filter_trusted_proxy_addresses(ips)
|
72
|
+
ips.reject { |ip| @trusted_proxies.any? { |proxy| proxy === ip } }
|
67
73
|
end
|
68
74
|
end
|
69
75
|
end
|
data/lib/sentry/version.rb
CHANGED
data/sentry-ruby-core.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
18
|
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
-
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
20
20
|
|
21
21
|
spec.bindir = "exe"
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
data/sentry-ruby.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
|
|
15
15
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
17
17
|
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
-
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/
|
18
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
19
19
|
|
20
20
|
spec.add_dependency "sentry-ruby-core", Sentry::VERSION
|
21
21
|
spec.add_dependency "faraday", ">= 1.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.1
|
4
|
+
version: 4.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -71,6 +71,7 @@ files:
|
|
71
71
|
- lib/sentry/core_ext/object/duplicable.rb
|
72
72
|
- lib/sentry/dsn.rb
|
73
73
|
- lib/sentry/event.rb
|
74
|
+
- lib/sentry/exceptions.rb
|
74
75
|
- lib/sentry/hub.rb
|
75
76
|
- lib/sentry/integrable.rb
|
76
77
|
- lib/sentry/interface.rb
|
@@ -78,8 +79,11 @@ files:
|
|
78
79
|
- lib/sentry/interfaces/request.rb
|
79
80
|
- lib/sentry/interfaces/single_exception.rb
|
80
81
|
- lib/sentry/interfaces/stacktrace.rb
|
82
|
+
- lib/sentry/interfaces/stacktrace_builder.rb
|
83
|
+
- lib/sentry/interfaces/threads.rb
|
81
84
|
- lib/sentry/linecache.rb
|
82
85
|
- lib/sentry/logger.rb
|
86
|
+
- lib/sentry/net/http.rb
|
83
87
|
- lib/sentry/rack.rb
|
84
88
|
- lib/sentry/rack/capture_exceptions.rb
|
85
89
|
- lib/sentry/rack/deprecations.rb
|
@@ -94,6 +98,7 @@ files:
|
|
94
98
|
- lib/sentry/transport/http_transport.rb
|
95
99
|
- lib/sentry/utils/argument_checking_helper.rb
|
96
100
|
- lib/sentry/utils/exception_cause_chain.rb
|
101
|
+
- lib/sentry/utils/logging_helper.rb
|
97
102
|
- lib/sentry/utils/real_ip.rb
|
98
103
|
- lib/sentry/utils/request_id.rb
|
99
104
|
- lib/sentry/version.rb
|
@@ -105,7 +110,7 @@ licenses:
|
|
105
110
|
metadata:
|
106
111
|
homepage_uri: https://github.com/getsentry/sentry-ruby
|
107
112
|
source_code_uri: https://github.com/getsentry/sentry-ruby
|
108
|
-
changelog_uri: https://github.com/getsentry/sentry-ruby/blob/master/
|
113
|
+
changelog_uri: https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md
|
109
114
|
post_install_message:
|
110
115
|
rdoc_options: []
|
111
116
|
require_paths:
|
@@ -121,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
126
|
- !ruby/object:Gem::Version
|
122
127
|
version: '0'
|
123
128
|
requirements: []
|
124
|
-
rubygems_version: 3.
|
129
|
+
rubygems_version: 3.1.6
|
125
130
|
signing_key:
|
126
131
|
specification_version: 4
|
127
132
|
summary: A gem that provides a client interface for the Sentry error logger
|