airbrake-ruby 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +197 -43
  3. data/lib/airbrake-ruby/config.rb +43 -11
  4. data/lib/airbrake-ruby/deploy_notifier.rb +47 -0
  5. data/lib/airbrake-ruby/filter_chain.rb +32 -50
  6. data/lib/airbrake-ruby/filters/git_repository_filter.rb +9 -1
  7. data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
  8. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  9. data/lib/airbrake-ruby/ignorable.rb +44 -0
  10. data/lib/airbrake-ruby/notice.rb +2 -22
  11. data/lib/airbrake-ruby/{notifier.rb → notice_notifier.rb} +66 -46
  12. data/lib/airbrake-ruby/performance_notifier.rb +161 -0
  13. data/lib/airbrake-ruby/stat.rb +56 -0
  14. data/lib/airbrake-ruby/tdigest.rb +393 -0
  15. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  16. data/lib/airbrake-ruby/version.rb +1 -1
  17. data/spec/airbrake_spec.rb +57 -13
  18. data/spec/async_sender_spec.rb +0 -2
  19. data/spec/backtrace_spec.rb +0 -2
  20. data/spec/code_hunk_spec.rb +0 -2
  21. data/spec/config/validator_spec.rb +0 -2
  22. data/spec/config_spec.rb +16 -4
  23. data/spec/deploy_notifier_spec.rb +41 -0
  24. data/spec/file_cache.rb +0 -2
  25. data/spec/filter_chain_spec.rb +1 -7
  26. data/spec/filters/context_filter_spec.rb +0 -2
  27. data/spec/filters/dependency_filter_spec.rb +0 -2
  28. data/spec/filters/exception_attributes_filter_spec.rb +0 -2
  29. data/spec/filters/gem_root_filter_spec.rb +0 -2
  30. data/spec/filters/git_last_checkout_filter_spec.rb +0 -2
  31. data/spec/filters/git_repository_filter.rb +0 -2
  32. data/spec/filters/git_revision_filter_spec.rb +0 -2
  33. data/spec/filters/keys_blacklist_spec.rb +0 -2
  34. data/spec/filters/keys_whitelist_spec.rb +0 -2
  35. data/spec/filters/root_directory_filter_spec.rb +0 -2
  36. data/spec/filters/sql_filter_spec.rb +219 -0
  37. data/spec/filters/system_exit_filter_spec.rb +0 -2
  38. data/spec/filters/thread_filter_spec.rb +0 -2
  39. data/spec/ignorable_spec.rb +14 -0
  40. data/spec/nested_exception_spec.rb +0 -2
  41. data/spec/{notifier_spec.rb → notice_notifier_spec.rb} +24 -114
  42. data/spec/{notifier_spec → notice_notifier_spec}/options_spec.rb +40 -39
  43. data/spec/notice_spec.rb +2 -4
  44. data/spec/performance_notifier_spec.rb +287 -0
  45. data/spec/promise_spec.rb +0 -2
  46. data/spec/response_spec.rb +0 -2
  47. data/spec/stat_spec.rb +35 -0
  48. data/spec/sync_sender_spec.rb +0 -2
  49. data/spec/tdigest_spec.rb +230 -0
  50. data/spec/time_truncate_spec.rb +13 -0
  51. data/spec/truncator_spec.rb +0 -2
  52. metadata +34 -15
  53. data/lib/airbrake-ruby/route_sender.rb +0 -175
  54. data/spec/route_sender_spec.rb +0 -130
@@ -0,0 +1,13 @@
1
+ RSpec.describe Airbrake::TimeTruncate do
2
+ describe "#utc_truncate_minutes" do
3
+ it "truncates time to the floor minute and returns an RFC3339 timestamp" do
4
+ time = Time.new(2018, 1, 1, 0, 0, 20, 0)
5
+ expect(subject.utc_truncate_minutes(time)).to eq('2018-01-01T00:00:00+00:00')
6
+ end
7
+
8
+ it "converts time with zone to UTC" do
9
+ time = Time.new(2018, 1, 1, 0, 0, 20, '-05:00')
10
+ expect(subject.utc_truncate_minutes(time)).to eq('2018-01-01T05:00:00+00:00')
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  RSpec.describe Airbrake::Truncator do
4
2
  def multiply_by_2_max_len(chr)
