airbrake-ruby 4.15.0 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby/async_sender.rb +4 -2
  3. data/lib/airbrake-ruby/backtrace.rb +6 -5
  4. data/lib/airbrake-ruby/config/processor.rb +71 -0
  5. data/lib/airbrake-ruby/config/validator.rb +6 -0
  6. data/lib/airbrake-ruby/config.rb +44 -35
  7. data/lib/airbrake-ruby/context.rb +51 -0
  8. data/lib/airbrake-ruby/file_cache.rb +1 -1
  9. data/lib/airbrake-ruby/filter_chain.rb +3 -0
  10. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  11. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  12. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  13. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  14. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +3 -4
  15. data/lib/airbrake-ruby/filters/git_repository_filter.rb +4 -1
  16. data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
  17. data/lib/airbrake-ruby/filters/keys_filter.rb +23 -15
  18. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/sql_filter.rb +11 -11
  20. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  21. data/lib/airbrake-ruby/filters/thread_filter.rb +4 -3
  22. data/lib/airbrake-ruby/grouppable.rb +1 -1
  23. data/lib/airbrake-ruby/ignorable.rb +1 -2
  24. data/lib/airbrake-ruby/mergeable.rb +1 -1
  25. data/lib/airbrake-ruby/monotonic_time.rb +1 -1
  26. data/lib/airbrake-ruby/notice.rb +1 -8
  27. data/lib/airbrake-ruby/notice_notifier.rb +4 -4
  28. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  29. data/lib/airbrake-ruby/performance_notifier.rb +40 -54
  30. data/lib/airbrake-ruby/promise.rb +1 -0
  31. data/lib/airbrake-ruby/query.rb +1 -6
  32. data/lib/airbrake-ruby/queue.rb +1 -8
  33. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  34. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  35. data/lib/airbrake-ruby/remote_settings.rb +128 -0
  36. data/lib/airbrake-ruby/request.rb +1 -8
  37. data/lib/airbrake-ruby/stat.rb +2 -13
  38. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  39. data/lib/airbrake-ruby/tdigest.rb +12 -9
  40. data/lib/airbrake-ruby/thread_pool.rb +9 -6
  41. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  42. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  43. data/lib/airbrake-ruby/truncator.rb +8 -2
  44. data/lib/airbrake-ruby/version.rb +11 -1
  45. data/lib/airbrake-ruby.rb +44 -54
  46. data/spec/airbrake_spec.rb +178 -92
  47. data/spec/async_sender_spec.rb +10 -8
  48. data/spec/backtrace_spec.rb +39 -36
  49. data/spec/benchmark_spec.rb +5 -3
  50. data/spec/code_hunk_spec.rb +26 -17
  51. data/spec/config/processor_spec.rb +151 -0
  52. data/spec/config/validator_spec.rb +23 -3
  53. data/spec/config_spec.rb +40 -52
  54. data/spec/context_spec.rb +54 -0
  55. data/spec/deploy_notifier_spec.rb +6 -4
  56. data/spec/file_cache_spec.rb +1 -0
  57. data/spec/filter_chain_spec.rb +29 -24
  58. data/spec/filters/context_filter_spec.rb +14 -5
  59. data/spec/filters/dependency_filter_spec.rb +3 -1
  60. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  61. data/spec/filters/gem_root_filter_spec.rb +9 -6
  62. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  63. data/spec/filters/git_repository_filter.rb +9 -9
  64. data/spec/filters/git_revision_filter_spec.rb +20 -20
  65. data/spec/filters/keys_allowlist_spec.rb +26 -16
  66. data/spec/filters/keys_blocklist_spec.rb +35 -18
  67. data/spec/filters/root_directory_filter_spec.rb +7 -7
  68. data/spec/filters/sql_filter_spec.rb +28 -28
  69. data/spec/filters/system_exit_filter_spec.rb +4 -2
  70. data/spec/filters/thread_filter_spec.rb +16 -14
  71. data/spec/loggable_spec.rb +2 -2
  72. data/spec/monotonic_time_spec.rb +8 -6
  73. data/spec/nested_exception_spec.rb +46 -46
  74. data/spec/notice_notifier/options_spec.rb +25 -15
  75. data/spec/notice_notifier_spec.rb +54 -49
  76. data/spec/notice_spec.rb +7 -3
  77. data/spec/performance_breakdown_spec.rb +0 -12
  78. data/spec/performance_notifier_spec.rb +69 -87
  79. data/spec/promise_spec.rb +38 -32
  80. data/spec/query_spec.rb +1 -11
  81. data/spec/queue_spec.rb +1 -13
  82. data/spec/remote_settings/callback_spec.rb +162 -0
  83. data/spec/remote_settings/settings_data_spec.rb +348 -0
  84. data/spec/remote_settings_spec.rb +201 -0
  85. data/spec/request_spec.rb +1 -13
  86. data/spec/response_spec.rb +34 -12
  87. data/spec/spec_helper.rb +4 -4
  88. data/spec/stashable_spec.rb +5 -5
  89. data/spec/stat_spec.rb +7 -14
  90. data/spec/sync_sender_spec.rb +52 -17
  91. data/spec/tdigest_spec.rb +61 -56
  92. data/spec/thread_pool_spec.rb +65 -56
  93. data/spec/time_truncate_spec.rb +23 -6
  94. data/spec/timed_trace_spec.rb +32 -30
  95. data/spec/truncator_spec.rb +72 -43
  96. metadata +66 -50
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9737dfd8078f14a0150525e1c8b226bcaa5bb87e7627542d7a1a25a27d3c191d
4
- data.tar.gz: 653ff8929bc7199a77c59ead004167a38d214741157282d93e947c3d5ca8ecd6
3
+ metadata.gz: f0310134480c9c13f2e8ab87a4c616768c213b590f98edd38e9c9abbd539a7ba
4
+ data.tar.gz: 6f5e8d2411eee4e18ff97ef7d89b3c4a1c8ebb3ba5ec14fafe24c25e850672b1
5
5
  SHA512:
