sentry-raven 1.1.0 → 3.1.2
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 +5 -5
- data/.craft.yml +19 -0
- data/.scripts/bump-version.rb +5 -0
- data/CHANGELOG.md +703 -0
- data/Gemfile +37 -0
- data/Makefile +3 -0
- data/README.md +116 -18
- data/Rakefile +30 -0
- data/exe/raven +32 -0
- data/lib/raven/backtrace.rb +17 -8
- data/lib/raven/base.rb +45 -194
- data/lib/raven/breadcrumbs/active_support_logger.rb +25 -0
- data/lib/raven/breadcrumbs/logger.rb +3 -0
- data/lib/raven/breadcrumbs/sentry_logger.rb +73 -0
- data/lib/raven/breadcrumbs.rb +76 -0
- data/lib/raven/cli.rb +31 -39
- data/lib/raven/client.rb +45 -32
- data/lib/raven/configuration.rb +427 -130
- data/lib/raven/context.rb +33 -6
- data/lib/raven/core_ext/object/deep_dup.rb +57 -0
- data/lib/raven/core_ext/object/duplicable.rb +153 -0
- data/lib/raven/event.rb +194 -206
- data/lib/raven/helpers/deprecation_helper.rb +17 -0
- data/lib/raven/instance.rb +249 -0
- data/lib/raven/integrations/delayed_job.rb +25 -23
- data/lib/raven/integrations/rack-timeout.rb +22 -0
- data/lib/raven/integrations/rack.rb +40 -24
- data/lib/raven/integrations/rails/active_job.rb +52 -20
- data/lib/raven/integrations/rails/backtrace_cleaner.rb +29 -0
- data/lib/raven/integrations/rails/controller_transaction.rb +13 -0
- data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
- data/lib/raven/integrations/rails.rb +39 -7
- data/lib/raven/integrations/rake.rb +7 -2
- data/lib/raven/integrations/sidekiq/cleanup_middleware.rb +13 -0
- data/lib/raven/integrations/sidekiq/error_handler.rb +38 -0
- data/lib/raven/integrations/sidekiq.rb +6 -48
- data/lib/raven/integrations/tasks.rb +1 -1
- data/lib/raven/interface.rb +25 -0
- data/lib/raven/interfaces/exception.rb +5 -8
- data/lib/raven/interfaces/http.rb +5 -12
- data/lib/raven/interfaces/message.rb +10 -6
- data/lib/raven/interfaces/single_exception.rb +1 -5
- data/lib/raven/interfaces/stack_trace.rb +23 -30
- data/lib/raven/linecache.rb +35 -23
- data/lib/raven/logger.rb +13 -16
- data/lib/raven/processor/cookies.rb +27 -7
- data/lib/raven/processor/http_headers.rb +55 -0
- data/lib/raven/processor/post_data.rb +16 -3
- data/lib/raven/processor/removecircularreferences.rb +12 -8
- data/lib/raven/processor/removestacktrace.rb +17 -6
- data/lib/raven/processor/sanitizedata.rb +92 -37
- data/lib/raven/processor/utf8conversion.rb +39 -14
- data/lib/raven/processor.rb +5 -1
- data/lib/raven/transports/http.rb +31 -22
- data/lib/raven/transports/stdout.rb +20 -0
- data/lib/raven/transports.rb +6 -10
- data/lib/raven/utils/context_filter.rb +42 -0
- data/lib/raven/utils/deep_merge.rb +6 -12
- data/lib/raven/utils/exception_cause_chain.rb +20 -0
- data/lib/raven/utils/real_ip.rb +62 -0
- data/lib/raven/utils/request_id.rb +16 -0
- data/lib/raven/version.rb +2 -1
- data/lib/sentry-raven-without-integrations.rb +6 -1
- data/lib/sentry_raven_without_integrations.rb +1 -0
- data/sentry-raven.gemspec +28 -0
- metadata +44 -127
- data/lib/raven/error.rb +0 -4
- data/lib/raven/interfaces.rb +0 -34
- data/lib/raven/okjson.rb +0 -614
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
module Raven
|
|
2
2
|
class Processor::RemoveCircularReferences < Processor
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
ELISION_STRING = "(...)".freeze
|
|
4
|
+
def process(value, visited = [])
|
|
5
|
+
return ELISION_STRING if visited.include?(value.__id__)
|
|
6
|
+
|
|
7
|
+
visited << value.__id__ if value.is_a?(Array) || value.is_a?(Hash)
|
|
8
|
+
|
|
9
|
+
case value
|
|
10
|
+
when Hash
|
|
11
|
+
!value.frozen? ? value.merge!(value) { |_, v| process v, visited } : value.merge(value) { |_, v| process v, visited }
|
|
12
|
+
when Array
|
|
13
|
+
!value.frozen? ? value.map! { |v| process v, visited } : value.map { |v| process v, visited }
|
|
10
14
|
else
|
|
11
|
-
|
|
15
|
+
value
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
end
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
module Raven
|
|
2
2
|
class Processor::RemoveStacktrace < Processor
|
|
3
|
-
def process(
|
|
4
|
-
if
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
def process(data)
|
|
4
|
+
process_if_symbol_keys(data) if data[:exception]
|
|
5
|
+
process_if_string_keys(data) if data["exception"]
|
|
6
|
+
|
|
7
|
+
data
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def process_if_symbol_keys(data)
|
|
13
|
+
data[:exception][:values].map do |single_exception|
|
|
14
|
+
single_exception.delete(:stacktrace) if single_exception[:stacktrace]
|
|
8
15
|
end
|
|
16
|
+
end
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
def process_if_string_keys(data)
|
|
19
|
+
data["exception"]["values"].map do |single_exception|
|
|
20
|
+
single_exception.delete("stacktrace") if single_exception["stacktrace"]
|
|
21
|
+
end
|
|
11
22
|
end
|
|
12
23
|
end
|
|
13
24
|
end
|
|
@@ -1,63 +1,118 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'json'
|
|
4
|
+
|
|
3
5
|
module Raven
|
|
4
6
|
class Processor::SanitizeData < Processor
|
|
5
|
-
STRING_MASK = '********'.freeze
|
|
6
|
-
INT_MASK = 0
|
|
7
7
|
DEFAULT_FIELDS = %w(authorization password passwd secret ssn social(.*)?sec).freeze
|
|
8
|
-
CREDIT_CARD_RE =
|
|
9
|
-
|
|
8
|
+
CREDIT_CARD_RE = /\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b/.freeze
|
|
9
|
+
QUERY_STRING = ['query_string', :query_string].freeze
|
|
10
|
+
JSON_STARTS_WITH = ["[", "{"].freeze
|
|
10
11
|
|
|
11
|
-
attr_accessor :sanitize_fields, :sanitize_credit_cards
|
|
12
|
+
attr_accessor :sanitize_fields, :sanitize_credit_cards, :sanitize_fields_excluded
|
|
12
13
|
|
|
13
14
|
def initialize(client)
|
|
14
15
|
super
|
|
15
16
|
self.sanitize_fields = client.configuration.sanitize_fields
|
|
16
17
|
self.sanitize_credit_cards = client.configuration.sanitize_credit_cards
|
|
18
|
+
self.sanitize_fields_excluded = client.configuration.sanitize_fields_excluded
|
|
17
19
|
end
|
|
18
20
|
|
|
19
|
-
def process(value)
|
|
20
|
-
value
|
|
21
|
+
def process(value, key = nil)
|
|
22
|
+
case value
|
|
23
|
+
when Hash
|
|
24
|
+
sanitize_hash_value(key, value)
|
|
25
|
+
when Array
|
|
26
|
+
sanitize_array_value(key, value)
|
|
27
|
+
when Integer
|
|
28
|
+
matches_regexes?(key, value.to_s) ? INT_MASK : value
|
|
29
|
+
when String
|
|
30
|
+
sanitize_string_value(key, value)
|
|
31
|
+
else
|
|
32
|
+
value
|
|
33
|
+
end
|
|
21
34
|
end
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
elsif matches_regexes?(k,v)
|
|
37
|
-
STRING_MASK
|
|
38
|
-
else
|
|
39
|
-
v
|
|
40
|
-
end
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# CGI.parse takes our nice UTF-8 strings and converts them back to ASCII,
|
|
39
|
+
# so we have to convert them back, again.
|
|
40
|
+
def utf8_processor
|
|
41
|
+
@utf8_processor ||= Processor::UTF8Conversion.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sanitize_hash_value(key, value)
|
|
45
|
+
if key =~ sensitive_fields
|
|
46
|
+
STRING_MASK
|
|
47
|
+
elsif value.frozen?
|
|
48
|
+
value.merge(value) { |k, v| process v, k }
|
|
41
49
|
else
|
|
42
|
-
v
|
|
50
|
+
value.merge!(value) { |k, v| process v, k }
|
|
43
51
|
end
|
|
44
52
|
end
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
def sanitize_array_value(key, value)
|
|
55
|
+
if value.frozen?
|
|
56
|
+
value.map { |v| process v, key }
|
|
57
|
+
else
|
|
58
|
+
value.map! { |v| process v, key }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def sanitize_string_value(key, value)
|
|
63
|
+
if value =~ sensitive_fields && (json = parse_json_or_nil(value))
|
|
64
|
+
# if this string is actually a json obj, convert and sanitize
|
|
65
|
+
process(json).to_json
|
|
66
|
+
elsif matches_regexes?(key, value)
|
|
67
|
+
STRING_MASK
|
|
68
|
+
elsif QUERY_STRING.include?(key)
|
|
69
|
+
sanitize_query_string(value)
|
|
70
|
+
elsif value =~ sensitive_fields
|
|
71
|
+
sanitize_sensitive_string_content(value)
|
|
72
|
+
else
|
|
73
|
+
value
|
|
74
|
+
end
|
|
75
|
+
end
|
|
47
76
|
|
|
48
77
|
def sanitize_query_string(query_string)
|
|
49
78
|
query_hash = CGI.parse(query_string)
|
|
50
|
-
|
|
79
|
+
sanitized = utf8_processor.process(query_hash)
|
|
80
|
+
processed_query_hash = process(sanitized)
|
|
51
81
|
URI.encode_www_form(processed_query_hash)
|
|
52
82
|
end
|
|
53
83
|
|
|
84
|
+
# this scrubs some sensitive info from the string content. for example:
|
|
85
|
+
#
|
|
86
|
+
# ```
|
|
87
|
+
# unexpected token at '{
|
|
88
|
+
# "role": "admin","password": "Abc@123","foo": "bar"
|
|
89
|
+
# }'
|
|
90
|
+
# ```
|
|
91
|
+
#
|
|
92
|
+
# will become
|
|
93
|
+
#
|
|
94
|
+
# ```
|
|
95
|
+
# unexpected token at '{
|
|
96
|
+
# "role": "admin","password": *******,"foo": "bar"
|
|
97
|
+
# }'
|
|
98
|
+
# ```
|
|
99
|
+
#
|
|
100
|
+
# it's particularly useful in hash or param-parsing related errors
|
|
101
|
+
def sanitize_sensitive_string_content(value)
|
|
102
|
+
value.gsub(/(#{sensitive_fields}['":]\s?(:|=>)?\s?)(".*?"|'.*?')/, '\1' + STRING_MASK)
|
|
103
|
+
end
|
|
104
|
+
|
|
54
105
|
def matches_regexes?(k, v)
|
|
55
|
-
(sanitize_credit_cards && CREDIT_CARD_RE
|
|
56
|
-
|
|
106
|
+
(sanitize_credit_cards && v =~ CREDIT_CARD_RE) ||
|
|
107
|
+
k =~ sensitive_fields
|
|
57
108
|
end
|
|
58
109
|
|
|
59
|
-
def
|
|
60
|
-
@
|
|
110
|
+
def sensitive_fields
|
|
111
|
+
return @sensitive_fields if instance_variable_defined?(:@sensitive_fields)
|
|
112
|
+
|
|
113
|
+
fields = DEFAULT_FIELDS | sanitize_fields
|
|
114
|
+
fields -= sanitize_fields_excluded
|
|
115
|
+
@sensitive_fields = /#{fields.map do |f|
|
|
61
116
|
use_boundary?(f) ? "\\b#{f}\\b" : f
|
|
62
117
|
end.join("|")}/i
|
|
63
118
|
end
|
|
@@ -71,11 +126,11 @@ module Raven
|
|
|
71
126
|
end
|
|
72
127
|
|
|
73
128
|
def parse_json_or_nil(string)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
return unless string.start_with?(*JSON_STARTS_WITH)
|
|
130
|
+
|
|
131
|
+
JSON.parse(string)
|
|
132
|
+
rescue JSON::ParserError, NoMethodError
|
|
133
|
+
nil
|
|
79
134
|
end
|
|
80
135
|
end
|
|
81
136
|
end
|
|
@@ -1,28 +1,53 @@
|
|
|
1
1
|
module Raven
|
|
2
2
|
class Processor::UTF8Conversion < Processor
|
|
3
|
+
# Slightly misnamed - actually just removes any bytes with invalid encoding
|
|
4
|
+
# Previously, our JSON backend required UTF-8. Since we now use the built-in
|
|
5
|
+
# JSON, we can use any encoding, but it must be valid anyway so we can do
|
|
6
|
+
# things like call #match and #slice on strings
|
|
7
|
+
REPLACE = "".freeze
|
|
8
|
+
|
|
3
9
|
def process(value)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
case value
|
|
11
|
+
when Hash
|
|
12
|
+
!value.frozen? ? value.merge!(value) { |_, v| process v } : value.merge(value) { |_, v| process v }
|
|
13
|
+
when Array
|
|
14
|
+
!value.frozen? ? value.map! { |v| process v } : value.map { |v| process v }
|
|
15
|
+
when Exception
|
|
16
|
+
return value if value.message.valid_encoding?
|
|
17
|
+
|
|
18
|
+
clean_exc = value.class.new(remove_invalid_bytes(value.message))
|
|
10
19
|
clean_exc.set_backtrace(value.backtrace)
|
|
11
20
|
clean_exc
|
|
21
|
+
when String
|
|
22
|
+
# Encoding::BINARY / Encoding::ASCII_8BIT is a special binary encoding.
|
|
23
|
+
# valid_encoding? will always return true because it contains all codepoints,
|
|
24
|
+
# so instead we check if it only contains actual ASCII codepoints, and if
|
|
25
|
+
# not we assume it's actually just UTF8 and scrub accordingly.
|
|
26
|
+
if value.encoding == Encoding::BINARY && !value.ascii_only?
|
|
27
|
+
value = value.dup
|
|
28
|
+
value.force_encoding(Encoding::UTF_8)
|
|
29
|
+
end
|
|
30
|
+
return value if value.valid_encoding?
|
|
31
|
+
|
|
32
|
+
remove_invalid_bytes(value)
|
|
12
33
|
else
|
|
13
|
-
|
|
34
|
+
value
|
|
14
35
|
end
|
|
15
36
|
end
|
|
16
37
|
|
|
17
38
|
private
|
|
18
39
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
# Stolen from RSpec
|
|
41
|
+
# https://github.com/rspec/rspec-support/blob/f0af3fd74a94ff7bb700f6ba06dbdc67bba17fbf/lib/rspec/support/encoded_string.rb#L120-L139
|
|
42
|
+
if String.method_defined?(:scrub) # 2.1+
|
|
43
|
+
def remove_invalid_bytes(string)
|
|
44
|
+
string.scrub(REPLACE)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
def remove_invalid_bytes(string)
|
|
48
|
+
string.chars.map do |char|
|
|
49
|
+
char.valid_encoding? ? char : REPLACE
|
|
50
|
+
end.join
|
|
26
51
|
end
|
|
27
52
|
end
|
|
28
53
|
end
|
data/lib/raven/processor.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
require 'faraday'
|
|
2
2
|
|
|
3
|
-
require 'raven/transports'
|
|
4
|
-
require 'raven/error'
|
|
5
|
-
|
|
6
3
|
module Raven
|
|
7
4
|
module Transports
|
|
8
5
|
class HTTP < Transport
|
|
@@ -15,43 +12,55 @@ module Raven
|
|
|
15
12
|
end
|
|
16
13
|
|
|
17
14
|
def send_event(auth_header, data, options = {})
|
|
15
|
+
unless configuration.sending_allowed?
|
|
16
|
+
configuration.logger.debug("Event not sent: #{configuration.error_messages}")
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
18
20
|
project_id = configuration[:project_id]
|
|
19
21
|
path = configuration[:path] + "/"
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
conn.post "#{path}api/#{project_id}/store/" do |req|
|
|
22
24
|
req.headers['Content-Type'] = options[:content_type]
|
|
23
25
|
req.headers['X-Sentry-Auth'] = auth_header
|
|
24
26
|
req.body = data
|
|
25
27
|
end
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
rescue Faraday::Error => e
|
|
29
|
+
error_info = e.message
|
|
30
|
+
if e.response && e.response[:headers]['x-sentry-error']
|
|
31
|
+
error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}"
|
|
32
|
+
end
|
|
33
|
+
raise Raven::Error, error_info
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
private
|
|
31
37
|
|
|
32
38
|
def set_conn
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Raven.logger.debug "Raven HTTP Transport connecting to #{configuration.server}"
|
|
39
|
+
configuration.logger.debug "Raven HTTP Transport connecting to #{configuration.server}"
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
ssl_configuration[:verify] = configuration.ssl_verification
|
|
39
|
-
ssl_configuration[:ca_file] = configuration.ssl_ca_file
|
|
41
|
+
proxy = configuration.public_send(:proxy)
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
:
|
|
44
|
-
|
|
43
|
+
Faraday.new(configuration.server, :ssl => ssl_configuration, :proxy => proxy) do |builder|
|
|
44
|
+
configuration.faraday_builder&.call(builder)
|
|
45
|
+
builder.response :raise_error
|
|
46
|
+
builder.options.merge! faraday_opts
|
|
47
|
+
builder.headers[:user_agent] = "sentry-ruby/#{Raven::VERSION}"
|
|
45
48
|
builder.adapter(*adapter)
|
|
46
49
|
end
|
|
50
|
+
end
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
# TODO: deprecate and replace where possible w/Faraday Builder
|
|
53
|
+
def faraday_opts
|
|
54
|
+
[:timeout, :open_timeout].each_with_object({}) do |opt, memo|
|
|
55
|
+
memo[opt] = configuration.public_send(opt) if configuration.public_send(opt)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
def ssl_configuration
|
|
60
|
+
(configuration.ssl || {}).merge(
|
|
61
|
+
:verify => configuration.ssl_verification,
|
|
62
|
+
:ca_file => configuration.ssl_ca_file
|
|
63
|
+
)
|
|
55
64
|
end
|
|
56
65
|
end
|
|
57
66
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Raven
|
|
2
|
+
module Transports
|
|
3
|
+
class Stdout < Transport
|
|
4
|
+
attr_accessor :events
|
|
5
|
+
|
|
6
|
+
def initialize(*)
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def send_event(_auth_header, data, _options = {})
|
|
11
|
+
unless configuration.sending_allowed?
|
|
12
|
+
logger.debug("Event not sent: #{configuration.error_messages}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
$stdout.puts data
|
|
16
|
+
$stdout.flush
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/raven/transports.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require 'raven/error'
|
|
2
|
-
|
|
3
1
|
module Raven
|
|
4
2
|
module Transports
|
|
5
3
|
class Transport
|
|
@@ -9,15 +7,13 @@ module Raven
|
|
|
9
7
|
@configuration = configuration
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
def send_event #(auth_header, data, options = {})
|
|
13
|
-
raise NotImplementedError
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
protected
|
|
17
|
-
|
|
18
|
-
def verify_configuration
|
|
19
|
-
configuration.verify!
|
|
10
|
+
def send_event # (auth_header, data, options = {})
|
|
11
|
+
raise NotImplementedError, 'Abstract method not implemented'
|
|
20
12
|
end
|
|
21
13
|
end
|
|
22
14
|
end
|
|
23
15
|
end
|
|
16
|
+
|
|
17
|
+
require "raven/transports/dummy"
|
|
18
|
+
require "raven/transports/http"
|
|
19
|
+
require "raven/transports/stdout"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Raven
|
|
2
|
+
module Utils
|
|
3
|
+
module ContextFilter
|
|
4
|
+
class << self
|
|
5
|
+
ACTIVEJOB_RESERVED_PREFIX_REGEX = /^_aj_/.freeze
|
|
6
|
+
HAS_GLOBALID = const_defined?('GlobalID')
|
|
7
|
+
|
|
8
|
+
# Once an ActiveJob is queued, ActiveRecord references get serialized into
|
|
9
|
+
# some internal reserved keys, such as _aj_globalid.
|
|
10
|
+
#
|
|
11
|
+
# The problem is, if this job in turn gets queued back into ActiveJob with
|
|
12
|
+
# these magic reserved keys, ActiveJob will throw up and error. We want to
|
|
13
|
+
# capture these and mutate the keys so we can sanely report it.
|
|
14
|
+
def filter_context(context)
|
|
15
|
+
case context
|
|
16
|
+
when Array
|
|
17
|
+
context.map { |arg| filter_context(arg) }
|
|
18
|
+
when Hash
|
|
19
|
+
Hash[context.map { |key, value| filter_context_hash(key, value) }]
|
|
20
|
+
else
|
|
21
|
+
format_globalid(context)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def filter_context_hash(key, value)
|
|
28
|
+
key = key.to_s.sub(ACTIVEJOB_RESERVED_PREFIX_REGEX, "") if key.match(ACTIVEJOB_RESERVED_PREFIX_REGEX)
|
|
29
|
+
[key, filter_context(value)]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def format_globalid(context)
|
|
33
|
+
if HAS_GLOBALID && context.is_a?(GlobalID)
|
|
34
|
+
context.to_s
|
|
35
|
+
else
|
|
36
|
+
context
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -7,21 +7,15 @@ module Raven
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def self.deep_merge!(hash, other_hash, &block)
|
|
10
|
-
other_hash
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
hash.merge!(other_hash) do |key, this_val, other_val|
|
|
11
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
|
12
|
+
deep_merge(this_val, other_val, &block)
|
|
13
|
+
elsif block_given?
|
|
14
|
+
block.call(key, this_val, other_val)
|
|
15
15
|
else
|
|
16
|
-
|
|
17
|
-
block.call(current_key, this_value, other_value)
|
|
18
|
-
else
|
|
19
|
-
other_value
|
|
20
|
-
end
|
|
16
|
+
other_val
|
|
21
17
|
end
|
|
22
18
|
end
|
|
23
|
-
|
|
24
|
-
hash
|
|
25
19
|
end
|
|
26
20
|
end
|
|
27
21
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Raven
|
|
2
|
+
module Utils
|
|
3
|
+
module ExceptionCauseChain
|
|
4
|
+
def self.exception_to_array(exception)
|
|
5
|
+
if exception.respond_to?(:cause) && exception.cause
|
|
6
|
+
exceptions = [exception]
|
|
7
|
+
while exception.cause
|
|
8
|
+
exception = exception.cause
|
|
9
|
+
break if exceptions.any? { |e| e.object_id == exception.object_id }
|
|
10
|
+
|
|
11
|
+
exceptions << exception
|
|
12
|
+
end
|
|
13
|
+
exceptions
|
|
14
|
+
else
|
|
15
|
+
[exception]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'ipaddr'
|
|
2
|
+
|
|
3
|
+
# Based on ActionDispatch::RemoteIp. All security-related precautions from that
|
|
4
|
+
# middleware have been removed, because the Event IP just needs to be accurate,
|
|
5
|
+
# and spoofing an IP here only makes data inaccurate, not insecure. Don't re-use
|
|
6
|
+
# this module if you have to *trust* the IP address.
|
|
7
|
+
module Raven
|
|
8
|
+
module Utils
|
|
9
|
+
class RealIp
|
|
10
|
+
LOCAL_ADDRESSES = [
|
|
11
|
+
"127.0.0.1", # localhost IPv4
|
|
12
|
+
"::1", # localhost IPv6
|
|
13
|
+
"fc00::/7", # private IPv6 range fc00::/7
|
|
14
|
+
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
|
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
|
+
].map { |proxy| IPAddr.new(proxy) }
|
|
18
|
+
|
|
19
|
+
attr_accessor :ip, :ip_addresses
|
|
20
|
+
|
|
21
|
+
def initialize(ip_addresses)
|
|
22
|
+
self.ip_addresses = ip_addresses
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def calculate_ip
|
|
26
|
+
# CGI environment variable set by Rack
|
|
27
|
+
remote_addr = ips_from(ip_addresses[:remote_addr]).last
|
|
28
|
+
|
|
29
|
+
# Could be a CSV list and/or repeated headers that were concatenated.
|
|
30
|
+
client_ips = ips_from(ip_addresses[:client_ip])
|
|
31
|
+
real_ips = ips_from(ip_addresses[:real_ip])
|
|
32
|
+
forwarded_ips = ips_from(ip_addresses[:forwarded_for])
|
|
33
|
+
|
|
34
|
+
ips = [client_ips, real_ips, forwarded_ips, remote_addr].flatten.compact
|
|
35
|
+
|
|
36
|
+
# If every single IP option is in the trusted list, just return REMOTE_ADDR
|
|
37
|
+
self.ip = filter_local_addresses(ips).first || remote_addr
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
protected
|
|
41
|
+
|
|
42
|
+
def ips_from(header)
|
|
43
|
+
# Split the comma-separated list into an array of strings
|
|
44
|
+
ips = header ? header.strip.split(/[,\s]+/) : []
|
|
45
|
+
ips.select do |ip|
|
|
46
|
+
begin
|
|
47
|
+
# Only return IPs that are valid according to the IPAddr#new method
|
|
48
|
+
range = IPAddr.new(ip).to_range
|
|
49
|
+
# we want to make sure nobody is sneaking a netmask in
|
|
50
|
+
range.begin == range.end
|
|
51
|
+
rescue ArgumentError
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def filter_local_addresses(ips)
|
|
58
|
+
ips.reject { |ip| LOCAL_ADDRESSES.any? { |proxy| proxy === ip } }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Raven
|
|
2
|
+
module Utils
|
|
3
|
+
module RequestId
|
|
4
|
+
REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
|
|
5
|
+
|
|
6
|
+
# Request ID based on ActionDispatch::RequestId
|
|
7
|
+
def self.read_from(env_hash)
|
|
8
|
+
REQUEST_ID_HEADERS.each do |key|
|
|
9
|
+
request_id = env_hash[key]
|
|
10
|
+
return request_id if request_id
|
|
11
|
+
end
|
|
12
|
+
nil
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/raven/version.rb
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'raven/base'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
|
2
|
+
require 'raven/version'
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.name = "sentry-raven"
|
|
6
|
+
gem.authors = ["Sentry Team"]
|
|
7
|
+
gem.description = gem.summary = "A gem that provides a client interface for the Sentry error logger"
|
|
8
|
+
gem.email = "accounts@sentry.io"
|
|
9
|
+
gem.license = 'Apache-2.0'
|
|
10
|
+
gem.homepage = "https://github.com/getsentry/raven-ruby"
|
|
11
|
+
|
|
12
|
+
gem.version = Raven::VERSION
|
|
13
|
+
gem.platform = Gem::Platform::RUBY
|
|
14
|
+
gem.required_ruby_version = '>= 2.3'
|
|
15
|
+
gem.extra_rdoc_files = ["README.md", "LICENSE"]
|
|
16
|
+
gem.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
|
|
17
|
+
gem.bindir = "exe"
|
|
18
|
+
gem.executables = "raven"
|
|
19
|
+
|
|
20
|
+
gem.add_dependency "faraday", ">= 1.0"
|
|
21
|
+
|
|
22
|
+
gem.post_install_message = <<~EOS
|
|
23
|
+
`sentry-raven` is deprecated! Please migrate to `sentry-ruby`
|
|
24
|
+
|
|
25
|
+
See https://docs.sentry.io/platforms/ruby/migration for the migration guide.
|
|
26
|
+
|
|
27
|
+
EOS
|
|
28
|
+
end
|