5
3
  chr * 2 * max_len
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.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: 2019-01-23 00:00:00.000000000 Z
11
+ date: 2019-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: tdigest
14
+ name: rbtree3
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '='
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.1
19
+ version: 0.5.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '='
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.1
26
+ version: 0.5.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -134,6 +134,7 @@ files:
134
134
  - lib/airbrake-ruby/code_hunk.rb
135
135
  - lib/airbrake-ruby/config.rb
136
136
  - lib/airbrake-ruby/config/validator.rb
137
+ - lib/airbrake-ruby/deploy_notifier.rb
137
138
  - lib/airbrake-ruby/file_cache.rb
138
139
  - lib/airbrake-ruby/filter_chain.rb
139
140
  - lib/airbrake-ruby/filters/context_filter.rb
@@ -147,15 +148,21 @@ files:
147
148
  - lib/airbrake-ruby/filters/keys_filter.rb
148
149
  - lib/airbrake-ruby/filters/keys_whitelist.rb
149
150
  - lib/airbrake-ruby/filters/root_directory_filter.rb
151
+ - lib/airbrake-ruby/filters/sql_filter.rb
150
152
  - lib/airbrake-ruby/filters/system_exit_filter.rb
151
153
  - lib/airbrake-ruby/filters/thread_filter.rb
154
+ - lib/airbrake-ruby/hash_keyable.rb
155
+ - lib/airbrake-ruby/ignorable.rb
152
156
  - lib/airbrake-ruby/nested_exception.rb
153
157
  - lib/airbrake-ruby/notice.rb
154
- - lib/airbrake-ruby/notifier.rb
158
+ - lib/airbrake-ruby/notice_notifier.rb
159
+ - lib/airbrake-ruby/performance_notifier.rb
155
160
  - lib/airbrake-ruby/promise.rb
156
161
  - lib/airbrake-ruby/response.rb
157
- - lib/airbrake-ruby/route_sender.rb
162
+ - lib/airbrake-ruby/stat.rb
158
163
  - lib/airbrake-ruby/sync_sender.rb
164
+ - lib/airbrake-ruby/tdigest.rb
165
+ - lib/airbrake-ruby/time_truncate.rb
159
166
  - lib/airbrake-ruby/truncator.rb
160
167
  - lib/airbrake-ruby/version.rb
161
168
  - spec/airbrake_spec.rb
@@ -164,6 +171,7 @@ files:
164
171
  - spec/code_hunk_spec.rb
165
172
  - spec/config/validator_spec.rb
166
173
  - spec/config_spec.rb
174
+ - spec/deploy_notifier_spec.rb
167
175
  - spec/file_cache.rb
168
176
  - spec/filter_chain_spec.rb
169
177
  - spec/filters/context_filter_spec.rb
@@ -176,6 +184,7 @@ files:
176
184
  - spec/filters/keys_blacklist_spec.rb
177
185
  - spec/filters/keys_whitelist_spec.rb
178
186
  - spec/filters/root_directory_filter_spec.rb
187
+ - spec/filters/sql_filter_spec.rb
179
188
  - spec/filters/system_exit_filter_spec.rb
180
189
  - spec/filters/thread_filter_spec.rb
181
190
  - spec/fixtures/notroot.txt
@@ -185,15 +194,19 @@ files:
185
194
  - spec/fixtures/project_root/short_file.rb
186
195
  - spec/fixtures/project_root/vendor/bundle/ignored_file.rb
187
196
  - spec/helpers.rb
197
+ - spec/ignorable_spec.rb
188
198
  - spec/nested_exception_spec.rb
199
+ - spec/notice_notifier_spec.rb
200
+ - spec/notice_notifier_spec/options_spec.rb
189
201
  - spec/notice_spec.rb
190
- - spec/notifier_spec.rb
191
- - spec/notifier_spec/options_spec.rb
202
+ - spec/performance_notifier_spec.rb
192
203
  - spec/promise_spec.rb
193
204
  - spec/response_spec.rb
194
- - spec/route_sender_spec.rb
195
205
  - spec/spec_helper.rb
206
+ - spec/stat_spec.rb
196
207
  - spec/sync_sender_spec.rb
