airbrake-ruby 4.6.0 → 6.2.2

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 (107) hide show
  1. checksums.yaml +5 -5
  2. data/lib/airbrake-ruby/async_sender.rb +24 -109
  3. data/lib/airbrake-ruby/backlog.rb +123 -0
  4. data/lib/airbrake-ruby/backtrace.rb +8 -7
  5. data/lib/airbrake-ruby/benchmark.rb +1 -1
  6. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  7. data/lib/airbrake-ruby/config/processor.rb +77 -0
  8. data/lib/airbrake-ruby/config/validator.rb +9 -3
  9. data/lib/airbrake-ruby/config.rb +78 -19
  10. data/lib/airbrake-ruby/context.rb +51 -0
  11. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  12. data/lib/airbrake-ruby/file_cache.rb +6 -0
  13. data/lib/airbrake-ruby/filter_chain.rb +18 -1
  14. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  15. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  16. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +3 -3
  17. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  18. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +7 -7
  19. data/lib/airbrake-ruby/filters/git_repository_filter.rb +11 -2
  20. data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
  21. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  22. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  23. data/lib/airbrake-ruby/filters/keys_filter.rb +41 -22
  24. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  25. data/lib/airbrake-ruby/filters/sql_filter.rb +35 -11
  26. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  27. data/lib/airbrake-ruby/filters/thread_filter.rb +6 -5
  28. data/lib/airbrake-ruby/grouppable.rb +12 -0
  29. data/lib/airbrake-ruby/ignorable.rb +1 -2
  30. data/lib/airbrake-ruby/inspectable.rb +2 -2
  31. data/lib/airbrake-ruby/loggable.rb +1 -1
  32. data/lib/airbrake-ruby/mergeable.rb +12 -0
  33. data/lib/airbrake-ruby/monotonic_time.rb +6 -1
  34. data/lib/airbrake-ruby/nested_exception.rb +22 -1
  35. data/lib/airbrake-ruby/notice.rb +12 -17
  36. data/lib/airbrake-ruby/notice_notifier.rb +15 -7
  37. data/lib/airbrake-ruby/performance_breakdown.rb +16 -9
  38. data/lib/airbrake-ruby/performance_notifier.rb +106 -54
  39. data/lib/airbrake-ruby/promise.rb +1 -0
  40. data/lib/airbrake-ruby/query.rb +20 -14
  41. data/lib/airbrake-ruby/queue.rb +65 -0
  42. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  43. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  44. data/lib/airbrake-ruby/remote_settings.rb +128 -0
  45. data/lib/airbrake-ruby/request.rb +14 -11
  46. data/lib/airbrake-ruby/response.rb +56 -5
  47. data/lib/airbrake-ruby/stat.rb +27 -34
  48. data/lib/airbrake-ruby/sync_sender.rb +43 -11
  49. data/lib/airbrake-ruby/tdigest.rb +52 -66
  50. data/lib/airbrake-ruby/thread_pool.rb +139 -0
  51. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  52. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  53. data/lib/airbrake-ruby/truncator.rb +10 -4
  54. data/lib/airbrake-ruby/version.rb +11 -1
  55. data/lib/airbrake-ruby.rb +157 -78
  56. metadata +122 -116
  57. data/spec/airbrake_spec.rb +0 -324
  58. data/spec/async_sender_spec.rb +0 -155
  59. data/spec/backtrace_spec.rb +0 -427
  60. data/spec/benchmark_spec.rb +0 -33
  61. data/spec/code_hunk_spec.rb +0 -115
  62. data/spec/config/validator_spec.rb +0 -184
  63. data/spec/config_spec.rb +0 -154
  64. data/spec/deploy_notifier_spec.rb +0 -48
  65. data/spec/file_cache.rb +0 -36
  66. data/spec/filter_chain_spec.rb +0 -92
  67. data/spec/filters/context_filter_spec.rb +0 -23
  68. data/spec/filters/dependency_filter_spec.rb +0 -12
  69. data/spec/filters/exception_attributes_filter_spec.rb +0 -50
  70. data/spec/filters/gem_root_filter_spec.rb +0 -41
  71. data/spec/filters/git_last_checkout_filter_spec.rb +0 -46
  72. data/spec/filters/git_repository_filter.rb +0 -61
  73. data/spec/filters/git_revision_filter_spec.rb +0 -126
  74. data/spec/filters/keys_blacklist_spec.rb +0 -225
  75. data/spec/filters/keys_whitelist_spec.rb +0 -194
  76. data/spec/filters/root_directory_filter_spec.rb +0 -39
  77. data/spec/filters/sql_filter_spec.rb +0 -219
  78. data/spec/filters/system_exit_filter_spec.rb +0 -14
  79. data/spec/filters/thread_filter_spec.rb +0 -277
  80. data/spec/fixtures/notroot.txt +0 -7
  81. data/spec/fixtures/project_root/code.rb +0 -221
  82. data/spec/fixtures/project_root/empty_file.rb +0 -0
  83. data/spec/fixtures/project_root/long_line.txt +0 -1
  84. data/spec/fixtures/project_root/short_file.rb +0 -3
  85. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +0 -5
  86. data/spec/helpers.rb +0 -9
  87. data/spec/ignorable_spec.rb +0 -14
  88. data/spec/inspectable_spec.rb +0 -45
  89. data/spec/monotonic_time_spec.rb +0 -12
  90. data/spec/nested_exception_spec.rb +0 -73
  91. data/spec/notice_notifier_spec/options_spec.rb +0 -259
  92. data/spec/notice_notifier_spec.rb +0 -356
  93. data/spec/notice_spec.rb +0 -296
  94. data/spec/performance_breakdown_spec.rb +0 -12
  95. data/spec/performance_notifier_spec.rb +0 -435
  96. data/spec/promise_spec.rb +0 -197
  97. data/spec/query_spec.rb +0 -11
  98. data/spec/request_spec.rb +0 -11
  99. data/spec/response_spec.rb +0 -88
  100. data/spec/spec_helper.rb +0 -100
  101. data/spec/stashable_spec.rb +0 -23
  102. data/spec/stat_spec.rb +0 -47
  103. data/spec/sync_sender_spec.rb +0 -133
  104. data/spec/tdigest_spec.rb +0 -230
  105. data/spec/time_truncate_spec.rb +0 -13
  106. data/spec/timed_trace_spec.rb +0 -125
  107. data/spec/truncator_spec.rb +0 -238