6
- metadata.gz: 21d60bd1415bec1e0ed3b89891f5b35e56f019b09dcb50c05f1e55b2176a2976a566c95e0a3fc4cdce2601a1b4b0e786b1ddbcc67188da6df51bd5c94188e54d
7
- data.tar.gz: 3c3b2312b7945685c6199ecd21f23601b752e1d4234d35461db2f4c7cf0318c24ee263df2536e9cffb7e107002a5a5be10af1cf7eecc16ac65e07cd948f2c68e
6
+ metadata.gz: d6e19e37eca3e38af441560c044ac1472ab06a282c4d8b4873a4c8529b9fb9e9a70634a42b4d0d5fe668ba866649ee115942678e5bbfffa1f60373ccd66e964b
7
+ data.tar.gz: f8b22c23d2534b1f4dc12dd1b5d8bf970504b297a61421cda6d496ec2f1143ac9eb031e2c1b033e8d8a24b94b634c2a17ea475f9ef13d482be08a419a54b1d98
@@ -7,16 +7,17 @@ module Airbrake
7
7
  class AsyncSender
8
8
  include Loggable
9
9
 
10
- def initialize(method = :post)
10
+ def initialize(method = :post, name = 'async-sender')
11
11
  @config = Airbrake::Config.instance
12
12
  @method = method
13
+ @name = name
13
14
  end
14
15
 
15
16
  # Asynchronously sends a notice to Airbrake.
16
17
  #
17
18
  # @param [Hash] payload Whatever needs to be sent
18
19
  # @return [Airbrake::Promise]
19
- def send(payload, promise, endpoint = @config.endpoint)
20
+ def send(payload, promise, endpoint = @config.error_endpoint)
20
21
  unless thread_pool << [payload, promise, endpoint]
