airbrake-ruby 4.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/lib/airbrake-ruby/async_sender.rb +22 -96
  3. data/lib/airbrake-ruby/backtrace.rb +8 -7
  4. data/lib/airbrake-ruby/benchmark.rb +39 -0
  5. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  6. data/lib/airbrake-ruby/config/processor.rb +84 -0
  7. data/lib/airbrake-ruby/config/validator.rb +9 -3
  8. data/lib/airbrake-ruby/config.rb +76 -20
  9. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  10. data/lib/airbrake-ruby/file_cache.rb +6 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  12. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  13. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  14. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  15. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
  16. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  17. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  18. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  19. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
  21. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  22. data/lib/airbrake-ruby/filters/sql_filter.rb +30 -6
  23. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  24. data/lib/airbrake-ruby/filters/thread_filter.rb +4 -2
  25. data/lib/airbrake-ruby/grouppable.rb +12 -0
  26. data/lib/airbrake-ruby/ignorable.rb +1 -0
  27. data/lib/airbrake-ruby/inspectable.rb +2 -2
  28. data/lib/airbrake-ruby/loggable.rb +2 -2
  29. data/lib/airbrake-ruby/mergeable.rb +12 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +48 -0
  31. data/lib/airbrake-ruby/notice.rb +10 -20
  32. data/lib/airbrake-ruby/notice_notifier.rb +23 -42
  33. data/lib/airbrake-ruby/performance_breakdown.rb +52 -0
  34. data/lib/airbrake-ruby/performance_notifier.rb +126 -49
  35. data/lib/airbrake-ruby/promise.rb +1 -0
  36. data/lib/airbrake-ruby/query.rb +26 -11
  37. data/lib/airbrake-ruby/queue.rb +65 -0
  38. data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
  39. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  40. data/lib/airbrake-ruby/request.rb +20 -6
  41. data/lib/airbrake-ruby/stashable.rb +15 -0
  42. data/lib/airbrake-ruby/stat.rb +34 -24
  43. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  44. data/lib/airbrake-ruby/tdigest.rb +43 -58
  45. data/lib/airbrake-ruby/thread_pool.rb +138 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +10 -4
  48. data/lib/airbrake-ruby/version.rb +11 -1
  49. data/lib/airbrake-ruby.rb +219 -53
  50. data/spec/airbrake_spec.rb +428 -9
  51. data/spec/async_sender_spec.rb +26 -110
  52. data/spec/backtrace_spec.rb +44 -44
  53. data/spec/benchmark_spec.rb +33 -0
  54. data/spec/code_hunk_spec.rb +11 -11
  55. data/spec/config/processor_spec.rb +209 -0
  56. data/spec/config/validator_spec.rb +23 -6
  57. data/spec/config_spec.rb +77 -7
  58. data/spec/deploy_notifier_spec.rb +2 -2
  59. data/spec/{file_cache.rb → file_cache_spec.rb} +2 -4
  60. data/spec/filter_chain_spec.rb +28 -1
  61. data/spec/filters/dependency_filter_spec.rb +1 -1
  62. data/spec/filters/gem_root_filter_spec.rb +9 -9
  63. data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
  64. data/spec/filters/git_repository_filter.rb +1 -1
  65. data/spec/filters/git_revision_filter_spec.rb +13 -11
  66. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
  67. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
  68. data/spec/filters/root_directory_filter_spec.rb +9 -9
  69. data/spec/filters/sql_filter_spec.rb +110 -55
  70. data/spec/filters/system_exit_filter_spec.rb +1 -1
  71. data/spec/filters/thread_filter_spec.rb +33 -31
  72. data/spec/fixtures/project_root/code.rb +9 -9
  73. data/spec/loggable_spec.rb +17 -0
  74. data/spec/monotonic_time_spec.rb +23 -0
  75. data/spec/{notice_notifier_spec → notice_notifier}/options_spec.rb +19 -21
  76. data/spec/notice_notifier_spec.rb +20 -80
  77. data/spec/notice_spec.rb +9 -11
  78. data/spec/performance_breakdown_spec.rb +11 -0
  79. data/spec/performance_notifier_spec.rb +360 -85
  80. data/spec/query_spec.rb +11 -0
  81. data/spec/queue_spec.rb +18 -0
  82. data/spec/remote_settings/settings_data_spec.rb +365 -0
  83. data/spec/remote_settings_spec.rb +230 -0
  84. data/spec/request_spec.rb +9 -0
  85. data/spec/response_spec.rb +8 -8
  86. data/spec/spec_helper.rb +9 -13
  87. data/spec/stashable_spec.rb +23 -0
  88. data/spec/stat_spec.rb +17 -15
  89. data/spec/sync_sender_spec.rb +14 -12
  90. data/spec/tdigest_spec.rb +6 -6
  91. data/spec/thread_pool_spec.rb +187 -0
  92. data/spec/timed_trace_spec.rb +125 -0
  93. data/spec/truncator_spec.rb +12 -12
  94. metadata +55 -18