@@ -0,0 +1,51 @@
1
+ module Airbrake
2
+ # Represents a thread-safe Airbrake context object, which carries arbitrary
3
+ # information added via {Airbrake.merge_context} calls.
4
+ #
5
+ # @example
6
+ # Airbrake::Context.current.merge!(foo: 'bar')
7
+ #
8
+ # @api private
9
+ # @since v5.2.1
10
+ class Context
11
+ # Returns current, thread-local, context.
12
+ # @return [self]
13
+ def self.current
14
+ Thread.current[:airbrake_context] ||= new
15
+ end
16
+
17
+ def initialize
18
+ @mutex = Mutex.new
19
+ @context = {}
20
+ end
21
+
22
+ # Merges the given context with the current one.
23
+ #
24
+ # @param [Hash{Object=>Object}] other
25
+ # @return [void]
26
+ def merge!(other)
27
+ @mutex.synchronize do
28
+ @context.merge!(other)
29
+ end
30
+ end
31
+
32
+ # @return [Hash] duplicated Hash context
33
+ def to_h
34
+ @mutex.synchronize do
35
+ @context.dup
36
+ end
37
+ end
38
+
39
+ # @return [Hash] clears (resets) the current context
40
+ def clear
41
+ @mutex.synchronize do
42
+ @context.clear
43
+ end
44
+ end
45
+
46
+ # @return [Boolean] checks whether the context has any data
47
+ def empty?
48
+ @context.empty?
49
+ end
50
+ end
51
+ end
@@ -27,7 +27,7 @@ module Airbrake
27
27
  @sender.send(
28
28
  deploy_info,
29
29
  promise,
30
- URI.join(@config.host, "api/v4/projects/#{@config.project_id}/deploys")
30
+ URI.join(@config.error_host, "api/v4/projects/#{@config.project_id}/deploys"),
31
31
  )
32
32
 
33
33
  promise
@@ -40,6 +40,12 @@ module Airbrake
40
40
  data.empty?
41
41
  end
42
42
 
43
+ # @since v4.7.0
44
+ # @return [void]
45
+ def self.reset
46
+ @data = {}
47
+ end
48
+
43
49
  def self.data
44
50
  @data ||= {}
45
51
  end
@@ -57,7 +57,9 @@ module Airbrake
57
57
  # @return [void]
58
58
  # @since v3.1.0
59
59
  def delete_filter(filter_class)
60
+ # rubocop:disable Style/ClassEqualityComparison
60
61
  index = @filters.index { |f| f.class.name == filter_class.name }
62
+ # rubocop:enable Style/ClassEqualityComparison
61
63
  @filters.delete_at(index) if index
62
64
  end
63
65
 
@@ -70,13 +72,14 @@ module Airbrake
70
72
  def refine(notice)
71
73
  @filters.each do |filter|
72
74
  break if notice.ignored?
75
+
73
76
  filter.call(notice)
74
77
  end
75
78
  end
76
79
 
77
80
  # @return [String] customized inspect to lessen the amount of clutter
