airbrake-ruby 2.13.0.pre.1 → 3.0.0.rc.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ae033f8b21b79ecf3902d570014f379e76b4c16
4
- data.tar.gz: 131f71f8a92906a05aadf1ed3dbd4ffc35753162
3
+ metadata.gz: 0aef633a546613b3d46a1669505df654fcc4348b
4
+ data.tar.gz: c35b98d3ffa46c7ee3fe976546585f27ca6d1bd1
5
5
  SHA512:
6
- metadata.gz: 62a0b41680b377eefd25a0e9433ada861ce92841d954477de949f3ee16bbb32a198d3133433b0783e7e4f747a7c7bff39e76dcf4744bfbb8da8fffbcd54c4a6d
7
- data.tar.gz: 6864d904a38696d892dcca249ddeff4b1c973e61735ef792a19518bf1e17c430fc9f1bd09ea59fe4c6d7685d6d1a1e911848a657a6647199bb3208d25183192c
6
+ metadata.gz: 820453ab4d27d72c7f356958af555bc361507ca48cde546e6bc15f77cb32077c2cc1623d6a0afe882c37b9ade81888b58ddaadbd2c7f17b820a6011ab666877d
7
+ data.tar.gz: fc7078da83d4cd3d2e4e4c2602e5441e9ebd6751410cfdf8ffb6c4998f1832790a3ccfb41aa97a39470315b60a965b0b91059bc4914c366f923e986842f8adf1
@@ -76,10 +76,6 @@ module Airbrake
76
76
  # @return [String] the label to be prepended to the log output
77
77
  LOG_LABEL = '**Airbrake:'.freeze
78
78
 
79
- # @return [Boolean] true if current Ruby is Ruby 2.0.*. The result is used
80
- # for special cases where we need to work around older implementations
81
- RUBY_20 = RUBY_VERSION.start_with?('2.0')
82
-
83
79
  # @return [Boolean] true if current Ruby is JRuby. The result is used for
84
80
  # special cases where we need to work around older implementations
85
81
  JRUBY = (RUBY_ENGINE == 'jruby')
@@ -366,7 +362,7 @@ module Airbrake
366
362
  # milliseconds
367
363
  # @param [Time] time When the request happened
368
364
  # @return [void]
369
- # @since v2.13.0
365
+ # @since v3.0.0
370
366
  def inc_request(method, route, status_code, dur, time)
371
367
  @notifiers[:default].inc_request(method, route, status_code, dur, time)
372
368
  end
@@ -83,10 +83,6 @@ module Airbrake
83
83
  #{RUBY}
84
84
  )
85
85
  \z/x
86
-
87
- # @return [Regexp] +EXECJS+ pattern without named captures and
88
- # uncommon frames
89
- EXECJS_SIMPLIFIED = /\A.+ \(.+:\d+:\d+\)\z/
90
86
  end
91
87
 
92
88
  # @return [Integer] how many first frames should include code hunks
@@ -136,24 +132,13 @@ module Airbrake
136
132
  defined?(OCIError) && exception.is_a?(OCIError)
137
133
  end
138
134
 
139
- # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
140
135
  def execjs_exception?(exception)
141
136
  return false unless defined?(ExecJS::RuntimeError)
142
137
  return true if exception.is_a?(ExecJS::RuntimeError)
143
-
144
- if Airbrake::RUBY_20
145
- # Ruby <2.1 doesn't support Exception#cause. We work around this by
146
- # parsing backtraces. It's slow, so we check only a few first frames.
147
- exception.backtrace[0..2].each do |frame|
148
- return true if frame =~ Patterns::EXECJS_SIMPLIFIED
149
- end
150
- elsif exception.cause && exception.cause.is_a?(ExecJS::RuntimeError)
151
- return true
152
- end
138
+ return true if exception.cause && exception.cause.is_a?(ExecJS::RuntimeError)
153
139
 
154
140
  false
155
141
  end
156
- # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
157
142
 
158
143
  def stack_frame(config, regexp, stackframe)
159
144
  if (match = match_frame(regexp, stackframe))
@@ -86,7 +86,7 @@ module Airbrake
86
86
  # @return [Integer] how many seconds to wait before sending collected route
87
87
  # stats
88
88
  # @api public
89
- # @since v2.13.0
89
+ # @since v3.0.0
90
90
  attr_accessor :route_stats_flush_period
