airbrake-ruby 2.5.1 → 2.6.0
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/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
|