78
81
  def inspect
79
- @filters.map(&:class).to_s
82
+ filter_classes.to_s
80
83
  end
81
84
 
82
85
  # @return [String] {#inspect} for PrettyPrint
@@ -91,5 +94,19 @@ module Airbrake
91
94
  end
92
95
  q.text(']')
93
96
  end
97
+
98
+ # @param [Class] filter_class
99
+ # @return [Boolean] true if the current chain has an instance of the given
100
+ # class, false otherwise
101
+ # @since v4.14.0
102
+ def includes?(filter_class)
103
+ filter_classes.include?(filter_class)
104
+ end
105
+
106
+ private
107
+
108
+ def filter_classes
109
+ @filters.map(&:class)
110
+ end
94
111
  end
95
112
  end
@@ -9,8 +9,7 @@ module Airbrake
9
9
  # @return [Integer]
10
10
  attr_reader :weight
11
11
 
12
- def initialize(context)
13
- @context = context
12
+ def initialize
14
13
  @weight = 119
15
14
  @mutex = Mutex.new
16
15
  end
@@ -18,10 +17,10 @@ module Airbrake
18
17
  # @macro call_filter
19
18
  def call(notice)
20
19
  @mutex.synchronize do
21
- return if @context.empty?
20
+ return if Airbrake::Context.current.empty?
22
21
 
23
- notice[:params][:airbrake_context] = @context.dup
24
- @context.clear
22
+ notice[:params][:airbrake_context] = Airbrake::Context.current.to_h
23
+ Airbrake::Context.current.clear
25
24
  end
26
25
  end
27
26
  end
@@ -24,6 +24,7 @@ module Airbrake
24
24
 
25
25
  def git_version(spec)
26
26
  return unless spec.respond_to?(:git_version) || spec.git_version
27
+
27
28
  spec.git_version.to_s
28
29
  end
29
30
  end
@@ -13,7 +13,7 @@ module Airbrake
13
13
  end
14
14
 
15
15
  # @macro call_filter
16
- def call(notice)
16
+ def call(notice) # rubocop:disable Metrics/AbcSize
17
17
  exception = notice.stash[:exception]
18
18
  return unless exception.respond_to?(:to_airbrake)
19
19
 
@@ -22,13 +22,13 @@ module Airbrake
22
22
  attributes = exception.to_airbrake
23
23
  rescue StandardError => ex
24
24
  logger.error(
25
- "#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}"
25
+ "#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}",
26
26
  )
27
27
  end
28
28
 
29
29
  unless attributes.is_a?(Hash)
30
30
  logger.error(
31
- "#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}"
31
+ "#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}",
32
32
  )
33
33
  return
34
34
  end
@@ -23,6 +23,7 @@ module Airbrake
23
23
  # If the frame is unparseable, then 'file' is nil, thus nothing to
24
24
  # filter (all frame's data is in 'function' instead).
25
25
  next unless (file = frame[:file])
26
+
26
27
  frame[:file] = file.sub(/\A#{gem_path}/, GEM_ROOT_LABEL)
27
28
  end
28
29
  end
@@ -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.fetch('AIRBRAKE_DEPLOY_USERNAME', nil)
30
31
  end
31
32
 
32
33
  # @macro call_filter
@@ -40,39 +41,38 @@ 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
- parts = line.chomp.split("\t").first.split(' ')
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')
72
72
  return unless File.exist?(head_path)
73
73
 
74
74
  last_line = nil
75
- IO.foreach(head_path) do |line|
75
+ File.foreach(head_path) do |line|
76
76
  last_line = line if checkout_line?(line)
77
77
  end
78
78
  last_line
@@ -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
 
@@ -35,10 +36,11 @@ module Airbrake
35
36
  `cd #{@git_path} && git config --get remote.origin.url`.chomp
36
37
  else
37
38
  "`git remote get-url` is unsupported in git #{@git_version}. " \
38
- 'Consider an upgrade to 2.7+'
39
+ 'Consider an upgrade to 2.7+'
39
40
  end
40
41
 
41
42
  return unless @repository
43
+
42
44
  notice[:context][:repository] = @repository
43
45
  end
44
46
 
@@ -46,7 +48,14 @@ module Airbrake
46
48
 
47
49
  def detect_git_version
48
50
  return unless which('git')
49
- Gem::Version.new(`git --version`.split[2])
51
+
52
+ begin
53
+ Gem::Version.new(`git --version`.split[2])
54
+ rescue Errno::EAGAIN
55
+ # Bugfix for the case when the system cannot allocate memory for
56
+ # a fork() call: https://github.com/airbrake/airbrake-ruby/issues/680
57
+ nil
58
+ end
50
59
  end
51
60
 
52
61
  # Cross-platform way to tell if an executable is accessible.
@@ -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)
@@ -55,7 +57,7 @@ module Airbrake
55
57
 