91
91
 
92
92
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
@@ -18,9 +18,12 @@ module Airbrake
18
18
  # @return [Integer]
19
19
  DEFAULT_WEIGHT = 0
20
20
 
21
- def initialize
21
+ def initialize(config, context)
22
+ @config = config
23
+ @context = context
22
24
  @filters = []
23
25
  DEFAULT_FILTERS.each { |f| add_filter(f.new) }
26
+ add_default_filters
24
27
  end
25
28
 
26
29
  # Adds a filter to the filter chain. Sorts filters by weight.
@@ -44,5 +47,57 @@ module Airbrake
44
47
  filter.call(notice)
45
48
  end
46
49
  end
50
+
51
+ # @return [String] customized inspect to lessen the amount of clutter
52
+ def inspect
53
+ @filters.map(&:class)
54
+ end
55
+
56
+ # @return [String] {#inspect} for PrettyPrint
57
+ def pretty_print(q)
58
+ q.text('[')
59
+
60
+ # Make nesting of the first element consistent on JRuby and MRI.
61
+ q.nest(2) { q.breakable }
62
+
63
+ q.nest(2) do
64
+ q.seplist(@filters) { |f| q.pp(f.class) }
65
+ end
66
+ q.text(']')
67
+ end
68
+
69
+ private
70
+
71
+ # rubocop:disable Metrics/AbcSize
72
+ def add_default_filters
73
+ if (whitelist_keys = @config.whitelist_keys).any?
74
+ add_filter(
75
+ Airbrake::Filters::KeysWhitelist.new(@config.logger, whitelist_keys)
76
+ )
77
+ end
78
+
79
+ if (blacklist_keys = @config.blacklist_keys).any?
80
+ add_filter(
81
+ Airbrake::Filters::KeysBlacklist.new(@config.logger, blacklist_keys)
82
+ )
83
+ end
84
+
85
+ add_filter(Airbrake::Filters::ContextFilter.new(@context))
86
+ add_filter(Airbrake::Filters::ExceptionAttributesFilter.new(@config.logger))
87
+
88
+ return unless (root_directory = @config.root_directory)
89
+ [
90
+ Airbrake::Filters::RootDirectoryFilter,
91
+ Airbrake::Filters::GitRevisionFilter,
92
+ Airbrake::Filters::GitRepositoryFilter
93
+ ].each do |filter|
94
+ add_filter(filter.new(root_directory))
95
+ end
96
+
97
+ add_filter(
98
+ Airbrake::Filters::GitLastCheckoutFilter.new(@config.logger, root_directory)
99
+ )
100
+ end
101
+ # rubocop:enable Metrics/AbcSize
47
102
  end
48
103
  end
@@ -9,6 +9,12 @@ module Airbrake
9
9
  # @return [String] the label to be prepended to the log output
10
10
  LOG_LABEL = '**Airbrake:'.freeze
11
11
 
12
+ # @return [String] inspect output template
13
+ INSPECT_TEMPLATE =
14
+ "#<#{self}:0x%<id>s project_id=\"%<project_id>s\" " \
15
+ "project_key=\"%<project_key>s\" " \
16
+ "host=\"%<host>s\" filter_chain=%<filter_chain>s>".freeze
17
+
12
18
  # Creates a new Airbrake notifier with the given config options.
13
19
  #
14
20
  # @example Configuring with a Hash
@@ -30,13 +36,9 @@ module Airbrake
30
36
  raise Airbrake::Error, @config.validation_error_message unless @config.valid?
31
37
 
32
38
  @context = {}
33
-
34
- @filter_chain = FilterChain.new
35
- add_default_filters
36
-
39
+ @filter_chain = FilterChain.new(@config, @context)
37
40
  @async_sender = AsyncSender.new(@config)
38
41
  @sync_sender = SyncSender.new(@config)
39
-
40
42
  @route_sender = RouteSender.new(@config)
41
43
  end
42
44
 
@@ -102,6 +104,29 @@ module Airbrake
102
104
  @route_sender.inc_request(*args)
103
105
  end
104
106
 