21
22
  return promise.reject(
22
23
  "AsyncSender has reached its capacity of #{@config.queue_size}",
@@ -47,6 +48,7 @@ module Airbrake
47
48
  @thread_pool ||= begin
48
49
  sender = SyncSender.new(@method)
49
50
  ThreadPool.new(
51
+ name: @name,
50
52
  worker_size: @config.workers,
51
53
  queue_size: @config.queue_size,
52
54
  block: proc { |args| sender.send(*args) },
@@ -22,7 +22,7 @@ module Airbrake
22
22
  (?<line>\d+) # Matches '43'
23
23
  :in\s
24
24
  `(?<function>.*)' # Matches "`block (3 levels) in <top (required)>'"
25
- \z}x
25
+ \z}x.freeze
26
26
 
27
27
  # @return [Regexp] the pattern that matches JRuby Java stack frames, such
28
28
  # as org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105)
@@ -39,7 +39,7 @@ module Airbrake
39
39
  :?
40
40
  (?<line>\d+)? # Matches '105'
41
41
  \)
42
- \z}x
42
+ \z}x.freeze
43
43
 
44
44
  # @return [Regexp] the pattern that tries to assume what a generic stack
45
45
  # frame might look like, when exception's backtrace is set manually.
@@ -53,7 +53,7 @@ module Airbrake
53
53
  |
54
54
  :in\s(?<function>.+) # Matches ":in func"
55
55
  )? # ... or nothing
56
- \z}x
56
+ \z}x.freeze
57
57
 
58
58
  # @return [Regexp] the pattern that matches exceptions from PL/SQL such as
59
59
  # ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945
@@ -67,7 +67,7 @@ module Airbrake
67
67
  |
68
68
  #{GENERIC}
69
69
  )
70
- \z/x
70
+ \z/x.freeze
71
71
 
72
72
  # @return [Regexp] the pattern that matches CoffeeScript backtraces
73
73
  # usually coming from Rails & ExecJS
@@ -82,7 +82,7 @@ module Airbrake
82
82
  # Matches the Ruby part of the backtrace
83
83
  #{RUBY}
84
84
  )
85
- \z/x
85
+ \z/x.freeze
86
86
  end
87
87
 
88
88
  # @return [Integer] how many first frames should include code hunks
@@ -95,6 +95,7 @@ module Airbrake
95
95
  # @return [Array<Hash{Symbol=>String,Integer}>] the parsed backtrace
96
96
  def self.parse(exception)
97
97
  return [] if exception.backtrace.nil? || exception.backtrace.none?
98
+
98
99
  parse_backtrace(exception)
99
100
  end
100
101
 
@@ -0,0 +1,71 @@
1
+ module Airbrake
2
+ class Config
3
+ # Processor is a helper class, which is responsible for setting default
4
+ # config values, default notifier filters and remote configuration changes.
5
+ #
6
+ # @since v5.0.0
7
+ # @api private
8
+ class Processor
9
+ # @param [Airbrake::Config] config
10
+ # @return [Airbrake::Config::Processor]
11
+ def self.process(config)
12
+ new(config).process
13
+ end
14
+
15
+ # @param [Airbrake::Config] config
16
+ def initialize(config)
17
+ @config = config
18
+ @blocklist_keys = @config.blocklist_keys
19
+ @allowlist_keys = @config.allowlist_keys
20
+ @project_id = @config.project_id
21
+ @poll_callback = Airbrake::RemoteSettings::Callback.new(config)
22
+ end
23
+
24
+ # @param [Airbrake::NoticeNotifier] notifier
25
+ # @return [void]
26
+ def process_blocklist(notifier)
27
+ return if @blocklist_keys.none?
28
+
29
+ blocklist = Airbrake::Filters::KeysBlocklist.new(@blocklist_keys)
30
+ notifier.add_filter(blocklist)
31
+ end
32
+
33
+ # @param [Airbrake::NoticeNotifier] notifier
34
+ # @return [void]
35
+ def process_allowlist(notifier)
36
+ return if @allowlist_keys.none?
37
+
38
+ allowlist = Airbrake::Filters::KeysAllowlist.new(@allowlist_keys)
39
+ notifier.add_filter(allowlist)
40
+ end
41
+
42
+ # @return [Airbrake::RemoteSettings]
43
+ def process_remote_configuration
44
+ return unless @config.remote_config
45
+ return unless @project_id
46
+ return if @config.environment == 'test'
47
+
48
+ RemoteSettings.poll(@project_id, @config.remote_config_host) do |data|
49
+ @poll_callback.call(data)
50
+ end
51
+ end
52
+
53
+ # @param [Airbrake::NoticeNotifier] notifier
54
+ # @return [void]
55
+ def add_filters(notifier)
56
+ return unless @config.root_directory
57
+
58
+ [
59
+ Airbrake::Filters::RootDirectoryFilter,
60
+ Airbrake::Filters::GitRevisionFilter,
61
+ Airbrake::Filters::GitRepositoryFilter,
62
+ Airbrake::Filters::GitLastCheckoutFilter,
63
+ ].each do |filter|
64
+ next if notifier.has_filter?(filter)
65
+
66
+ notifier.add_filter(filter.new(@config.root_directory))
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -44,6 +44,10 @@ module Airbrake
44
44
  def check_notify_ability(config)