208
+ - spec/tdigest_spec.rb
209
+ - spec/time_truncate_spec.rb
197
210
  - spec/truncator_spec.rb
198
211
  homepage: https://airbrake.io
199
212
  licenses:
@@ -220,11 +233,11 @@ signing_key:
220
233
  specification_version: 4
221
234
  summary: Ruby notifier for https://airbrake.io
222
235
  test_files:
223
- - spec/route_sender_spec.rb
224
236
  - spec/truncator_spec.rb
225
237
  - spec/helpers.rb
226
238
  - spec/filters/exception_attributes_filter_spec.rb
227
239
  - spec/filters/root_directory_filter_spec.rb
240
+ - spec/filters/sql_filter_spec.rb
228
241
  - spec/filters/keys_whitelist_spec.rb
229
242
  - spec/filters/system_exit_filter_spec.rb
230
243
  - spec/filters/thread_filter_spec.rb
@@ -238,13 +251,19 @@ test_files:
238
251
  - spec/spec_helper.rb
239
252
  - spec/notice_spec.rb
240
253
  - spec/config_spec.rb
254
+ - spec/tdigest_spec.rb
241
255
  - spec/async_sender_spec.rb
256
+ - spec/stat_spec.rb
242
257
  - spec/backtrace_spec.rb
258
+ - spec/notice_notifier_spec.rb
259
+ - spec/time_truncate_spec.rb
243
260
  - spec/promise_spec.rb
244
261
  - spec/config/validator_spec.rb
245
262
  - spec/sync_sender_spec.rb
263
+ - spec/ignorable_spec.rb
264
+ - spec/deploy_notifier_spec.rb
265
+ - spec/performance_notifier_spec.rb
246
266
  - spec/airbrake_spec.rb
247
- - spec/notifier_spec.rb
248
267
  - spec/nested_exception_spec.rb
249
268
  - spec/filter_chain_spec.rb
250
269
  - spec/response_spec.rb
@@ -256,4 +275,4 @@ test_files:
256
275
  - spec/fixtures/project_root/code.rb
257
276
  - spec/fixtures/project_root/short_file.rb
258
277
  - spec/fixtures/project_root/vendor/bundle/ignored_file.rb