@@ -27,6 +27,7 @@ module Airbrake
27
27
  @git_path = File.join(root_directory, '.git')
28
28
  @weight = 116
29
29
  @last_checkout = nil
30
+ @deploy_username = ENV['AIRBRAKE_DEPLOY_USERNAME']
30
31
  end
31
32
 
32
33
  # @macro call_filter
@@ -40,32 +41,31 @@ module Airbrake
40
41
 
41
42
  return unless File.exist?(@git_path)
42
43
  return unless (checkout = last_checkout)
44
+
43
45
  notice[:context][:lastCheckout] = checkout
44
46
  end
45
47
 
46
48
  private
47
49
 
48
- # rubocop:disable Metrics/AbcSize
49
50
  def last_checkout
50
51
  return unless (line = last_checkout_line)
51
52
 
52
53
  parts = line.chomp.split("\t").first.split(' ')
53
54
  if parts.size < MIN_HEAD_COLS
54
55
  logger.error(
55
- "#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}"
56
+ "#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}",
56
57
  )
57
58
  return
58
59
  end
59
60
 
60
61
  author = parts[2..-4]
61
62
  @last_checkout = {
62
- username: author[0..1].join(' '),
63
+ username: @deploy_username || author[0..1].join(' '),
63
64
  email: parts[-3][1..-2],
64
65
  revision: parts[1],
65
- time: timestamp(parts[-2].to_i)
66
+ time: timestamp(parts[-2].to_i),
66
67
  }
67
68
  end
68
- # rubocop:enable Metrics/AbcSize
69
69
 
70
70
  def last_checkout_line
71
71
  head_path = File.join(@git_path, 'logs', 'HEAD')
@@ -18,6 +18,7 @@ module Airbrake
18
18
  # @macro call_filter
19
19
  def call(notice)
20
20
  return if notice[:context].key?(:repository)
21
+
21
22
  attach_repository(notice)
22
23
  end
23
24
 
@@ -39,6 +40,7 @@ module Airbrake
39
40
  end
40
41
 
41
42
  return unless @repository
43
+
42
44
  notice[:context][:repository] = @repository
43
45
  end
44
46
 
@@ -46,6 +48,7 @@ module Airbrake
46
48
 
47
49
  def detect_git_version
48
50
  return unless which('git')
51
+
49
52
  Gem::Version.new(`git --version`.split[2])
50
53
  end
51
54
 
@@ -30,6 +30,7 @@ module Airbrake
30
30
 
31
31
  @revision = find_revision
32
32
  return unless @revision
33
+
33
34
  notice[:context][:revision] = @revision
34
35
  end
35
36
 
@@ -41,6 +42,7 @@ module Airbrake
41
42
 
42
43
  head = File.read(head_path)
43
44
  return head unless head.start_with?(PREFIX)
45
+
44
46
  head = head.chomp[PREFIX.size..-1]
45
47
 
46
48
  ref_path = File.join(@git_path, head)
