airbrake-ruby 4.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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +513 -0
  3. data/lib/airbrake-ruby/async_sender.rb +142 -0
  4. data/lib/airbrake-ruby/backtrace.rb +196 -0
  5. data/lib/airbrake-ruby/benchmark.rb +39 -0
  6. data/lib/airbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/airbrake-ruby/config.rb +229 -0
  8. data/lib/airbrake-ruby/config/validator.rb +91 -0
  9. data/lib/airbrake-ruby/deploy_notifier.rb +36 -0
  10. data/lib/airbrake-ruby/file_cache.rb +48 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  12. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  19. data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  21. data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -0
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  26. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  27. data/lib/airbrake-ruby/ignorable.rb +44 -0
  28. data/lib/airbrake-ruby/inspectable.rb +39 -0
  29. data/lib/airbrake-ruby/loggable.rb +34 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +43 -0
  31. data/lib/airbrake-ruby/nested_exception.rb +38 -0
  32. data/lib/airbrake-ruby/notice.rb +162 -0
  33. data/lib/airbrake-ruby/notice_notifier.rb +134 -0
  34. data/lib/airbrake-ruby/performance_breakdown.rb +45 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +125 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +53 -0
  38. data/lib/airbrake-ruby/request.rb +45 -0
  39. data/lib/airbrake-ruby/response.rb +74 -0
  40. data/lib/airbrake-ruby/stashable.rb +15 -0
  41. data/lib/airbrake-ruby/stat.rb +73 -0
  42. data/lib/airbrake-ruby/sync_sender.rb +113 -0
  43. data/lib/airbrake-ruby/tdigest.rb +393 -0
  44. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  45. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  46. data/lib/airbrake-ruby/truncator.rb +115 -0
  47. data/lib/airbrake-ruby/version.rb +6 -0
  48. data/spec/airbrake_spec.rb +324 -0
  49. data/spec/async_sender_spec.rb +155 -0
  50. data/spec/backtrace_spec.rb +427 -0
  51. data/spec/benchmark_spec.rb +33 -0
  52. data/spec/code_hunk_spec.rb +115 -0
  53. data/spec/config/validator_spec.rb +184 -0
  54. data/spec/config_spec.rb +154 -0
  55. data/spec/deploy_notifier_spec.rb +48 -0
  56. data/spec/file_cache.rb +36 -0
  57. data/spec/filter_chain_spec.rb +92 -0
  58. data/spec/filters/context_filter_spec.rb +23 -0
  59. data/spec/filters/dependency_filter_spec.rb +12 -0
  60. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  61. data/spec/filters/gem_root_filter_spec.rb +41 -0
  62. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  63. data/spec/filters/git_repository_filter.rb +61 -0
  64. data/spec/filters/git_revision_filter_spec.rb +126 -0
  65. data/spec/filters/keys_blacklist_spec.rb +225 -0
  66. data/spec/filters/keys_whitelist_spec.rb +194 -0
  67. data/spec/filters/root_directory_filter_spec.rb +39 -0
  68. data/spec/filters/sql_filter_spec.rb +219 -0
  69. data/spec/filters/system_exit_filter_spec.rb +14 -0
  70. data/spec/filters/thread_filter_spec.rb +277 -0
  71. data/spec/fixtures/notroot.txt +7 -0
  72. data/spec/fixtures/project_root/code.rb +221 -0
  73. data/spec/fixtures/project_root/empty_file.rb +0 -0
  74. data/spec/fixtures/project_root/long_line.txt +1 -0
  75. data/spec/fixtures/project_root/short_file.rb +3 -0
  76. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  77. data/spec/helpers.rb +9 -0
  78. data/spec/ignorable_spec.rb +14 -0
  79. data/spec/inspectable_spec.rb +45 -0
  80. data/spec/monotonic_time_spec.rb +12 -0
  81. data/spec/nested_exception_spec.rb +73 -0
  82. data/spec/notice_notifier_spec.rb +356 -0
  83. data/spec/notice_notifier_spec/options_spec.rb +259 -0
  84. data/spec/notice_spec.rb +296 -0
  85. data/spec/performance_breakdown_spec.rb +12 -0
  86. data/spec/performance_notifier_spec.rb +435 -0
  87. data/spec/promise_spec.rb +197 -0
  88. data/spec/query_spec.rb +11 -0
  89. data/spec/request_spec.rb +11 -0
  90. data/spec/response_spec.rb +88 -0
  91. data/spec/spec_helper.rb +100 -0
  92. data/spec/stashable_spec.rb +23 -0
  93. data/spec/stat_spec.rb +47 -0
  94. data/spec/sync_sender_spec.rb +133 -0
  95. data/spec/tdigest_spec.rb +230 -0
  96. data/spec/time_truncate_spec.rb +13 -0
  97. data/spec/timed_trace_spec.rb +125 -0
  98. data/spec/truncator_spec.rb +238 -0
  99. metadata +213 -0