45
45
  promise = Airbrake::Promise.new
46
46
 
47
+ unless config.error_notifications
48
+ return promise.reject('error notifications are disabled')
49
+ end
50
+
47
51
  if ignored_environment?(config)
48
52
  return promise.reject(
49
53
  "current environment '#{config.environment}' is ignored",
@@ -57,12 +61,14 @@ module Airbrake
57
61
 
58
62
  def valid_project_id?(config)
59
63
  return true if config.project_id.to_i > 0
64
+
60
65
  false
61
66
  end
62
67
 
63
68
  def valid_project_key?(config)
64
69
  return false unless config.project_key.is_a?(String)
65
70
  return false if config.project_key.empty?
71
+
66
72
  true
67
73
  end
68
74
 
@@ -46,6 +46,17 @@ module Airbrake
46
46
  # @api public
47
47
  attr_accessor :host
48
48
 
49
+ # @since v5.0.0
50
+ alias error_host host
51
+ # @since v5.0.0
52
+ alias error_host= host=
53
+
54
+ # @return [String] the host, which provides the API endpoint to which
55
+ # APM data should be sent
56
+ # @api public
57
+ # @since v5.0.0
58
+ attr_accessor :apm_host
59
+
49
60
  # @return [String, Pathname] the working directory of your project
50
61
  # @api public
51
62
  attr_accessor :root_directory
@@ -71,18 +82,12 @@ module Airbrake
71
82
  # @since v4.15.0
72
83
  attr_accessor :allowlist_keys
73
84
 
74
- # @deprecated Use allowlist_keys instead
75
- alias whitelist_keys allowlist_keys
76
-
77
85
  # @return [Array<String, Symbol, Regexp>] the keys, which should be
78
86
  # filtered
79
87
  # @api public
80
88
  # @since v4.15.0
81
89
  attr_accessor :blocklist_keys
82
90
 
83
- # @deprecated Use blocklist_keys instead
84
- alias blacklist_keys blocklist_keys
85
-
86
91
  # @return [Boolean] true if the library should attach code hunks to each
87
92
  # frame in a backtrace, false otherwise
88
93
  # @api public
@@ -113,6 +118,24 @@ module Airbrake
113
118
  # @since v4.12.0
114
119
  attr_accessor :job_stats
115
120
 
121
+ # @return [Boolean] true if the library should send error reports to
122
+ # Airbrake, false otherwise
123
+ # @api public
124
+ # @since v5.0.0
125
+ attr_accessor :error_notifications
126
+
127
+ # @return [String] the host which should be used for fetching remote
128
+ # configuration options
129
+ # @api public
130
+ # @since v5.0.0
131
+ attr_accessor :remote_config_host
132
+
133
+ # @return [String] true if notifier should periodically fetch remote
134
+ # configuration, false otherwise
135
+ # @api public
136
+ # @since v5.2.0
137
+ attr_accessor :remote_config
138
+
116
139
  class << self
117
140
  # @return [Config]
118
141
  attr_writer :instance
@@ -125,7 +148,7 @@ module Airbrake
125
148
 
126
149
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
127
150
  # config
128
- # rubocop:disable Metrics/AbcSize
151
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
129
152
  def initialize(user_config = {})
130
153
  self.proxy = {}
131
154
  self.queue_size = 100
@@ -134,7 +157,9 @@ module Airbrake
134
157
  self.logger = ::Logger.new(File::NULL).tap { |l| l.level = Logger::WARN }
135
158
  self.project_id = user_config[:project_id]
136
159
  self.project_key = user_config[:project_key]
137
- self.host = 'https://api.airbrake.io'
160
+ self.error_host = 'https://api.airbrake.io'
161
+ self.apm_host = 'https://api.airbrake.io'
162
+ self.remote_config_host = 'https://notifier-configs.airbrake.io'
138
163
 
139
164
  self.ignore_environments = []
140
165
 
@@ -153,37 +178,21 @@ module Airbrake
153
178
  self.performance_stats_flush_period = 15
154
179
  self.query_stats = true
155
180
  self.job_stats = true
181
+ self.error_notifications = true
182
+ self.remote_config = true
156
183
 
157
184
  merge(user_config)
158
185
  end
159
- # rubocop:enable Metrics/AbcSize
160
-
161
- def blacklist_keys=(keys)
162
- loc = caller_locations(1..1).first
163
- Kernel.warn(
164
- "#{loc.path}:#{loc.lineno}: warning: blacklist_keys= is deprecated " \
165
- "use blocklist_keys= instead",
166
- )
167
- self.blocklist_keys = keys
168
- end
169
-
170
- def whitelist_keys=(keys)
171
- loc = caller_locations(1..1).first
172
- Kernel.warn(
173
- "#{loc.path}:#{loc.lineno}: warning: whitelist_keys= is deprecated " \
174
- "use allowlist_keys= instead",
175
- )
176
- self.allowlist_keys = keys
177
- end
186
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
178
187
 
179
- # The full URL to the Airbrake Notice API. Based on the +:host+ option.
188
+ # The full URL to the Airbrake Notice API. Based on the +:error_host+ option.
180
189
  # @return [URI] the endpoint address
181
- def endpoint
182
- @endpoint ||=
190
+ def error_endpoint
191
+ @error_endpoint ||=
183
192
  begin
184
- self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
193
+ self.error_host = ('https://' << error_host) if error_host !~ %r{\Ahttps?://}
185
194
  api = "api/v3/projects/#{project_id}/notices"
186
- URI.join(host, api)
195
+ URI.join(error_host, api)
187
196
  end
188
197
  end
189
198
 
@@ -239,14 +248,14 @@ module Airbrake
239
248
 
240
249
  # @return [Promise] resolved promise if neither of the performance options
241
250
  # reject it, false otherwise
242
- def check_performance_options(resource)
251
+ def check_performance_options(metric)
243
252
  promise = Airbrake::Promise.new
244
253
 
245
254
  if !performance_stats
246
255
  promise.reject("The Performance Stats feature is disabled")
247
- elsif resource.is_a?(Airbrake::Query) && !query_stats
256
+ elsif metric.is_a?(Airbrake::Query) && !query_stats
248
257
  promise.reject("The Query Stats feature is disabled")
249
- elsif resource.is_a?(Airbrake::Queue) && !job_stats
258
+ elsif metric.is_a?(Airbrake::Queue) && !job_stats
250
259
  promise.reject("The Job Stats feature is disabled")
251
260
  else
252
261
  promise
@@ -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
@@ -40,7 +40,7 @@ module Airbrake
40
40
  data.empty?
41
41
  end
42
42
 
43
- # @since ?.?.?
43
+ # @since v4.7.0
44
44
  # @return [void]
45
45
  def self.reset
46
46
  @data = {}
@@ -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,6 +72,7 @@ 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
@@ -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
 
@@ -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
@@ -41,16 +41,16 @@ module Airbrake
41
41
 
42
42
  return unless File.exist?(@git_path)
43
43
  return unless (checkout = last_checkout)
44
+
44
45
  notice[:context][:lastCheckout] = checkout
45
46
  end
46
47
 
47
48
  private
48
49
 
49
- # rubocop:disable Metrics/AbcSize
50
50
  def last_checkout
51
51
  return unless (line = last_checkout_line)
52
52
 
53
- parts = line.chomp.split("\t").first.split(' ')
53
+ parts = line.chomp.split("\t").first.split
54
54
  if parts.size < MIN_HEAD_COLS
55
55
  logger.error(
56
56
  "#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}",
@@ -66,14 +66,13 @@ module Airbrake
66
66
  time: timestamp(parts[-2].to_i),
67
67
  }
68
68
  end
69
- # rubocop:enable Metrics/AbcSize
70
69
 
71
70
  def last_checkout_line
72
71
  head_path = File.join(@git_path, 'logs', 'HEAD')
73
72
  return unless File.exist?(head_path)
74
73
 
75
74
  last_line = nil
76
- IO.foreach(head_path) do |line|
75
+ File.foreach(head_path) do |line|
77
76
  last_line = line if checkout_line?(line)
78
77
  end
79
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,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)
@@ -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
 
@@ -64,10 +64,14 @@ module Airbrake
64
64
  validate_patterns
65
65
  end
66
66
 
67
- FILTERABLE_KEYS.each { |key| filter_hash(notice[key]) }
67
+ FILTERABLE_KEYS.each do |key|
68
+ notice[key] = filter_hash(notice[key])
69
+ end
70
+
68
71
  FILTERABLE_CONTEXT_KEYS.each { |key| filter_context_key(notice, key) }
69
72
 
70
73
  return unless notice[:context][:url]
74
+
71
75
  filter_url(notice)
72
76
  end
73
77
 
@@ -78,29 +82,29 @@ module Airbrake
78
82
 
79
83
  private
80
84
 
81
- def filter_hash(hash)
85
+ def filter_hash(hash) # rubocop:disable Metrics/AbcSize
82
86
  return hash unless hash.is_a?(Hash)
83
87
 
88
+ hash_copy = hash.dup
89
+
84
90
  hash.each_key do |key|
85
91
  if should_filter?(key.to_s)
86
- hash[key] = FILTERED
87
- elsif hash[key].is_a?(Hash)
88
- 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])
89
95
  elsif hash[key].is_a?(Array)