107
+ # @return [String] customized inspect to lessen the amount of clutter
108
+ def inspect
109
+ format(
110
+ INSPECT_TEMPLATE,
111
+ id: (object_id << 1).to_s(16).rjust(16, '0'),
112
+ project_id: @config.project_id,
113
+ project_key: @config.project_key,
114
+ host: @config.host,
115
+ filter_chain: @filter_chain.inspect
116
+ )
117
+ end
118
+
119
+ # @return [String] {#inspect} for PrettyPrint
120
+ def pretty_print(q)
121
+ q.text("#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(16, '0')} ")
122
+ q.text(
123
+ "project_id=\"#{@config.project_id}\" project_key=\"#{@config.project_key}\" " \
124
+ "host=\"#{@config.host}\" filter_chain="
125
+ )
126
+ q.pp(@filter_chain)
127
+ q.text('>')
128
+ end
129
+
105
130
  private
106
131
 
107
132
  def convert_to_exception(ex)
@@ -152,39 +177,5 @@ module Airbrake
152
177
  return caller_copy if clean_bt.empty?
153
178
  clean_bt
154
179
  end
155
-
156
- # rubocop:disable Metrics/AbcSize
157
- def add_default_filters
158
- if (whitelist_keys = @config.whitelist_keys).any?
159
- @filter_chain.add_filter(
160
- Airbrake::Filters::KeysWhitelist.new(@config.logger, whitelist_keys)
161
- )
162
- end
163
-
164
- if (blacklist_keys = @config.blacklist_keys).any?
165
- @filter_chain.add_filter(
166
- Airbrake::Filters::KeysBlacklist.new(@config.logger, blacklist_keys)
167
- )
168
- end
169
-
170
- @filter_chain.add_filter(Airbrake::Filters::ContextFilter.new(@context))
171
- @filter_chain.add_filter(
172
- Airbrake::Filters::ExceptionAttributesFilter.new(@config.logger)
173
- )
174
-
175
- return unless (root_directory = @config.root_directory)
176
- [
177
- Airbrake::Filters::RootDirectoryFilter,
178
- Airbrake::Filters::GitRevisionFilter,
179
- Airbrake::Filters::GitRepositoryFilter
180
- ].each do |filter|
181
- @filter_chain.add_filter(filter.new(root_directory))
182
- end
183
-
184
- @filter_chain.add_filter(
185
- Airbrake::Filters::GitLastCheckoutFilter.new(@config.logger, root_directory)
186
- )
187
- end
188
- # rubocop:enable Metrics/AbcSize
189
180
  end
190
181
  end
@@ -23,7 +23,7 @@ module Airbrake
23
23
 
24
24
  begin
25
25
  case code
26
- when 200
26
+ when 200, 204
27
27
  logger.debug("#{LOG_LABEL} #{name} (#{code}): #{body}")
28
28
  { response.msg => response.body }
29
29
  when 201
@@ -1,20 +1,76 @@
1
+ require 'tdigest'
2
+ require 'base64'
3
+
1
4
  module Airbrake
2
5
  # RouteSender aggregates information about requests and periodically sends
3
6
  # collected data to Airbrake.
4
- # @since v2.13.0
7
+ # @since v3.0.0
5
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
+
6
51
  # The key that represents a route.
7
52
  RouteKey = Struct.new(:method, :route, :statusCode, :time)
8
53
 
9
54
  # RouteStat holds data that describes a route's performance.
10
- RouteStat = Struct.new(:count, :sum, :sumsq, :min, :max) do
55
+ RouteStat = Struct.new(:count, :sum, :sumsq, :tdigest) do
11
56
  # @param [Integer] count The number of requests
12
57
  # @param [Float] sum The sum of request duration in milliseconds
13
58
  # @param [Float] sumsq The squared sum of request duration in milliseconds
14
- # @param [Float] min The minimal request duration
15
- # @param [Float] max The maximum request duration
16
- def initialize(count: 0, sum: 0.0, sumsq: 0.0, min: 0.0, max: 0.0)
17
- super(count, sum, sumsq, min, max)
59
+ # @param [TDigest::TDigest] tdigest
60
+ def initialize(count: 0, sum: 0.0, sumsq: 0.0, tdigest: TDigest::TDigest.new)
61
+ super(count, sum, sumsq, tdigest)
62
+ end
63
+
64
+ # @return [Hash{String=>Object}] the route stat as a hash with compressed
65
+ # and serialized as binary base64 tdigest
66
+ def to_h
67
+ tdigest.compress!
68
+ {
69
+ 'count' => count,
70
+ 'sum' => sum,
71
+ 'sumsq' => sumsq,
72
+ 'tDigest' => Base64.strict_encode64(tdigest.as_small_bytes)
73
+ }
18
74
  end