@@ -4,7 +4,7 @@ module Airbrake
4
4
  # notice, but specified keys.
5
5
  #
6
6
  # @example
7
- # filter = Airbrake::Filters::KeysBlacklist.new(
7
+ # filter = Airbrake::Filters::KeysAllowlist.new(
8
8
  # [:email, /credit/i, 'password']
9
9
  # )
10
10
  # airbrake.add_filter(filter)
@@ -22,9 +22,9 @@ module Airbrake
22
22
  # # email: 'john@example.com',
23
23
  # # account_id: 42 }
24
24
  #
25
- # @see KeysBlacklist
25
+ # @see KeysBlocklist
26
26
  # @see KeysFilter
27
- class KeysWhitelist
27
+ class KeysAllowlist
28
28
  include KeysFilter
29
29
 
30
30
  def initialize(*)
@@ -4,7 +4,7 @@ module Airbrake
4
4
  # list of parameters in the payload of a notice.
5
5
  #
6
6
  # @example
7
- # filter = Airbrake::Filters::KeysBlacklist.new(
7
+ # filter = Airbrake::Filters::KeysBlocklist.new(
8
8
  # [:email, /credit/i, 'password']
9
9
  # )
10
10
  # airbrake.add_filter(filter)
@@ -22,10 +22,10 @@ module Airbrake
22
22
  # # email: '[Filtered]',
23
23
  # # credit_card: '[Filtered]' }
24
24
  #
25
- # @see KeysWhitelist
25
+ # @see KeysAllowlist
26
26
  # @see KeysFilter
27
27
  # @api private
28
- class KeysBlacklist
28
+ class KeysBlocklist
29
29
  include KeysFilter
30
30
 
31
31
  def initialize(*)
@@ -7,8 +7,8 @@ module Airbrake
7
7
  # class that includes this module must implement.
8
8
  #
9
9
  # @see Notice
10
- # @see KeysWhitelist
11
- # @see KeysBlacklist
10
+ # @see KeysAllowlist
11
+ # @see KeysBlocklist
12
12
  # @api private
13
13
  module KeysFilter
14
14
  # @return [String] The label to replace real values of filtered payload
@@ -19,19 +19,30 @@ module Airbrake
19
19
  VALID_PATTERN_CLASSES = [String, Symbol, Regexp].freeze
20
20
 
21
21
  # @return [Array<Symbol>] parts of a Notice's payload that can be modified
22
- # by blacklist/whitelist filters
22
+ # by blocklist/allowlist filters
23
23
  FILTERABLE_KEYS = %i[environment session params].freeze
24
24
 
25
25
  # @return [Array<Symbol>] parts of a Notice's *context* payload that can
26
- # be modified by blacklist/whitelist filters
27
- FILTERABLE_CONTEXT_KEYS = %i[user headers].freeze
26
+ # be modified by blocklist/allowlist filters
27
+ FILTERABLE_CONTEXT_KEYS = %i[
28
+ user
29
+
30
+ # Provided by Airbrake::Rack::HttpHeadersFilter
31
+ headers
32
+ referer
33
+ httpMethod
34
+
35
+ # Provided by Airbrake::Rack::ContextFilter
36
+ userAddr
37
+ userAgent
38
+ ].freeze
28
39
 
29
40
  include Loggable
30
41
 
31
42
  # @return [Integer]
32
43
  attr_reader :weight
33
44
 
34
- # Creates a new KeysBlacklist or KeysWhitelist filter that uses the given
45
+ # Creates a new KeysBlocklist or KeysAllowlist filter that uses the given
35
46
  # +patterns+ for filtering a notice's payload.
36
47
  #
37
48
  # @param [Array<String,Regexp,Symbol>] patterns
@@ -53,10 +64,14 @@ module Airbrake
53
64
  validate_patterns
54
65
  end
55
66
 