90
- 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
91
99
  end
92
100
  end
101
+
102
+ hash_copy
93
103
  end
94
104
 
95
105
  def filter_url_params(url)
96
- url.query = Hash[URI.decode_www_form(url.query)].map do |key, val|
97
- # Ruby < 2.2 raises InvalidComponentError if the query contains
98
- # invalid characters, so be sure to escape individual components.
99
- if should_filter?(key)
100
- "#{URI.encode_www_form_component(key)}=[Filtered]"
101
- else
102
- "#{URI.encode_www_form_component(key)}=#{URI.encode_www_form_component(val)}"
103
- end
106
+ url.query = URI.decode_www_form(url.query).to_h.map do |key, val|
107
+ should_filter?(key) ? "#{key}=[Filtered]" : "#{key}=#{val}"
104
108
  end.join('&')
105
109
 
106
110
  url.to_s
@@ -114,6 +118,7 @@ module Airbrake
114
118
  end
115
119
 
116
120
  return unless url.query
121
+
117
122
  notice[:context][:url] = filter_url_params(url)
118
123
  end
119
124
 
@@ -122,6 +127,7 @@ module Airbrake
122
127
 
123
128
  @patterns = @patterns.flat_map do |pattern|
124
129
  next(pattern) unless pattern.respond_to?(:call)
130
+
125
131
  pattern.call
126
132
  end
127
133
  end
@@ -142,7 +148,9 @@ module Airbrake
142
148
  def filter_context_key(notice, key)
143
149
  return unless notice[:context][key]
144
150
  return if notice[:context][key] == FILTERED
145
- 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
146
154
 
147
155
  notice[:context][key] = FILTERED
148
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