259
- - spec/notifier_spec/options_spec.rb
278
+ - spec/notice_notifier_spec/options_spec.rb
@@ -1,175 +0,0 @@
1
- require 'tdigest'
2
- require 'base64'
3
-
4
- module Airbrake
5
- # RouteSender aggregates information about requests and periodically sends
6
- # collected data to Airbrake.
7
- # @since v3.0.0
8
- class RouteSender
9
- # Monkey-patch https://github.com/castle/tdigest to pack with Big Endian
10
- # (instead of Little Endian) since our backend wants it.
11
- #
12
- # @see https://github.com/castle/tdigest/blob/master/lib/tdigest/tdigest.rb
13
- # @since v3.0.0
14
- # @api private
15
- module TDigestBigEndianness
16
- refine TDigest::TDigest do
17
- # rubocop:disable Metrics/AbcSize
18
- def as_small_bytes
19
- size = @centroids.size
20
- output = [self.class::SMALL_ENCODING, compression, size]
21
- x = 0
22
- # delta encoding allows saving 4-bytes floats
23
- mean_arr = @centroids.map do |_, c|
24
- val = c.mean - x
25
- x = c.mean
26
- val
27
- end
28
- output += mean_arr
29
- # Variable length encoding of numbers
30
- c_arr = @centroids.each_with_object([]) do |(_, c), arr|
31
- k = 0
32
- n = c.n
33
- while n < 0 || n > 0x7f
34
- b = 0x80 | (0x7f & n)
35
- arr << b
36
- n = n >> 7
37
- k += 1
38
- raise 'Unreasonable large number' if k > 6
39
- end
40
- arr << n
41
- end
42
- output += c_arr
43
- output.pack("NGNg#{size}C#{size}")
44
- end
45
- # rubocop:enable Metrics/AbcSize
46
- end
47
- end
48
-
49
- using TDigestBigEndianness
50
-
51
- # The key that represents a route.
52
- RouteKey = Struct.new(:method, :route, :statusCode, :time)
53
-
54
- # RouteStat holds data that describes a route's performance.
55
- RouteStat = Struct.new(:count, :sum, :sumsq, :tdigest) do
56
- # @param [Integer] count The number of requests
57
- # @param [Float] sum The sum of request duration in milliseconds
58
- # @param [Float] sumsq The squared sum of request duration in milliseconds
59
- # @param [TDigest::TDigest] tdigest By default, the compression is 20
60
- def initialize(
61
- count: 0, sum: 0.0, sumsq: 0.0, tdigest: TDigest::TDigest.new(0.05)
62
- )
63
- super(count, sum, sumsq, tdigest)
64
- end
65
-
66
- # @return [Hash{String=>Object}] the route stat as a hash with compressed
67
- # and serialized as binary base64 tdigest
68
- def to_h
69
- tdigest.compress!
70
- {
71
- 'count' => count,
72
- 'sum' => sum,
73
- 'sumsq' => sumsq,
74
- 'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes)
75
- }
76
- end
77
- end
78
-
79
- # @param [Airbrake::Config] config
80
- def initialize(config)
81
- @config = config
82
- @flush_period = config.route_stats_flush_period
83
- @sender = SyncSender.new(config, :put)
84
- @routes = {}
85
- @thread = nil
86
- @mutex = Mutex.new
87
- end
88
-
89
- # @macro see_public_api_method
90
- # @param [Airbrake::Promise] promise
91
- def notify_request(request_info, promise = Airbrake::Promise.new)
92
- route = create_route_key(
93
- request_info[:method],
94
- request_info[:route],
95
- request_info[:status_code],
96
- utc_truncate_minutes(request_info[:start_time])
97
- )
98
-
99
- @mutex.synchronize do
100
- @routes[route] ||= RouteStat.new
101
- increment_stats(request_info, @routes[route])
102
-
103
- if @flush_period > 0
104
- schedule_flush(promise)
105
- else
106
- send(@routes, promise)
107
- end
108
- end
109
-
110
- promise
111
- end
112
-
113
- private
114
-
115
- def create_route_key(method, route, status_code, tm)
116
- # rubocop:disable Style/DateTime
117
- time = DateTime.new(
118
- tm.year, tm.month, tm.day, tm.hour, tm.min, 0, tm.zone || 0
119
- )
120
- # rubocop:enable Style/DateTime
121
- RouteKey.new(method, route, status_code, time.rfc3339)
122
- end
123
-
124
- def increment_stats(request_info, stat)
125
- stat.count += 1
126
-
127
- end_time = request_info[:end_time] || Time.new
128
- ms = (end_time - request_info[:start_time]) * 1000
129
-
130
- stat.sum += ms
131
- stat.sumsq += ms * ms
132
-
133
- stat.tdigest.push(ms)
134
- end
135
-
136
- def schedule_flush(promise)
137
- @thread ||= Thread.new do
138
- sleep(@flush_period)
139
-
140
- routes = nil
141
- @mutex.synchronize do
142
- routes = @routes
143
- @routes = {}
144
- @thread = nil
145
- end
146
-
147
- send(routes, promise)
148
- end
149
-
150
- # Setting a name is needed to test the timer.
151
- # Ruby <=2.2 doesn't support Thread#name, so we have this check.
152
- @thread.name = 'route-stat-thread' if @thread.respond_to?(:name)
153
- end
154
-
155
- def send(routes, promise)
156
- if routes.none?
157
- raise "#{self.class.name}##{__method__}: routes cannot be empty. Race?"
158
- end
159
-
160
- @config.logger.debug("#{LOG_LABEL} RouteStats#send: #{routes}")
161
-
162
- @sender.send(
163
- { routes: routes.map { |k, v| k.to_h.merge(v.to_h) } },
164
- promise,
165
- URI.join(@config.host, "api/v5/projects/#{@config.project_id}/routes-stats")
166
- )
167
- end
168
-
169
- def utc_truncate_minutes(time)
170
- time_array = time.to_a
171
- time_array[0] = 0
172
- Time.utc(*time_array)
173
- end
174
- end
175
- end
@@ -1,130 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Airbrake::RouteSender do
4
- let(:endpoint) { 'https://api.airbrake.io/api/v5/projects/1/routes-stats' }
5
-
6
- let(:config) do
7
- Airbrake::Config.new(
8
- project_id: 1,
9
- project_key: 'banana',
10
- route_stats_flush_period: 0.1
11
- )
12
- end
13
-
14
- subject { described_class.new(config) }
15
-
16
- describe "#notify_request" do
17
- before do
18
- stub_request(:put, endpoint).to_return(status: 200, body: '')
19
- end
20
-
21
- # Let the request finish.
22
- after { sleep 0.2 }
23
-
24
- it "rounds time to the floor minute" do
25
- subject.notify_request(
26
- method: 'GET',
27
- route: '/foo',
28
- status_code: 200,
29
- start_time: Time.new(2018, 1, 1, 0, 0, 20, 0)
30
- )
31
- sleep 0.2
32
- expect(
33
- a_request(:put, endpoint).with(body: /"time":"2018-01-01T00:00:00\+00:00"/)
34
- ).to have_been_made
35
- end
36
-
37
- it "increments routes with the same key" do
38
- subject.notify_request(
39
- method: 'GET',
40
- route: '/foo',
41
- status_code: 200,
42
- start_time: Time.new(2018, 1, 1, 0, 0, 20, 0)
43
- )
44
- subject.notify_request(
45
- method: 'GET',
46
- route: '/foo',
47
- status_code: 200,
48
- start_time: Time.new(2018, 1, 1, 0, 0, 50, 0)
49
- )
50
- sleep 0.2
51
- expect(
52
- a_request(:put, endpoint).with(body: /"count":2/)
53
- ).to have_been_made
54
- end
55
-
56
- it "groups routes by time" do
57
- subject.notify_request(
58
- method: 'GET',
59
- route: '/foo',
60
- status_code: 200,
61
- start_time: Time.new(2018, 1, 1, 0, 0, 49, 0),
62
- end_time: Time.new(2018, 1, 1, 0, 0, 50, 0)
63
- )
64
- subject.notify_request(
65
- method: 'GET',
66
- route: '/foo',
67
- status_code: 200,
68
- start_time: Time.new(2018, 1, 1, 0, 1, 49, 0),
69
- end_time: Time.new(2018, 1, 1, 0, 1, 55, 0)
70
- )
71
- sleep 0.2
72
- expect(
73
- a_request(:put, endpoint).with(
74
- body: %r|\A
75
- {"routes":\[
76
- {"method":"GET","route":"/foo","statusCode":200,
77
- "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":1000.0,
78
- "sumsq":1000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUR6AAAB"},
79
- {"method":"GET","route":"/foo","statusCode":200,
80
- "time":"2018-01-01T00:01:00\+00:00","count":1,"sum":6000.0,
81
- "sumsq":36000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUW7gAAB"}\]}
82
- \z|x
83
- )
84
- ).to have_been_made
85
- end
86
-
87
- it "groups routes by route key" do
88
- subject.notify_request(
89
- method: 'GET',
90
- route: '/foo',
91
- status_code: 200,
92
- start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
93
- end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
94
- )
95
- subject.notify_request(
96
- method: 'POST',
97
- route: '/foo',
98
- status_code: 200,
99
- start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
100
- end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
101
- )
102
- sleep 0.2
103
- expect(
104
- a_request(:put, endpoint).with(
105
- body: %r|\A
106
- {"routes":\[
107
- {"method":"GET","route":"/foo","statusCode":200,
108
- "time":"2018-01-01T00:49:00\+00:00","count":1,"sum":60000.0,
109
- "sumsq":3600000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB"},
110
- {"method":"POST","route":"/foo","statusCode":200,
111
- "time":"2018-01-01T00:49:00\+00:00","count":1,"sum":60000.0,
112
- "sumsq":3600000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB"}\]}
113
- \z|x
114
- )
115
- ).to have_been_made
116
- end
117
-
118
- it "returns a promise" do
119
- promise = subject.notify_request(
120
- method: 'GET',
121
- route: '/foo',
122
- status_code: 200,
123
- start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
124
- )
125
- sleep 0.2
126
- expect(promise).to be_an(Airbrake::Promise)
127
- expect(promise.value).to eq('' => nil)
128
- end
129
- end
130
- end