19
75
  end
20
76
 
@@ -66,8 +122,7 @@ module Airbrake
66
122
  stat.sum += ms
67
123
  stat.sumsq += ms * ms
68
124
 
69
- stat.min = ms if ms < stat.min || stat.min == 0
70
- stat.max = ms if ms > stat.max
125
+ stat.tdigest.push(ms)
71
126
  end
72
127
 
73
128
  def schedule_flush(promise)
@@ -2,5 +2,5 @@
2
2
  # More information: http://semver.org/
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
- AIRBRAKE_RUBY_VERSION = '2.13.0.pre.1'.freeze
5
+ AIRBRAKE_RUBY_VERSION = '3.0.0.rc.2'.freeze
6
6
  end
@@ -248,6 +248,8 @@ RSpec.describe Airbrake::Backtrace do
248
248
  "/opt/rubies/ruby-2.3.1/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'"]
249
249
  end
250
250
 
251
+ let(:ex) { ExecJS::RuntimeError.new.tap { |e| e.set_backtrace(bt) } }
252
+
251
253
  let(:parsed_backtrace) do
252
254
  [{ file: '(execjs)', line: 6692, function: 'compile' },
253
255
  { file: '<anonymous>', line: 1, function: 'eval' },
@@ -261,31 +263,9 @@ RSpec.describe Airbrake::Backtrace do
261
263
  function: 'realtime' }]
262
264
  end
263
265
 
264
- context "when not on Ruby 2.0" do
265
- let(:ex) { ExecJS::RuntimeError.new.tap { |e| e.set_backtrace(bt) } }
266
-
267
- it "returns a properly formatted array of hashes" do
268
- stub_const('ExecJS::RuntimeError', AirbrakeTestError)
269
- stub_const('Airbrake::RUBY_20', false)
270
-
271
- expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
272
- end
273
- end
274
-
275
- context "when on Ruby 2.0" do
276
- context "and when exception's class isn't ExecJS" do
277
- let(:ex) do
278
- ActionView::Template::Error.new.tap { |e| e.set_backtrace(bt) }
279
- end
280
-
281
- it "returns a properly formatted array of hashes" do
282
- stub_const('ActionView::Template::Error', AirbrakeTestError)
283
- stub_const('ExecJS::RuntimeError', NameError)
284
- stub_const('Airbrake::RUBY_20', true)
285
-
286
- expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
287
- end
288
- end
266
+ it "returns a properly formatted array of hashes" do
267
+ stub_const('ExecJS::RuntimeError', AirbrakeTestError)
268
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
289
269
  end
290
270
  end
291
271
 
@@ -1,8 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Airbrake::FilterChain do
4
+ subject { described_class.new(config, {}) }
5
+
6
+ let(:config) { Airbrake::Config.new }
7
+
4
8
  let(:notice) do
5
- Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
9
+ Airbrake::Notice.new(config, AirbrakeTestError.new)
6
10
  end
7
11
 
8
12
  describe "#refine" do
@@ -459,5 +459,35 @@ RSpec.describe Airbrake::Notifier do
459
459
  subject.inc_request('GET', '/foo', 200, 1000, t)
460
460
  end
461
461
  end