56
- FILTERABLE_KEYS.each { |key| filter_hash(notice[key]) }
67
+ FILTERABLE_KEYS.each do |key|
68
+ notice[key] = filter_hash(notice[key])
69
+ end
70
+
57
71
  FILTERABLE_CONTEXT_KEYS.each { |key| filter_context_key(notice, key) }
58
72
 
59
73
  return unless notice[:context][:url]
74
+
60
75
  filter_url(notice)
61
76
  end
62
77
 
@@ -70,26 +85,26 @@ module Airbrake
70
85
  def filter_hash(hash)
71
86
  return hash unless hash.is_a?(Hash)
72
87
 
88
+ hash_copy = hash.dup
89
+
73
90
  hash.each_key do |key|
74
91
  if should_filter?(key.to_s)
75
- hash[key] = FILTERED
76
- elsif hash[key].is_a?(Hash)
77
- filter_hash(hash[key])
92
+ hash_copy[key] = FILTERED
93
+ elsif hash_copy[key].is_a?(Hash)
94
+ hash_copy[key] = filter_hash(hash_copy[key])
78
95
  elsif hash[key].is_a?(Array)
79
- hash[key].each { |h| filter_hash(h) }
96
+ hash_copy[key].each_with_index do |h, i|
97
+ hash_copy[key][i] = filter_hash(h)
98
+ end
80
99
  end
81
100
  end
101
+
102
+ hash_copy
82
103
  end
83
104
 
84
105
  def filter_url_params(url)
85
106
  url.query = Hash[URI.decode_www_form(url.query)].map do |key, val|
86
- # Ruby < 2.2 raises InvalidComponentError if the query contains
87
- # invalid characters, so be sure to escape individual components.
88
- if should_filter?(key)
89
- "#{URI.encode_www_form_component(key)}=[Filtered]"
90
- else
91
- "#{URI.encode_www_form_component(key)}=#{URI.encode_www_form_component(val)}"
92
- end
107
+ should_filter?(key) ? "#{key}=[Filtered]" : "#{key}=#{val}"
93
108
  end.join('&')
94
109
 
95
110
  url.to_s
@@ -103,6 +118,7 @@ module Airbrake
103
118
  end
104
119
 
105
120
  return unless url.query
121
+
106
122
  notice[:context][:url] = filter_url_params(url)
107
123
  end
108
124
 
@@ -111,6 +127,7 @@ module Airbrake
111
127
 
112
128
  @patterns = @patterns.flat_map do |pattern|
113
129
  next(pattern) unless pattern.respond_to?(:call)
130
+
114
131
  pattern.call
115
132
  end
116
133
  end
@@ -124,14 +141,16 @@ module Airbrake
124
141
 
125
142
  logger.error(
126
143
  "#{LOG_LABEL} one of the patterns in #{self.class} is invalid. " \
127
- "Known patterns: #{@patterns}"
144
+ "Known patterns: #{@patterns}",
128
145
  )
129
146
  end
130
147
 
131
148
  def filter_context_key(notice, key)
132
149
  return unless notice[:context][key]
133
150
  return if notice[:context][key] == FILTERED
134
- return filter_hash(notice[:context][key]) unless should_filter?(key)
151
+ unless should_filter?(key)
152
+ return notice[:context][key] = filter_hash(notice[:context][key])
153
+ end
135
154
 
136
155
  notice[:context][key] = FILTERED
137
156
  end
@@ -19,6 +19,7 @@ module Airbrake
19
19
  notice[:errors].each do |error|
20
20
  error[:backtrace].each do |frame|
21
21
  next unless (file = frame[:file])