56
58
  File.readlines(packed_refs_path).each do |line|
57
59
  next if %w[# ^].include?(line[0])
58
- next unless (parts = line.split(' ')).size == 2
60
+ next unless (parts = line.split).size == 2
59
61
  return parts.first if parts.last == head
60
62
  end
61
63
 
@@ -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
 
@@ -67,29 +82,29 @@ module Airbrake
67
82
 
68
83
  private
69
84
 
70
- def filter_hash(hash)
85
+ def filter_hash(hash) # rubocop:disable Metrics/AbcSize
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
- 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
106
+ url.query = URI.decode_www_form(url.query).to_h.map do |key, val|
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,20 +23,24 @@ 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|$)/,
30
- uuids: /\{?(?:[0-9a-fA-F]\-*){32}\}?/,
30
+ uuids: /\{?(?:[0-9a-fA-F]-*){32}\}?/,
31
31
  numeric_literals: /\b-?(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
32
32
  boolean_literals: /\b(?:true|false|null)\b/i,
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
@@ -91,13 +108,20 @@ module Airbrake
91
108
  @regexp = Regexp.union(features)
92
109
  end
93
110
 
94
- # @param [Airbrake::Query] resource
95
- def call(resource)
96
- return unless resource.respond_to?(:query)
111
+ # @param [Airbrake::Query] metric
112
+ def call(metric)
113
+ return unless metric.respond_to?(:query)
114
+
115
+ query = metric.query
116
+ if IGNORED_QUERIES.any? { |q| q =~ query }
117
+ metric.ignore!
118
+ return
119
+ end
97
120
 
98
- q = resource.query.gsub(@regexp, FILTERED)
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
- resource.query = q
124
+ metric.query = q
101
125
  end
102
126
  end
103
127
  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.
@@ -41,8 +41,7 @@ module Airbrake
41
41
  thread_info[:fiber_variables] = vars
42
42
  end
43
43
 
44
- # Present in Ruby 2.3+.
45
- if th.respond_to?(:name) && (name = th.name)
44
+ if (name = th.name)
46
45
  thread_info[:name] = name
47
46
  end
48
47
 
@@ -56,6 +55,7 @@ module Airbrake
56
55
  def thread_variables(th)
57
56
  th.thread_variables.map.with_object({}) do |var, h|
58
57
  next if var.to_s.start_with?(IGNORE_PREFIX)
58
+
59
59
  h[var] = sanitize_value(th.thread_variable_get(var))
60
60
  end
61
61
  end
@@ -63,6 +63,7 @@ module Airbrake
63
63
  def fiber_variables(th)
64
64
  th.keys.map.with_object({}) do |key, h|
65
65
  next if key.to_s.start_with?(IGNORE_PREFIX)
66
+
66
67
  h[key] = sanitize_value(th[key])
67
68
  end
68
69
  end
@@ -72,7 +73,7 @@ module Airbrake
72
73
  thread_info[:group] = th.group.list.map(&:inspect)
73
74
  thread_info[:priority] = th.priority
74
75
 
75
- thread_info[:safe_level] = th.safe_level unless Airbrake::JRUBY
76
+ thread_info[:safe_level] = th.safe_level if Airbrake::HAS_SAFE_LEVEL
76
77
  end
77
78
 
78
79
  def sanitize_value(value)
@@ -82,7 +83,7 @@ module Airbrake
82
83
  when Array
83
84
  value = value.map { |elem| sanitize_value(elem) }
84
85
  when Hash
85
- Hash[value.map { |k, v| [k, sanitize_value(v)] }]
86
+ value.transform_values { |v| sanitize_value(v) }
86
87
  else
87
88
  value.to_s
88
89
  end
@@ -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 v4.9.0
6
+ # @api private
7
+ module Grouppable
8
+ def groups
9
+ {}
10
+ end
11
+ end
12
+ end
@@ -18,11 +18,9 @@ module Airbrake
18
18
  # Checks whether the instance was ignored.
19
19
  # @return [Boolean]
20
20
  # @see #ignore!
21
- # rubocop:disable Style/DoubleNegation
22
21
  def ignored?
23
22
  !!ignored
24
23
  end
25
- # rubocop:enable Style/DoubleNegation
26
24
 
27
25
  # Ignores an instance. Ignored instances must never reach the Airbrake
28
26
  # dashboard.
@@ -38,6 +36,7 @@ module Airbrake
38
36
  # @raise [Airbrake::Error] when instance is ignored
39
37
  def raise_if_ignored
40
38
  return unless ignored?
39
+
41
40
  raise Airbrake::Error, "cannot access ignored #{self.class}"
42
41
  end
43
42
  end