@@ -0,0 +1,225 @@
1
+ RSpec.describe Airbrake::Filters::KeysBlacklist do
2
+ subject { described_class.new(patterns) }
3
+
4
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
5
+
6
+ shared_examples 'pattern matching' do |patts, params|
7
+ let(:patterns) { patts }
8
+
9
+ it "filters out the matching values" do
10
+ notice[:params] = params.first
11
+ subject.call(notice)
12
+ expect(notice[:params]).to eq(params.last)
13
+ end
14
+ end
15
+
16
+ context "when a pattern is a Regexp" do
17
+ include_examples(
18
+ 'pattern matching',
19
+ [/\Abon/],
20
+ [
21
+ { bongo: 'bango' },
22
+ { bongo: '[Filtered]' }
23
+ ]
24
+ )
25
+
26
+ context "and when a key is a hash" do
27
+ let(:patterns) { [/bango/] }
28
+
29
+ # https://github.com/airbrake/airbrake/issues/739
30
+ it "doesn't fail" do
31
+ notice[:params] = { bingo: { {} => 'unfiltered' } }
32
+ expect { subject.call(notice) }.not_to raise_error
33
+ end
34
+ end
35
+ end
36
+
37
+ context "when a pattern is a Symbol" do
38
+ include_examples(
39
+ 'pattern matching',
40
+ [:bingo],
41
+ [
42
+ { bingo: 'bango' },
43
+ { bingo: '[Filtered]' }
44
+ ]
45
+ )
46
+ end
47
+
48
+ context "when a pattern is a String" do
49
+ include_examples(
50
+ 'pattern matching',
51
+ ['bingo'],
52
+ [
53
+ { bingo: 'bango' },
54
+ { bingo: '[Filtered]' }
55
+ ]
56
+ )
57
+ end
58
+
59
+ context "when a pattern is a Array of Hash" do
60
+ include_examples(
61
+ 'pattern matching',
62
+ ['bingo'],
63
+ [
64
+ { array: [{ bingo: 'bango' }, []] },
65
+ { array: [{ bingo: '[Filtered]' }, []] }
66
+ ]
67
+ )
68
+ end
69
+
70
+ context "when a Proc pattern was provided" do
71
+ context "along with normal keys" do
72
+ include_examples(
73
+ 'pattern matching',
74
+ [proc { 'bongo' }, :bash],
75
+ [
76
+ { bingo: 'bango', bongo: 'bish', bash: 'bosh' },
77
+ { bingo: 'bango', bongo: '[Filtered]', bash: '[Filtered]' }
78
+ ]
79
+ )
80
+ end
81
+
82
+ context "which doesn't return an array of keys" do
83
+ include_examples(
84
+ 'pattern matching',
85
+ [proc { Object.new }],
86
+ [
87
+ { bingo: 'bango', bongo: 'bish' },
88
+ { bingo: 'bango', bongo: 'bish' }
89
+ ]
90
+ )
91
+
92
+ it "logs an error" do
93
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
94
+ /KeysBlacklist is invalid.+patterns: \[#<Object:.+>\]/
95
+ )
96
+ keys_blacklist = described_class.new(patterns)
97
+ keys_blacklist.call(notice)
98
+ end
99
+ end
100
+
101
+ context "which returns another Proc" do
102
+ let(:patterns) { [proc { proc { ['bingo'] } }] }
103
+
104
+ context "and when the filter is called once" do
105
+ it "logs an error" do
106
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
107
+ /KeysBlacklist is invalid.+patterns: \[#<Proc:.+>\]/
108
+ )
109
+ keys_blacklist = described_class.new(patterns)
110
+ keys_blacklist.call(notice)
111
+ end
112
+ end
113
+
114
+ context "and when the filter is called twice" do
115
+ it "unwinds procs and filters keys" do
116
+ notice[:params] = { bingo: 'bango', bongo: 'bish' }
117
+ 2.times { subject.call(notice) }
118
+ expect(notice[:params]).to eq(bingo: '[Filtered]', bongo: 'bish')
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ context "when a pattern is invalid" do
125
+ include_examples(
126
+ 'pattern matching',
127
+ [Object.new],
128
+ [
129
+ { bingo: 'bango', bongo: 'bish' },
130
+ { bingo: 'bango', bongo: 'bish' }
131
+ ]
132
+ )
133
+
134
+ it "logs an error" do
135
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
136
+ /KeysBlacklist is invalid.+patterns: \[#<Object:.+>\]/
137
+ )
138
+ keys_blacklist = described_class.new(patterns)
139
+ keys_blacklist.call(notice)
140
+ end
141
+ end
142
+
143
+ context "when a value contains a nested hash" do
144
+ context "and it is non-recursive" do
145
+ include_examples(
146
+ 'pattern matching',
147
+ ['bish'],
148
+ [
149
+ { bongo: { bish: 'bash' } },
150
+ { bongo: { bish: '[Filtered]' } }
151
+ ]
152
+ )
153
+ end
154
+
155
+ context "and it is recursive" do
156
+ bongo = { bingo: {} }
157
+ bongo[:bingo][:bango] = bongo
158
+
159
+ include_examples(
160
+ 'pattern matching',
161
+ ['bango'],
162
+ [
163
+ bongo,
164
+ { bingo: { bango: '[Filtered]' } }
165
+ ]
166
+ )
167
+ end
168
+ end
169
+
170
+ describe "context payload" do
171
+ context "when a URL with query params is present" do
172
+ let(:patterns) { ['bish'] }
173
+
174
+ it "filters the params" do
175
+ notice[:context][:url] =
176
+ 'http://localhost:3000/crash?foo=bar&baz=bongo&bish=bash&color=%23FFAAFF'
177
+
178
+ subject.call(notice)
179
+ expect(notice[:context][:url]).to(
180
+ eq 'http://localhost:3000/crash?foo=bar&baz=bongo&bish=[Filtered]&color=%23FFAAFF'
181
+ )
182
+ end
183
+ end
184
+
185
+ context "when the user key is present" do
186
+ let(:patterns) { ['user'] }
187
+
188
+ it "filters out the user" do
189
+ notice[:context][:user] = { id: 1337, name: 'Bingo Bango' }
190
+ subject.call(notice)
191
+ expect(notice[:context][:user]).to eq('[Filtered]')
192
+ end
193
+ end
194
+
195
+ context "and when it is a hash" do
196
+ let(:patterns) { ['name'] }
197
+
198
+ it "filters out individual user fields" do
199
+ notice[:context][:user] = { id: 1337, name: 'Bingo Bango' }
200
+ subject.call(notice)
201
+ expect(notice[:context][:user][:name]).to eq('[Filtered]')
202
+ end
203
+ end
204
+ end
205
+
206
+ context "when the headers key is present" do
207
+ let(:patterns) { ['headers'] }
208
+
209
+ it "filters out the headers" do
210
+ notice[:context][:headers] = { 'HTTP_COOKIE' => 'banana' }
211
+ subject.call(notice)
212
+ expect(notice[:context][:headers]).to eq('[Filtered]')
213
+ end
214
+
215
+ context "and when it is a hash" do
216
+ let(:patterns) { ['HTTP_COOKIE'] }
217
+
218
+ it "filters out individual header fields" do
219
+ notice[:context][:headers] = { 'HTTP_COOKIE' => 'banana' }
220
+ subject.call(notice)
221
+ expect(notice[:context][:headers]['HTTP_COOKIE']).to eq('[Filtered]')
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,194 @@
1
+ RSpec.describe Airbrake::Filters::KeysWhitelist do
2
+ subject { described_class.new(patterns) }
3
+
4
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
5
+
6
+ shared_examples 'pattern matching' do |patts, params|
7
+ let(:patterns) { patts }
8
+
9
+ it "filters out the matching values" do
10
+ notice[:params] = params.first
11
+ subject.call(notice)
12
+ expect(notice[:params]).to eq(params.last)
13
+ end
14
+ end
15
+
16
+ context "when a pattern is a Regexp" do
17
+ include_examples(
18
+ 'pattern matching',
19
+ [/\Abin/],
20
+ [
21
+ { bingo: 'bango', bongo: 'bish', bash: 'bosh' },
22
+ { bingo: 'bango', bongo: '[Filtered]', bash: '[Filtered]' }
23
+ ]
24
+ )
25
+ end
26
+
27
+ context "when a pattern is a Symbol" do
28
+ include_examples(
29
+ 'pattern matching',
30
+ [:bongo],
31
+ [
32
+ { bongo: 'bish', bash: 'bosh', bbashh: 'bboshh' },
33
+ { bongo: 'bish', bash: '[Filtered]', bbashh: '[Filtered]' }
34
+ ]
35
+ )
36
+ end
37
+
38
+ context "when a pattern is a String" do
39
+ include_examples(
40
+ 'pattern matching',
41
+ ['bash'],
42
+ [
43
+ { bingo: 'bango', bongo: 'bish', bash: 'bosh' },
44
+ { bingo: '[Filtered]', bongo: '[Filtered]', bash: 'bosh' }
45
+ ]
46
+ )
47
+ end
48
+
49
+ context "when a Proc pattern was provided" do
50
+ context "along with normal keys" do
51
+ include_examples(
52
+ 'pattern matching',
53
+ [proc { 'bongo' }, :bash],
54
+ [
55
+ { bingo: 'bango', bongo: 'bish', bash: 'bosh' },
56
+ { bingo: '[Filtered]', bongo: 'bish', bash: 'bosh' }
57
+ ]
58
+ )
59
+ end
60
+
61
+ context "which doesn't return an array of keys" do
62
+ include_examples(
63
+ 'pattern matching',
64
+ [proc { Object.new }],
65
+ [
66
+ { bingo: 'bango', bongo: 'bish', bash: 'bosh' },
67
+ { bingo: '[Filtered]', bongo: '[Filtered]', bash: '[Filtered]' }
68
+ ]
69
+ )
70
+
71
+ it "logs an error" do
72
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
73
+ /KeysWhitelist is invalid.+patterns: \[#<Object:.+>\]/
74
+ )
75
+ keys_whitelist = described_class.new(patterns)
76
+ keys_whitelist.call(notice)
77
+ end
78
+ end
79
+
80
+ context "which returns another Proc" do
81
+ let(:patterns) { [proc { proc { ['bingo'] } }] }
82
+
83
+ context "and when the filter is called once" do
84
+ it "logs an error" do
85
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
86
+ /KeysWhitelist is invalid.+patterns: \[#<Proc:.+>\]/
87
+ )
88
+ keys_whitelist = described_class.new(patterns)
89
+ keys_whitelist.call(notice)
90
+ end
91
+
92
+ include_examples(
93
+ 'pattern matching',
94
+ [proc { proc { ['bingo'] } }],
95
+ [
96
+ { bingo: 'bango', bongo: 'bish', bash: 'bosh' },
97
+ { bingo: '[Filtered]', bongo: '[Filtered]', bash: '[Filtered]' }
98
+ ]
99
+ )
100
+ end
101
+ end
102
+ end
103
+
104
+ context "when a pattern is invalid" do
105
+ include_examples(
106
+ 'pattern matching',
107
+ [Object.new],
108
+ [
109
+ { bingo: 'bango', bongo: 'bish', bash: 'bosh' },
110
+ { bingo: '[Filtered]', bongo: '[Filtered]', bash: '[Filtered]' }
111
+ ]
112
+ )
113
+
114
+ it "logs an error" do
115
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
116
+ /KeysWhitelist is invalid.+patterns: \[#<Object:.+>\]/
117
+ )
118
+ keys_whitelist = described_class.new(patterns)
119
+ keys_whitelist.call(notice)
120
+ end
121
+ end
122
+
123
+ context "when a value contains a nested hash" do
124
+ context "and it is non-recursive" do
125
+ include_examples(
126
+ 'pattern matching',
127
+ %w[bongo bish],
128
+ [
129
+ { bingo: 'bango', bongo: { bish: 'bash' } },
130
+ { bingo: '[Filtered]', bongo: { bish: 'bash' } }
131
+ ]
132
+ )
133
+ end
134
+
135
+ context "and it is recursive" do
136
+ let(:patterns) { ['bingo'] }
137
+
138
+ it "raises error (MRI)", skip: (
139
+ # MRI 2.3 & 2.4 may segfault on Circle CI. Example build:
140
+ # https://circleci.com/workflow-run/c112358c-e7bf-4789-9eb2-4891ea84da68
141
+ RUBY_ENGINE == 'ruby' && RUBY_VERSION =~ /\A2\.[34]\.\d+\z/
142
+ ) do
143
+ bongo = {}
144
+ bongo[:bingo] = bongo
145
+ notice[:params] = bongo
146
+
147
+ begin
148
+ expect { subject.call(notice) }.to raise_error(SystemStackError)
149
+ rescue RSpec::Expectations::ExpectationNotMetError => ex
150
+ # JRuby might raise two different exceptions, which represent the same
151
+ # thing. One is a Java exception, the other is a Ruby exception.
152
+ # Likely a bug: https://github.com/jruby/jruby/issues/1903
153
+ raise ex unless RUBY_ENGINE == 'jruby'
154
+ expect { subject.call(notice) }.to raise_error(java.lang.StackOverflowError)
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ describe "context payload" do
161
+ describe "URL" do
162
+ let(:patterns) { ['bish'] }
163
+
164
+ context "when it contains query params" do
165
+ it "filters the params" do
166
+ notice[:context][:url] = 'http://localhost:3000/crash?foo=bar&baz=bongo&bish=bash'
167
+ subject.call(notice)
168
+ expect(notice[:context][:url]).to(
169
+ eq('http://localhost:3000/crash?foo=[Filtered]&baz=[Filtered]&bish=bash')
170
+ )
171
+ end
172
+ end
173
+
174
+ context "when it is invalid" do
175
+ it "leaves the URL unfiltered" do
176
+ notice[:context][:url] =
177
+ 'http://localhost:3000/cra]]]sh?foo=bar&baz=bongo&bish=bash'
178
+ subject.call(notice)
179
+ expect(notice[:context][:url]).to(
180
+ eq('http://localhost:3000/cra]]]sh?foo=bar&baz=bongo&bish=bash')
181
+ )
182
+ end
183
+ end
184
+
185
+ context "when it is without a query" do
186
+ it "leaves the URL untouched" do
187
+ notice[:context][:url] = 'http://localhost:3000/crash'
188
+ subject.call(notice)
189
+ expect(notice[:context][:url]).to(eq('http://localhost:3000/crash'))
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,39 @@
1
+ RSpec.describe Airbrake::Filters::RootDirectoryFilter do
2
+ subject { described_class.new(root_directory) }
3
+
4
+ let(:root_directory) { '/var/www/project' }
5
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
6
+
7
+ it "replaces root directory in the backtrace with a label" do
8
+ # rubocop:disable Metrics/LineLength
9
+ notice[:errors].first[:backtrace] = [
10
+ { file: "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb" },
11
+ { file: "#{root_directory}/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb " },
12
+ { file: "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb" },
13
+ { file: "#{root_directory}/gems/rspec-core-3.3.2/exe/rspec" }
14
+ ]
15
+ # rubocop:enable Metrics/LineLength
16
+
17
+ subject.call(notice)
18
+
19
+ # rubocop:disable Metrics/LineLength
20
+ expect(notice[:errors].first[:backtrace]).to(
21
+ eq(
22
+ [
23
+ { file: "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb" },
24
+ { file: "/PROJECT_ROOT/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb " },
25
+ { file: "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb" },
26
+ { file: "/PROJECT_ROOT/gems/rspec-core-3.3.2/exe/rspec" }
27
+ ]
28
+ )
29
+ )
30
+ # rubocop:enable Metrics/LineLength
31
+ end
32
+
33
+ it "does not filter file when it is nil" do
34
+ expect(notice[:errors].first[:file]).to be_nil
35
+ expect { subject.call(notice) }.not_to(
36
+ change { notice[:errors].first[:file] }
37
+ )
38
+ end
39
+ end