462
+
463
+ describe "#inspect" do
464
+ it "displays object information" do
465
+ expect(subject.inspect).to match(/
466
+ #<Airbrake::Notifier:0x\w+\s
467
+ project_id="\d+"\s
468
+ project_key=".+"\s
469
+ host="http.+"\s
470
+ filter_chain=\[.+\]>
471
+ /x)
472
+ end
473
+ end
474
+
475
+ describe "#pretty_print" do
476
+ it "displays object information in a beautiful way" do
477
+ q = PP.new
478
+
479
+ # Guarding is needed to fix JRuby failure:
480
+ # NoMethodError: undefined method `[]' for nil:NilClass
481
+ q.guard_inspect_key { subject.pretty_print(q) }
482
+
483
+ expect(q.output).to match(/
484
+ #<Airbrake::Notifier:0x\w+\s
485
+ project_id="\d+"\s
486
+ project_key=".+"\s
487
+ host="http.+"\s
488
+ filter_chain=\[\n\s\s
489
+ /x)
490
+ end
491
+ end
462
492
  end
463
493
  # rubocop:enable Layout/DotPosition
@@ -5,7 +5,7 @@ RSpec.describe Airbrake::Response do
5
5
  let(:out) { StringIO.new }
6
6
  let(:logger) { Logger.new(out) }
7
7
 
8
- [200, 201].each do |code|
8
+ [200, 201, 204].each do |code|
9
9
  context "when response code is #{code}" do
10
10
  it "logs response body" do
11
11
  described_class.parse(OpenStruct.new(code: code, body: '{}'), logger)
@@ -48,10 +48,10 @@ RSpec.describe Airbrake::RouteSender do
48
48
  {"routes":\[
49
49
  {"method":"GET","route":"/foo","statusCode":200,
50
50
  "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":24.0,
51
- "sumsq":576.0,"min":24.0,"max":24.0},
51
+ "sumsq":576.0,"tDigest":"AAAAAkBZAAAAAAAAAAAAAUHAAAAB"},
52
52
  {"method":"GET","route":"/foo","statusCode":200,
53
53
  "time":"2018-01-01T00:01:00\+00:00","count":1,"sum":10.0,
54
- "sumsq":100.0,"min":10.0,"max":10.0}\]}
54
+ "sumsq":100.0,"tDigest":"AAAAAkBZAAAAAAAAAAAAAUEgAAAB"}\]}
55
55
  \z|x
56
56
  )
57
57
  ).to have_been_made
@@ -67,10 +67,10 @@ RSpec.describe Airbrake::RouteSender do
67
67
  {"routes":\[
68
68
  {"method":"GET","route":"/foo","statusCode":200,
69
69
  "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":24.0,
70
- "sumsq":576.0,"min":24.0,"max":24.0},
70
+ "sumsq":576.0,"tDigest":"AAAAAkBZAAAAAAAAAAAAAUHAAAAB"},
71
71
  {"method":"POST","route":"/foo","statusCode":200,
72
72
  "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":10.0,
73
- "sumsq":100.0,"min":10.0,"max":10.0}\]}
73
+ "sumsq":100.0,"tDigest":"AAAAAkBZAAAAAAAAAAAAAUEgAAAB"}\]}
74
74
  \z|x
75
75
  )
76
76
  ).to have_been_made
@@ -8,6 +8,7 @@ require 'pathname'
8
8
  require 'webrick'
9
9
  require 'English'
10
10
  require 'base64'
11
+ require 'pp'
11
12
 
12
13
  require 'helpers'
13
14
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.0.pre.1
4
+ version: 3.0.0.rc.2
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: 2018-10-26 00:00:00.000000000 Z
11
+ date: 2018-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tdigest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -104,12 +118,11 @@ description: |
104
118
  Airbrake Ruby is a plain Ruby notifier for Airbrake (https://airbrake.io), the
105
119
  leading exception reporting service. Airbrake Ruby provides minimalist API that
106
120
  enables the ability to send any Ruby exception to the Airbrake dashboard. The
107
- library is extremely lightweight, contains no dependencies and perfectly suits
108
- plain Ruby applications. For apps that are built with Rails, Sinatra or any
109
- other Rack-compliant web framework we offer the airbrake gem
110
- (https://github.com/airbrake/airbrake). It has additional features such as
111
- reporting of any unhandled exceptions automatically, integrations with Resque,
112
- Sidekiq, Delayed Job and many more.
121
+ library is extremely lightweight and it perfectly suits plain Ruby applications.
122
+ For apps that are built with Rails, Sinatra or any other Rack-compliant web
123
+ framework we offer the airbrake gem (https://github.com/airbrake/airbrake). It
124
+ has additional features such as reporting of any unhandled exceptions
125
+ automatically, integrations with Resque, Sidekiq, Delayed Job and many more.
113
126
  email: support@airbrake.io
114
127
  executables: []
115
128
  extensions: []
@@ -194,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
194
207
  requirements:
195
208
  - - ">="
196
209
  - !ruby/object:Gem::Version
197
- version: '2.0'
210
+ version: '2.1'
198
211
  required_rubygems_version: !ruby/object:Gem::Requirement
199
212
  requirements:
200
213
  - - ">"