airbrake-ruby 2.5.1 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby/notice.rb +3 -1
- data/lib/airbrake-ruby/truncator.rb +51 -71
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/fixtures/project_root/code.rb +1 -1
- data/spec/truncator_spec.rb +149 -372
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10cd1c4c112ec8325d9e800128cb3598ecca5e79
|
4
|
+
data.tar.gz: e06b612001f91e0a6e928250d735fc57b2d9a313
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39edba81094187bde03fd1f5fc916255737dde0fabcd0f5c720075c22c97d914c602bdda046669db3b48680ec0bae798bf4225a4645d4a08e450171de76a34d0
|
7
|
+
data.tar.gz: 3d0b4de47de97f5cc9bd425a70aa1128449df90329f33e93f843c6729571cd2ca38d371477fa98ea46d380c116dd7bf20a670f230bef59eeb34d8fd72d1b0f23
|
data/lib/airbrake-ruby/notice.rb
CHANGED
@@ -180,7 +180,9 @@ module Airbrake
|
|
180
180
|
end
|
181
181
|
|
182
182
|
def truncate
|
183
|
-
TRUNCATABLE_KEYS.each
|
183
|
+
TRUNCATABLE_KEYS.each do |key|
|
184
|
+
@payload[key] = @truncator.truncate(@payload[key])
|
185
|
+
end
|
184
186
|
|
185
187
|
new_max_size = @truncator.reduce_max_size
|
186
188
|
if new_max_size == 0
|
@@ -1,55 +1,34 @@
|
|
1
1
|
module Airbrake
|
2
|
-
##
|
3
2
|
# This class is responsible for truncation of too big objects. Mainly, you
|
4
3
|
# should use it for simple objects such as strings, hashes, & arrays.
|
5
4
|
#
|
6
5
|
# @api private
|
7
6
|
# @since v1.0.0
|
8
7
|
class Truncator
|
9
|
-
##
|
10
8
|
# @return [Hash] the options for +String#encode+
|
11
9
|
ENCODING_OPTIONS = { invalid: :replace, undef: :replace }.freeze
|
12
10
|
|
13
|
-
##
|
14
11
|
# @return [String] the temporary encoding to be used when fixing invalid
|
15
12
|
# strings with +ENCODING_OPTIONS+
|
16
13
|
TEMP_ENCODING = 'utf-16'.freeze
|
17
14
|
|
18
|
-
##
|
19
15
|
# @param [Integer] max_size maximum size of hashes, arrays and strings
|
20
16
|
def initialize(max_size)
|
21
17
|
@max_size = max_size
|
22
18
|
end
|
23
19
|
|
24
|
-
|
25
|
-
# Performs deep truncation of arrays, hashes and sets. Uses a
|
20
|
+
# Performs deep truncation of arrays, hashes, sets & strings. Uses a
|
26
21
|
# placeholder for recursive objects (`[Circular]`).
|
27
22
|
#
|
28
|
-
# @param [
|
29
|
-
# @param [
|
30
|
-
# @return [
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
return seen[object] if seen[object]
|
35
|
-
|
36
|
-
seen[object] = '[Circular]'.freeze
|
37
|
-
truncated =
|
38
|
-
if object.is_a?(Hash)
|
39
|
-
truncate_hash(object, seen)
|
40
|
-
elsif object.is_a?(Array)
|
41
|
-
truncate_array(object, seen)
|
42
|
-
elsif object.is_a?(Set)
|
43
|
-
truncate_set(object, seen)
|
44
|
-
else
|
45
|
-
raise Airbrake::Error,
|
46
|
-
"cannot truncate object: #{object} (#{object.class})"
|
47
|
-
end
|
48
|
-
seen[object] = truncated
|
23
|
+
# @param [Object] object The object to truncate
|
24
|
+
# @param [Set] seen The cache that helps to detect recursion
|
25
|
+
# @return [Object] truncated object
|
26
|
+
def truncate(object, seen = Set.new)
|
27
|
+
return '[Circular]'.freeze if seen.include?(object)
|
28
|
+
truncate_object(object, seen << object)
|
49
29
|
end
|
50
30
|
|
51
|
-
|
52
|
-
# Reduces maximum allowed size of the truncated object.
|
31
|
+
# Reduces maximum allowed size of hashes, arrays, sets & strings by half.
|
53
32
|
# @return [Integer] current +max_size+ value
|
54
33
|
def reduce_max_size
|
55
34
|
@max_size /= 2
|
@@ -57,67 +36,68 @@ module Airbrake
|
|
57
36
|
|
58
37
|
private
|
59
38
|
|
60
|
-
def
|
61
|
-
case
|
62
|
-
when
|
63
|
-
|
64
|
-
when
|
65
|
-
|
66
|
-
when Numeric, TrueClass, FalseClass, Symbol, NilClass
|
67
|
-
val
|
39
|
+
def truncate_object(object, seen)
|
40
|
+
case object
|
41
|
+
when Hash then truncate_hash(object, seen)
|
42
|
+
when Array then truncate_array(object, seen)
|
43
|
+
when Set then truncate_set(object, seen)
|
44
|
+
when String then truncate_string(object)
|
45
|
+
when Numeric, TrueClass, FalseClass, Symbol, NilClass then object
|
68
46
|
else
|
69
|
-
|
70
|
-
begin
|
71
|
-
val.to_json
|
72
|
-
rescue *Notice::JSON_EXCEPTIONS
|
73
|
-
val.to_s
|
74
|
-
end
|
75
|
-
truncate_string(stringified_val)
|
47
|
+
truncate_string(stringify_object(object))
|
76
48
|
end
|
77
49
|
end
|
78
50
|
|
79
51
|
def truncate_string(str)
|
80
|
-
|
81
|
-
return
|
82
|
-
|
52
|
+
fixed_str = replace_invalid_characters(str)
|
53
|
+
return fixed_str if fixed_str.length <= @max_size
|
54
|
+
(fixed_str.slice(0, @max_size) + '[Truncated]').freeze
|
83
55
|
end
|
84
56
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# @return [String] a UTF-8 encoded string
|
90
|
-
# @note This method mutates +str+ unless it's frozen,
|
91
|
-
# in which case it creates a duplicate
|
92
|
-
# @see https://github.com/flori/json/commit/3e158410e81f94dbbc3da6b7b35f4f64983aa4e3
|
93
|
-
def replace_invalid_characters!(str)
|
94
|
-
encoding = str.encoding
|
95
|
-
utf8_string = (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII)
|
96
|
-
return str if utf8_string && str.valid_encoding?
|
97
|
-
|
98
|
-
str = str.dup if str.frozen?
|
99
|
-
str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
|
100
|
-
str.encode!('utf-8', ENCODING_OPTIONS)
|
57
|
+
def stringify_object(object)
|
58
|
+
object.to_json
|
59
|
+
rescue *Notice::JSON_EXCEPTIONS
|
60
|
+
object.to_s
|
101
61
|
end
|
102
62
|
|
103
63
|
def truncate_hash(hash, seen)
|
64
|
+
truncated_hash = {}
|
104
65
|
hash.each_with_index do |(key, val), idx|
|
105
|
-
if idx
|
106
|
-
|
107
|
-
else
|
108
|
-
hash.delete(key)
|
109
|
-
end
|
66
|
+
break if idx + 1 > @max_size
|
67
|
+
truncated_hash[key] = truncate(val, seen)
|
110
68
|
end
|
69
|
+
|
70
|
+
truncated_hash.freeze
|
111
71
|
end
|
112
72
|
|
113
73
|
def truncate_array(array, seen)
|
114
|
-
array.slice(0, @max_size).map! { |
|
74
|
+
array.slice(0, @max_size).map! { |elem| truncate(elem, seen) }.freeze
|
115
75
|
end
|
116
76
|
|
117
77
|
def truncate_set(set, seen)
|
118
|
-
|
119
|
-
|
78
|
+
truncated_set = Set.new
|
79
|
+
|
80
|
+
set.each do |elem|
|
81
|
+
truncated_set << truncate(elem, seen)
|
82
|
+
break if truncated_set.size >= @max_size
|
120
83
|
end
|
84
|
+
|
85
|
+
truncated_set.freeze
|
86
|
+
end
|
87
|
+
|
88
|
+
# Replaces invalid characters in a string with arbitrary encoding.
|
89
|
+
#
|
90
|
+
# @param [String] str The string to replace characters
|
91
|
+
# @return [String] a UTF-8 encoded string
|
92
|
+
# @see https://github.com/flori/json/commit/3e158410e81f94dbbc3da6b7b35f4f64983aa4e3
|
93
|
+
def replace_invalid_characters(str)
|
94
|
+
encoding = str.encoding
|
95
|
+
utf8_string = (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII)
|
96
|
+
return str if utf8_string && str.valid_encoding?
|
97
|
+
|
98
|
+
temp_str = str.dup
|
99
|
+
temp_str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
|
100
|
+
temp_str.encode!('utf-8', ENCODING_OPTIONS)
|
121
101
|
end
|
122
102
|
end
|
123
103
|
end
|
@@ -180,7 +180,7 @@ module Airbrake
|
|
180
180
|
end
|
181
181
|
|
182
182
|
def truncate
|
183
|
-
TRUNCATABLE_KEYS.each { |key| @truncator.
|
183
|
+
TRUNCATABLE_KEYS.each { |key| @truncator.truncate(self[key]) }
|
184
184
|
|
185
185
|
new_max_size = @truncator.reduce_max_size
|
186
186
|
if new_max_size == 0
|
data/spec/truncator_spec.rb
CHANGED
@@ -1,437 +1,214 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Airbrake::Truncator do
|
4
|
-
|
5
|
-
|
6
|
-
let(:max_len) { max_size + truncated_len }
|
7
|
-
|
8
|
-
before do
|
9
|
-
@truncator = described_class.new(max_size)
|
4
|
+
def multiply_by_2_max_len(chr)
|
5
|
+
chr * 2 * max_len
|
10
6
|
end
|
11
7
|
|
12
|
-
describe "#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
before do
|
19
|
-
backtrace = Array.new(size) do
|
20
|
-
{ file: 'foo.rb', line: 23, function: '<main>' }
|
21
|
-
end
|
22
|
-
|
23
|
-
@error = error.merge(backtrace: backtrace)
|
24
|
-
described_class.new(max_size).truncate_object(@error)
|
25
|
-
end
|
26
|
-
|
27
|
-
context "when long" do
|
28
|
-
let(:size) { 2003 }
|
8
|
+
describe "#truncate" do
|
9
|
+
let(:max_size) { 3 }
|
10
|
+
let(:truncated) { '[Truncated]' }
|
11
|
+
let(:max_len) { max_size + truncated.length }
|
29
12
|
|
30
|
-
|
31
|
-
expect(@error[:backtrace].size).to eq(1000)
|
32
|
-
end
|
33
|
-
end
|
13
|
+
subject { described_class.new(max_size).truncate(object) }
|
34
14
|
|
35
|
-
|
36
|
-
|
15
|
+
context "given a frozen string" do
|
16
|
+
let(:object) { multiply_by_2_max_len('a') }
|
37
17
|
|
38
|
-
|
39
|
-
|
40
|
-
|
18
|
+
it "returns a new truncated frozen string" do
|
19
|
+
expect(subject.length).to eq(max_len)
|
20
|
+
expect(subject).to be_frozen
|
41
21
|
end
|
42
22
|
end
|
43
23
|
|
44
|
-
|
45
|
-
let(:
|
46
|
-
{
|
24
|
+
context "given a frozen hash of strings" do
|
25
|
+
let(:object) do
|
26
|
+
{
|
27
|
+
banana: multiply_by_2_max_len('a'),
|
28
|
+
kiwi: multiply_by_2_max_len('b'),
|
29
|
+
strawberry: 'c',
|
30
|
+
shrimp: 'd'
|
31
|
+
}.freeze
|
47
32
|
end
|
48
33
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
34
|
+
it "returns a new truncated frozen hash" do
|
35
|
+
expect(subject.size).to eq(max_size)
|
36
|
+
expect(subject).to be_frozen
|
53
37
|
|
54
|
-
|
55
|
-
|
38
|
+
expect(subject).to eq(
|
39
|
+
banana: 'aaa[Truncated]', kiwi: 'bbb[Truncated]', strawberry: 'c'
|
40
|
+
)
|
41
|
+
expect(subject[:banana]).to be_frozen
|
42
|
+
expect(subject[:kiwi]).to be_frozen
|
43
|
+
expect(subject[:strawberry]).not_to be_frozen
|
44
|
+
end
|
45
|
+
end
|
56
46
|
|
57
|
-
|
58
|
-
|
59
|
-
|
47
|
+
context "given a frozen array of strings" do
|
48
|
+
let(:object) do
|
49
|
+
[
|
50
|
+
multiply_by_2_max_len('a'),
|
51
|
+
'b',
|
52
|
+
multiply_by_2_max_len('c'),
|
53
|
+
'd'
|
54
|
+
].freeze
|
60
55
|
end
|
61
56
|
|
62
|
-
|
63
|
-
|
64
|
-
|
57
|
+
it "returns a new truncated frozen array" do
|
58
|
+
expect(subject.size).to eq(max_size)
|
59
|
+
expect(subject).to be_frozen
|
65
60
|
|
66
|
-
|
67
|
-
|
68
|
-
|
61
|
+
expect(subject).to eq(['aaa[Truncated]', 'b', 'ccc[Truncated]'])
|
62
|
+
expect(subject[0]).to be_frozen
|
63
|
+
expect(subject[1]).not_to be_frozen
|
64
|
+
expect(subject[2]).to be_frozen
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
72
|
-
|
73
|
-
let(:
|
74
|
-
|
68
|
+
context "given a frozen set of strings" do
|
69
|
+
let(:object) do
|
70
|
+
Set.new([
|
71
|
+
multiply_by_2_max_len('a'),
|
72
|
+
'b',
|
73
|
+
multiply_by_2_max_len('c'),
|
74
|
+
'd'
|
75
|
+
]).freeze
|
75
76
|
end
|
76
77
|
|
77
|
-
it "
|
78
|
-
|
79
|
-
expect(
|
78
|
+
it "returns a new truncated frozen array" do
|
79
|
+
expect(subject.size).to eq(max_size)
|
80
|
+
expect(subject).to be_frozen
|
81
|
+
|
82
|
+
expect(subject).to eq(
|
83
|
+
Set.new(['aaa[Truncated]', 'b', 'ccc[Truncated]'])
|
84
|
+
)
|
85
|
+
expect(subject).to be_frozen
|
80
86
|
end
|
81
87
|
end
|
82
88
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
+
context "given an arbitrary frozen object that responds to #to_json" do
|
90
|
+
let(:object) do
|
91
|
+
obj = Object.new
|
92
|
+
def obj.to_json
|
93
|
+
'{"object":"shrimp"}'
|
89
94
|
end
|
95
|
+
obj.freeze
|
96
|
+
end
|
90
97
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
@truncator.truncate_object(params)
|
98
|
+
it "converts the object to truncated JSON" do
|
99
|
+
expect(subject.length).to eq(max_len)
|
100
|
+
expect(subject).to be_frozen
|
96
101
|
|
97
|
-
|
98
|
-
expect(params[0].size).to eq(1000)
|
99
|
-
end
|
102
|
+
expect(subject).to eq('{"o[Truncated]')
|
100
103
|
end
|
101
104
|
end
|
102
105
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
it "truncates all the sets to the max allowed size" do
|
112
|
-
expect(params[:bingo].size).to eq(4124)
|
113
|
-
expect(params[:bingo].to_a[0].size).to eq(4123)
|
114
|
-
|
115
|
-
@truncator.truncate_object(params)
|
116
|
-
|
117
|
-
expect(params[:bingo].size).to eq(1000)
|
118
|
-
expect(params[:bingo].to_a[0].size).to eq(1000)
|
119
|
-
end
|
106
|
+
context "given an arbitrary object that doesn't respond to #to_json" do
|
107
|
+
let(:object) do
|
108
|
+
obj = Object.new
|
109
|
+
allow(obj).to receive(:to_json).
|
110
|
+
and_raise(Airbrake::Notice::JSON_EXCEPTIONS.first)
|
111
|
+
obj
|
120
112
|
end
|
121
113
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
a << a << :bango
|
126
|
-
{ bingo: a }
|
127
|
-
end
|
128
|
-
|
129
|
-
it "prevents recursion" do
|
130
|
-
@truncator.truncate_object(params)
|
131
|
-
|
132
|
-
expect(params).to eq(bingo: Set.new(['[Circular]', :bango]))
|
133
|
-
end
|
114
|
+
it "converts the object to a truncated string" do
|
115
|
+
expect(subject.length).to eq(max_len)
|
116
|
+
expect(subject).to eq('#<O[Truncated]')
|
134
117
|
end
|
135
118
|
end
|
136
119
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
row = (0...4124).each_cons(2)
|
141
|
-
{ bingo: row.to_a.unshift(row.to_a) }
|
142
|
-
end
|
143
|
-
|
144
|
-
it "truncates all the arrays to the max allowed size" do
|
145
|
-
expect(params[:bingo].size).to eq(4124)
|
146
|
-
expect(params[:bingo][0].size).to eq(4123)
|
147
|
-
|
148
|
-
@truncator.truncate_object(params)
|
149
|
-
|
150
|
-
expect(params[:bingo].size).to eq(1000)
|
151
|
-
expect(params[:bingo][0].size).to eq(1000)
|
152
|
-
end
|
120
|
+
shared_examples 'self returning objects' do |object|
|
121
|
+
it "returns the passed object" do
|
122
|
+
expect(described_class.new(max_size).truncate(object)).to eql(object)
|
153
123
|
end
|
154
124
|
end
|
155
125
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
{ bingo: 'bango' * 2000, bongo: 'bish', bash: 'bosh' * 1000 }
|
160
|
-
end
|
161
|
-
|
162
|
-
it "truncates only long strings" do
|
163
|
-
expect(params[:bingo].length).to eq(10_000)
|
164
|
-
expect(params[:bongo].length).to eq(4)
|
165
|
-
expect(params[:bash].length).to eq(4000)
|
166
|
-
|
167
|
-
@truncator.truncate_object(params)
|
126
|
+
[1, true, false, :symbol, nil].each do |object|
|
127
|
+
include_examples 'self returning objects', object
|
128
|
+
end
|
168
129
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
130
|
+
context "given a recursive array" do
|
131
|
+
let(:object) do
|
132
|
+
a = %w[aaaaa bb]
|
133
|
+
a << a
|
134
|
+
a << 'c'
|
135
|
+
a
|
173
136
|
end
|
174
137
|
|
175
|
-
|
176
|
-
|
177
|
-
let(:params) do
|
178
|
-
{ bingo: ['foo', 'bango' * 2000, 'bar', 'piyo' * 2000, 'baz'],
|
179
|
-
bongo: 'bish',
|
180
|
-
bash: 'bosh' * 1000 }
|
181
|
-
end
|
182
|
-
|
183
|
-
it "truncates long strings in the array, but not short ones" do
|
184
|
-
expect(params[:bingo].map(&:length)).to eq([3, 10_000, 3, 8_000, 3])
|
185
|
-
expect(params[:bongo].length).to eq(4)
|
186
|
-
expect(params[:bash].length).to eq(4000)
|
187
|
-
|
188
|
-
@truncator.truncate_object(params)
|
189
|
-
|
190
|
-
expect(params[:bingo].map(&:length)).to eq([3, max_len, 3, max_len, 3])
|
191
|
-
expect(params[:bongo].length).to eq(4)
|
192
|
-
expect(params[:bash].length).to eq(max_len)
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
context "of short strings" do
|
197
|
-
let(:params) do
|
198
|
-
{ bingo: %w[foo bar baz], bango: 'bongo', bish: 'bash' }
|
199
|
-
end
|
200
|
-
|
201
|
-
it "truncates long strings in the array, but not short ones" do
|
202
|
-
@truncator.truncate_object(params)
|
203
|
-
expect(params).
|
204
|
-
to eq(bingo: %w[foo bar baz], bango: 'bongo', bish: 'bash')
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
context "of hashes" do
|
209
|
-
context "with long strings" do
|
210
|
-
let(:params) do
|
211
|
-
{ bingo: [{}, { bango: 'bongo', hoge: { fuga: 'piyo' * 2000 } }],
|
212
|
-
bish: 'bash',
|
213
|
-
bosh: 'foo' }
|
214
|
-
end
|
215
|
-
|
216
|
-
it "truncates the long string" do
|
217
|
-
expect(params[:bingo][1][:hoge][:fuga].length).to eq(8000)
|
218
|
-
|
219
|
-
@truncator.truncate_object(params)
|
220
|
-
|
221
|
-
expect(params[:bingo][0]).to eq({})
|
222
|
-
expect(params[:bingo][1][:bango]).to eq('bongo')
|
223
|
-
expect(params[:bingo][1][:hoge][:fuga].length).to eq(max_len)
|
224
|
-
expect(params[:bish]).to eq('bash')
|
225
|
-
expect(params[:bosh]).to eq('foo')
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
context "with short strings" do
|
230
|
-
let(:params) do
|
231
|
-
{ bingo: [{}, { bango: 'bongo', hoge: { fuga: 'piyo' } }],
|
232
|
-
bish: 'bash',
|
233
|
-
bosh: 'foo' }
|
234
|
-
end
|
235
|
-
|
236
|
-
it "doesn't truncate the short string" do
|
237
|
-
expect(params[:bingo][1][:hoge][:fuga].length).to eq(4)
|
238
|
-
|
239
|
-
@truncator.truncate_object(params)
|
240
|
-
|
241
|
-
expect(params[:bingo][0]).to eq({})
|
242
|
-
expect(params[:bingo][1][:bango]).to eq('bongo')
|
243
|
-
expect(params[:bingo][1][:hoge][:fuga].length).to eq(4)
|
244
|
-
expect(params[:bish]).to eq('bash')
|
245
|
-
expect(params[:bosh]).to eq('foo')
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
context "with strings that equal to max_size" do
|
250
|
-
before do
|
251
|
-
@truncator = described_class.new(max_size)
|
252
|
-
end
|
253
|
-
|
254
|
-
let(:params) { { unicode: '1111' } }
|
255
|
-
let(:max_size) { params[:unicode].size }
|
256
|
-
|
257
|
-
it "is doesn't truncate the string" do
|
258
|
-
@truncator.truncate_object(params)
|
259
|
-
|
260
|
-
expect(params[:unicode].length).to eq(max_size)
|
261
|
-
expect(params[:unicode]).to match(/\A1{#{max_size}}\z/)
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
context "of recursive hashes" do
|
267
|
-
let(:params) do
|
268
|
-
a = { bingo: {} }
|
269
|
-
a[:bingo][:bango] = a
|
270
|
-
end
|
271
|
-
|
272
|
-
it "prevents recursion" do
|
273
|
-
@truncator.truncate_object(params)
|
274
|
-
|
275
|
-
expect(params).to eq(bingo: { bango: '[Circular]' })
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
context "of arrays" do
|
280
|
-
context "with long strings" do
|
281
|
-
let(:params) do
|
282
|
-
{ bingo: ['bango', ['bongo', ['bish' * 2000]]],
|
283
|
-
bish: 'bash',
|
284
|
-
bosh: 'foo' }
|
285
|
-
end
|
286
|
-
|
287
|
-
it "truncates only the long string" do
|
288
|
-
expect(params[:bingo][1][1][0].length).to eq(8000)
|
289
|
-
|
290
|
-
@truncator.truncate_object(params)
|
291
|
-
|
292
|
-
expect(params[:bingo][1][1][0].length).to eq(max_len)
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
context "of recursive arrays" do
|
298
|
-
let(:params) do
|
299
|
-
a = []
|
300
|
-
a << a << :bango
|
301
|
-
{ bingo: a }
|
302
|
-
end
|
303
|
-
|
304
|
-
it "prevents recursion" do
|
305
|
-
@truncator.truncate_object(params)
|
306
|
-
|
307
|
-
expect(params).to eq(bingo: ['[Circular]', :bango])
|
308
|
-
end
|
309
|
-
end
|
138
|
+
it "prevents recursion" do
|
139
|
+
expect(subject).to eq(['aaa[Truncated]', 'bb', '[Circular]'])
|
310
140
|
end
|
141
|
+
end
|
311
142
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
it "converts the object to a safe string" do
|
317
|
-
@truncator.truncate_object(params)
|
318
|
-
|
319
|
-
expect(params[:bingo]).to include('Object')
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
context "with redefined #to_s" do
|
324
|
-
let(:params) do
|
325
|
-
obj = Object.new
|
326
|
-
|
327
|
-
def obj.to_s
|
328
|
-
'bango' * 2000
|
329
|
-
end
|
330
|
-
|
331
|
-
{ bingo: obj }
|
332
|
-
end
|
333
|
-
|
334
|
-
it "truncates the string if it's too long" do
|
335
|
-
@truncator.truncate_object(params)
|
336
|
-
|
337
|
-
expect(params[:bingo].length).to eq(max_len)
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
context "with other owner than Kernel" do
|
342
|
-
let(:params) do
|
343
|
-
mod = Module.new do
|
344
|
-
def to_s
|
345
|
-
"I am a fancy object" * 2000
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
klass = Class.new { include mod }
|
350
|
-
|
351
|
-
{ bingo: klass.new }
|
352
|
-
end
|
143
|
+
context "given a recursive array with recursive hashes" do
|
144
|
+
let(:object) do
|
145
|
+
a = []
|
146
|
+
a << a
|
353
147
|
|
354
|
-
|
355
|
-
|
148
|
+
h = {}
|
149
|
+
h[:k] = h
|
150
|
+
a << h << 'aaaa'
|
151
|
+
end
|
356
152
|
|
357
|
-
|
358
|
-
|
359
|
-
|
153
|
+
it "prevents recursion" do
|
154
|
+
expect(subject).to eq(['[Circular]', { k: '[Circular]' }, 'aaa[Truncated]'])
|
155
|
+
expect(subject).to be_frozen
|
360
156
|
end
|
157
|
+
end
|
361
158
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
bingo << bango << bango
|
367
|
-
{ bish: bingo }
|
368
|
-
end
|
159
|
+
context "given a recursive set with recursive arrays" do
|
160
|
+
let(:object) do
|
161
|
+
s = Set.new
|
162
|
+
s << s
|
369
163
|
|
370
|
-
|
371
|
-
|
164
|
+
h = {}
|
165
|
+
h[:k] = h
|
166
|
+
s << h << 'aaaa'
|
167
|
+
end
|
372
168
|
|
373
|
-
|
374
|
-
|
169
|
+
it "prevents recursion" do
|
170
|
+
expect(subject).to eq(
|
171
|
+
Set.new(['[Circular]', { k: '[Circular]' }, 'aaa[Truncated]'])
|
172
|
+
)
|
173
|
+
expect(subject).to be_frozen
|
375
174
|
end
|
376
175
|
end
|
377
176
|
|
378
|
-
|
379
|
-
|
380
|
-
|
177
|
+
context "given a hash with long strings" do
|
178
|
+
let(:object) do
|
179
|
+
{
|
180
|
+
a: multiply_by_2_max_len('a'),
|
181
|
+
b: multiply_by_2_max_len('b'),
|
182
|
+
c: { d: multiply_by_2_max_len('d'), e: 'e' }
|
183
|
+
}
|
381
184
|
end
|
382
185
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
@truncator.truncate_object(params)
|
389
|
-
|
390
|
-
expect(params[:unicode].length).to eq(max_len - 1)
|
391
|
-
expect(params[:unicode]).to match(/\A€{#{max_size - 1}}\[Truncated\]\z/)
|
392
|
-
end
|
186
|
+
it "truncates the long strings" do
|
187
|
+
expect(subject).to eq(
|
188
|
+
a: 'aaa[Truncated]', b: 'bbb[Truncated]', c: { d: 'ddd[Truncated]', e: 'e' }
|
189
|
+
)
|
190
|
+
expect(subject).to be_frozen
|
393
191
|
end
|
192
|
+
end
|
394
193
|
|
395
|
-
|
396
|
-
|
397
|
-
let(:max_size) { 100 }
|
398
|
-
|
399
|
-
it "converts strings to valid UTF-8" do
|
400
|
-
@truncator.truncate_object(params)
|
401
|
-
|
402
|
-
expect(params[:unicode]).to match(/\Abad string€[�\?]\z/)
|
403
|
-
expect { params.to_json }.not_to raise_error
|
404
|
-
end
|
405
|
-
|
406
|
-
it "converts ASCII-8BIT strings with invalid characters to UTF-8 correctly" do
|
407
|
-
# Shenanigans to get a bad ASCII-8BIT string. Direct conversion raises error.
|
408
|
-
encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
|
409
|
-
bad_string = Base64.decode64(encoded)
|
410
|
-
|
411
|
-
params = { unicode: bad_string }
|
412
|
-
|
413
|
-
@truncator.truncate_object(params)
|
414
|
-
|
415
|
-
expect(params[:unicode]).to match(/[�\?]{4}/)
|
416
|
-
end
|
417
|
-
|
418
|
-
it "doesn't fail when string is frozen" do
|
419
|
-
encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
|
420
|
-
bad_string = Base64.decode64(encoded).freeze
|
421
|
-
|
422
|
-
params = { unicode: bad_string }
|
423
|
-
|
424
|
-
@truncator.truncate_object(params)
|
194
|
+
context "given a string with valid unicode characters" do
|
195
|
+
let(:object) { "€€€€€" }
|
425
196
|
|
426
|
-
|
427
|
-
|
197
|
+
it "truncates the string" do
|
198
|
+
expect(subject).to eq("€€€[Truncated]")
|
428
199
|
end
|
429
200
|
end
|
430
201
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
202
|
+
context "given an ASCII-8BIT string with invalid characters" do
|
203
|
+
let(:object) do
|
204
|
+
# Shenanigans to get a bad ASCII-8BIT string. Direct conversion raises error.
|
205
|
+
encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
|
206
|
+
Base64.decode64(encoded).freeze
|
207
|
+
end
|
208
|
+
|
209
|
+
it "converts and truncates the string to UTF-8" do
|
210
|
+
expect(subject).to eq("���[Truncated]")
|
211
|
+
expect(subject).to be_frozen
|
435
212
|
end
|
436
213
|
end
|
437
214
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airbrake-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Airbrake Technologies, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|