22
+
22
23
  file.sub!(/\A#{@root_directory}/, PROJECT_ROOT_LABEL)
23
24
  end
24
25
  end
@@ -23,7 +23,7 @@ module Airbrake
23
23
 
24
24
  # @return [Hash{Symbol=>Regexp}] matchers for certain features of SQL
25
25
  ALL_FEATURES = {
26
- # rubocop:disable Metrics/LineLength
26
+ # rubocop:disable Layout/LineLength
27
27
  single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
28
28
  double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
29
29
  dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
@@ -33,10 +33,14 @@ module Airbrake
33
33
  hexadecimal_literals: /0x[0-9a-fA-F]+/,
34
34
  comments: /(?:#|--).*?(?=\r|\n|$)/i,
35
35
  multi_line_comments: %r{/\*(?:[^/]|/[^*])*?(?:\*/|/\*.*)},
36
- oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/
37
- # rubocop:enable Metrics/LineLength
36
+ oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/,
37
+ # rubocop:enable Layout/LineLength
38
38
  }.freeze
39
39
 
40
+ # @return [Regexp] the regexp that is applied after the feature regexps
41
+ # were used
42
+ POST_FILTER = /(?<=[values|in ]\().+(?=\))/i.freeze
43
+
40
44
  # @return [Hash{Symbol=>Array<Symbol>}] a set of features that corresponds
41
45
  # to a certain dialect
42
46
  DIALECT_FEATURES = {
@@ -60,7 +64,7 @@ module Airbrake
60
64
  cassandra: %i[
61
65
  single_quotes uuids numeric_literals boolean_literals
62
66
  hexadecimal_literals comments multi_line_comments
63
- ].freeze
67
+ ].freeze,
64
68
  }.freeze
65
69
 
66
70
  # @return [Hash{Symbol=>Regexp}] a set of regexps to check for unmatches
@@ -72,9 +76,22 @@ module Airbrake
72
76
  sqlite: %r{'|/\*|\*/},
73
77
  cassandra: %r{'|/\*|\*/},
74
78
  oracle: %r{'|/\*|\*/},
75
- oracle_enhanced: %r{'|/\*|\*/}
79
+ oracle_enhanced: %r{'|/\*|\*/},
76
80
  }.freeze
77
81
 
82
+ # @return [Array<Regexp>] the list of queries to be ignored
83
+ IGNORED_QUERIES = [
84
+ /\ACOMMIT/i,
85
+ /\ABEGIN/i,
86
+ /\ASET/i,
87
+ /\ASHOW/i,
88
+ /\AWITH/i,
89
+ /FROM pg_attribute/i,
90
+ /FROM pg_index/i,
91
+ /FROM pg_class/i,
92
+ /FROM pg_type/i,
93
+ ].freeze
94
+
78
95
  def initialize(dialect)
79
96
  @dialect =
80
97
  case dialect
@@ -95,7 +112,14 @@ module Airbrake
95
112
  def call(resource)
96
113
  return unless resource.respond_to?(:query)
97
114
 
98
- q = resource.query.gsub(@regexp, FILTERED)
115
+ query = resource.query
116
+ if IGNORED_QUERIES.any? { |q| q =~ query }
117
+ resource.ignore!
118
+ return
119
+ end
120
+
121
+ q = query.gsub(@regexp, FILTERED)
122
+ q.gsub!(POST_FILTER, FILTERED) if q =~ POST_FILTER
99
123
  q = ERROR_MSG if UNMATCHED_PAIR[@dialect] =~ q
100
124
  resource.query = q
101
125
  end
@@ -16,6 +16,7 @@ module Airbrake
16
16
  # @macro call_filter
17
17
  def call(notice)
18
18
  return if notice[:errors].none? { |error| error[:type] == SYSTEM_EXIT_TYPE }
19
+
19
20
  notice.ignore!
20
21
  end
21
22
  end
@@ -16,7 +16,7 @@ module Airbrake
16
16
  String,
17
17
  Symbol,
18
18
  Regexp,
19
- Numeric
19
+ Numeric,
20
20
  ].freeze
21
21
 
22
22
  # Variables starting with this prefix are not attached to a notice.
@@ -56,6 +56,7 @@ module Airbrake
56
56
  def thread_variables(th)
57
57
  th.thread_variables.map.with_object({}) do |var, h|
58
58
  next if var.to_s.start_with?(IGNORE_PREFIX)
59
+
59
60
  h[var] = sanitize_value(th.thread_variable_get(var))
60
61
  end
61
62
  end
@@ -63,6 +64,7 @@ module Airbrake
63
64
  def fiber_variables(th)
64
65
  th.keys.map.with_object({}) do |key, h|
65
66
  next if key.to_s.start_with?(IGNORE_PREFIX)
67
+
66
68
  h[key] = sanitize_value(th[key])
67
69
  end
68
70
  end
@@ -72,7 +74,7 @@ module Airbrake
72
74
  thread_info[:group] = th.group.list.map(&:inspect)
73
75
  thread_info[:priority] = th.priority
74
76
 
75
- thread_info[:safe_level] = th.safe_level unless Airbrake::JRUBY
77
+ thread_info[:safe_level] = th.safe_level if Airbrake::HAS_SAFE_LEVEL
76
78
  end
77
79
 
78
80
  def sanitize_value(value)
@@ -0,0 +1,12 @@
1
+ module Airbrake
2
+ # Grouppable adds the `#groups` method, so that we don't need to define it in
3
+ # all of performance models every time we add a model without groups.
4
+ #
5
+ # @since 4.9.0
6
+ # @api private
7
+ module Grouppable
8
+ def groups
9
+ {}
10
+ end
11
+ end
12
+ end
@@ -38,6 +38,7 @@ module Airbrake
38
38
  # @raise [Airbrake::Error] when instance is ignored
39
39
  def raise_if_ignored
40
40
  return unless ignored?
41
+
41
42
  raise Airbrake::Error, "cannot access ignored #{self.class}"
42
43
  end
43
44
  end
@@ -21,7 +21,7 @@ module Airbrake
21
21
  project_id: @config.project_id,
22
22
  project_key: @config.project_key,
23
23
  host: @config.host,
24
- filter_chain: @filter_chain.inspect
24
+ filter_chain: @filter_chain.inspect,
25
25
  )
26
26
  end
27
27
 
@@ -30,7 +30,7 @@ module Airbrake
30
30
  q.text("#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(16, '0')} ")
31
31
  q.text(
32
32
  "project_id=\"#{@config.project_id}\" project_key=\"#{@config.project_key}\" " \
33
- "host=\"#{@config.host}\" filter_chain="
33
+ "host=\"#{@config.host}\" filter_chain=",
34
34
  )
35
35
  q.pp(@filter_chain)
36
36
  q.text('>')
@@ -17,12 +17,12 @@ module Airbrake
17
17
  # @api private
18
18
  module Loggable
19
19
  class << self
20
- # @param [Logger] logger
20
+ # @return [Logger]
21
21
  attr_writer :instance
22
22
 
23
23
  # @return [Logger]
24
24
  def instance
25
- @instance ||= ::Logger.new(File::NULL)
25
+ @instance ||= ::Logger.new(File::NULL).tap { |l| l.level = ::Logger::WARN }
26
26
  end
27
27
  end
28
28
 
@@ -0,0 +1,12 @@
1
+ module Airbrake
2
+ # Mergeable adds the `#merge` method, so that we don't need to define it in
3
+ # all of performance models every time we add a model.
4
+ #
5
+ # @since 4.9.0
6
+ # @api private
7
+ module Mergeable
8
+ def merge(_other)
9
+ nil
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,48 @@
1
+ module Airbrake
2
+ # MonotonicTime is a helper for getting monotonic time suitable for
3
+ # performance measurements. It guarantees that the time is strictly linearly
4
+ # increasing (unlike realtime).
5
+ #
6
+ # @example
7
+ # MonotonicTime.time_in_ms #=> 287138801.144576
8
+ #
9
+ # @see http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
10
+ # @since v4.2.4
11
+ # @api private
12
+ module MonotonicTime
13
+ class << self
14
+ # @return [Integer] current monotonic time in milliseconds
15
+ def time_in_ms
16
+ time_in_nanoseconds / (10.0**6)
17
+ end
18
+
19
+ # @return [Integer] current monotonic time in seconds
20
+ def time_in_s
21
+ time_in_nanoseconds / (10.0**9)
22
+ end
23
+
24
+ private
25
+
26
+ if defined?(Process::CLOCK_MONOTONIC)
27
+
28
+ def time_in_nanoseconds
29
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
30
+ end
31
+
32
+ elsif RUBY_ENGINE == 'jruby'
33
+
34
+ def time_in_nanoseconds
35
+ java.lang.System.nanoTime
36
+ end
37
+
38
+ else
39
+
40
+ def time_in_nanoseconds
41
+ time = Time.now
42
+ time.to_i * (10**9) + time.nsec
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -4,19 +4,12 @@ module Airbrake
4
4
  #
5
5
  # @since v1.0.0
6
6
  class Notice
7
- # @return [Hash{Symbol=>String}] the information about the notifier library
8
- NOTIFIER = {
9
- name: 'airbrake-ruby'.freeze,
10
- version: Airbrake::AIRBRAKE_RUBY_VERSION,
11
- url: 'https://github.com/airbrake/airbrake-ruby'.freeze
12
- }.freeze
13
-
14
7
  # @return [Hash{Symbol=>String,Hash}] the information to be displayed in the
15
8
  # Context tab in the dashboard
16
9
  CONTEXT = {
17
10
  os: RUBY_PLATFORM,
18
11
  language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
19
- notifier: NOTIFIER
12
+ notifier: Airbrake::NOTIFIER_INFO,
20
13
  }.freeze
21
14
 
22
15
  # @return [Integer] the maxium size of the JSON payload in bytes
@@ -32,7 +25,7 @@ module Airbrake
32
25
  IOError,
33
26
  NotImplementedError,
34
27
  JSON::GeneratorError,
35
- Encoding::UndefinedConversionError
28
+ Encoding::UndefinedConversionError,
36
29
  ].freeze
37
30
 
38
31
  # @return [Array<Symbol>] the list of keys that can be be overwritten with
@@ -51,11 +44,7 @@ module Airbrake
51
44
 
52
45
  include Ignorable
53
46
  include Loggable
54
-
55
- # @since v1.7.0
56
- # @return [Hash{Symbol=>Object}] the hash with arbitrary objects to be used
57
- # in filters
58
- attr_reader :stash
47
+ include Stashable
59
48
 
60
49
  # @api private
61
50
  def initialize(exception, params = {})
@@ -64,13 +53,14 @@ module Airbrake
64
53
  errors: NestedException.new(exception).as_json,
65
54
  context: context,
66
55
  environment: {
67
- program_name: $PROGRAM_NAME
56
+ program_name: $PROGRAM_NAME,
68
57
  },
69
58
  session: {},
70
- params: params
59
+ params: params,
71
60
  }
72
- @stash = { exception: exception }
73
61
  @truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
62
+
63
+ stash[:exception] = exception
74
64
  end
75
65
 
76
66
  # Converts the notice to JSON. Calls +to_json+ on each object inside
@@ -79,7 +69,7 @@ module Airbrake
79
69
  #
80
70
  # @return [Hash{String=>String}, nil]
81
71
  # @api private
82
- def to_json
72
+ def to_json(*_args)
83
73
  loop do
84
74
  begin
85
75
  json = @payload.to_json
@@ -141,7 +131,7 @@ module Airbrake
141
131
  # Make sure we always send hostname.
142
132
  hostname: HOSTNAME,
143
133
 
144
- severity: DEFAULT_SEVERITY
134
+ severity: DEFAULT_SEVERITY,
145
135
  }.merge(CONTEXT).delete_if { |_key, val| val.nil? || val.empty? }
146
136
  end
147
137
 
@@ -155,7 +145,7 @@ module Airbrake
155
145
  logger.error(
156
146
  "#{LOG_LABEL} truncation failed. File an issue at " \
157
147
  "https://github.com/airbrake/airbrake-ruby " \
158
- "and attach the following payload: #{@payload}"
148
+ "and attach the following payload: #{@payload}",
159
149
  )
160
150
  end
161
151