airbrake-ruby 3.2.6 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +31 -138
  3. data/lib/airbrake-ruby/async_sender.rb +20 -8
  4. data/lib/airbrake-ruby/backtrace.rb +15 -13
  5. data/lib/airbrake-ruby/code_hunk.rb +2 -4
  6. data/lib/airbrake-ruby/config.rb +8 -38
  7. data/lib/airbrake-ruby/deploy_notifier.rb +4 -17
  8. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +5 -4
  9. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +6 -4
  10. data/lib/airbrake-ruby/filters/keys_blacklist.rb +0 -1
  11. data/lib/airbrake-ruby/filters/keys_filter.rb +4 -4
  12. data/lib/airbrake-ruby/filters/keys_whitelist.rb +0 -1
  13. data/lib/airbrake-ruby/loggable.rb +31 -0
  14. data/lib/airbrake-ruby/nested_exception.rb +2 -3
  15. data/lib/airbrake-ruby/notice.rb +6 -6
  16. data/lib/airbrake-ruby/notice_notifier.rb +11 -47
  17. data/lib/airbrake-ruby/performance_notifier.rb +6 -18
  18. data/lib/airbrake-ruby/response.rb +5 -2
  19. data/lib/airbrake-ruby/sync_sender.rb +8 -6
  20. data/lib/airbrake-ruby/version.rb +1 -1
  21. data/spec/airbrake_spec.rb +1 -143
  22. data/spec/async_sender_spec.rb +83 -90
  23. data/spec/backtrace_spec.rb +36 -47
  24. data/spec/code_hunk_spec.rb +12 -15
  25. data/spec/config_spec.rb +79 -96
  26. data/spec/deploy_notifier_spec.rb +3 -7
  27. data/spec/filter_chain_spec.rb +1 -3
  28. data/spec/filters/context_filter_spec.rb +1 -3
  29. data/spec/filters/dependency_filter_spec.rb +1 -3
  30. data/spec/filters/exception_attributes_filter_spec.rb +1 -14
  31. data/spec/filters/gem_root_filter_spec.rb +1 -4
  32. data/spec/filters/git_last_checkout_filter_spec.rb +3 -5
  33. data/spec/filters/git_revision_filter_spec.rb +1 -3
  34. data/spec/filters/keys_blacklist_spec.rb +14 -25
  35. data/spec/filters/keys_whitelist_spec.rb +14 -25
  36. data/spec/filters/root_directory_filter_spec.rb +1 -4
  37. data/spec/filters/system_exit_filter_spec.rb +2 -2
  38. data/spec/filters/thread_filter_spec.rb +1 -3
  39. data/spec/nested_exception_spec.rb +3 -5
  40. data/spec/notice_notifier_spec.rb +23 -20
  41. data/spec/notice_notifier_spec/options_spec.rb +20 -25
  42. data/spec/notice_spec.rb +13 -12
  43. data/spec/performance_notifier_spec.rb +19 -31
  44. data/spec/response_spec.rb +23 -17
  45. data/spec/sync_sender_spec.rb +26 -33
  46. metadata +2 -1
@@ -9,9 +9,7 @@ module Airbrake
9
9
  # @return [Integer] how many lines should be read around the base line
10
10
  NLINES = 2
11
11
 
12
- def initialize(config)
13
- @config = config
14
- end
12
+ include Loggable
15
13
 
16
14
  # @param [String] file The file to read
17
15
  # @param [Integer] line The base line in the file
@@ -31,7 +29,7 @@ module Airbrake
31
29
  def get_from_cache(file)
32
30
  Airbrake::FileCache[file] ||= File.foreach(file)
33
31
  rescue StandardError => ex
34
- @config.logger.error(
32
+ logger.error(
35
33
  "#{self.class.name}: can't read code hunk for #{file}: #{ex}"
36
34
  )
37
35
  nil
@@ -5,6 +5,13 @@ module Airbrake
5
5
  # @api public
6
6
  # @since v1.0.0
7
7
  class Config
8
+ @instance = new
9
+
10
+ class << self
11
+ # @return [Config]
12
+ attr_accessor :instance
13
+ end
14
+
8
15
  # @return [Integer] the project identificator. This value *must* be set.
9
16
  # @api public
10
17
  attr_accessor :project_id
@@ -97,7 +104,6 @@ module Airbrake
97
104
 
98
105
  # @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
99
106
  # config
100
- # rubocop:disable Metrics/AbcSize
101
107
  def initialize(user_config = {})
102
108
  @validator = Config::Validator.new(self)
103
109
 
@@ -105,10 +111,7 @@ module Airbrake
105
111
  self.queue_size = 100
106
112
  self.workers = 1
107
113
  self.code_hunks = true
108
-
109
- self.logger = Logger.new(STDOUT)
110
- logger.level = Logger::WARN
111
-
114
+ self.logger = ::Logger.new(File::NULL)
112
115
  self.project_id = user_config[:project_id]
113
116
  self.project_key = user_config[:project_key]
114
117
  self.host = 'https://api.airbrake.io'
@@ -131,7 +134,6 @@ module Airbrake
131
134
 
132
135
  merge(user_config)
133
136
  end
134
- # rubocop:enable Metrics/AbcSize
135
137
 
136
138
  # The full URL to the Airbrake Notice API. Based on the +:host+ option.
137
139
  # @return [URI] the endpoint address
@@ -195,38 +197,6 @@ module Airbrake
195
197
  end
196
198
  end
197
199
 
198
- def route_stats
199
- logger.warn(
200
- "#{LOG_LABEL} the 'route_stats' option is deprecated. " \
201
- "Use 'performance_stats' instead"
202
- )
203
- @performance_stats
204
- end
205
-
206
- def route_stats=(value)
207
- logger.warn(
208
- "#{LOG_LABEL} the 'route_stats' option is deprecated. " \
209
- "Use 'performance_stats' instead"
210
- )
211
- @performance_stats = value
212
- end
213
-
214
- def route_stats_flush_period
215
- logger.warn(
216
- "#{LOG_LABEL} the 'route_stats_flush_period' option is deprecated. " \
217
- "Use 'performance_stats_flush_period' instead"
218
- )
219
- @performance_stats_flush_period
220
- end
221
-
222
- def route_stats_flush_period=(value)
223
- logger.warn(
224
- "#{LOG_LABEL} the 'route_stats_flush_period' option is deprecated. " \
225
- "Use 'performance_stats' instead"
226
- )
227
- @performance_stats_flush_period = value
228
- end
229
-
230
200
  private
231
201
 
232
202
  def set_option(option, value)
@@ -12,25 +12,12 @@ module Airbrake
12
12
  class DeployNotifier
13
13
  include Inspectable
14
14
 
15
- # @param [Airbrake::Config] config
16
- def initialize(config)
17
- @config =
18
- if config.is_a?(Config)
19
- config
20
- else
21
- loc = caller_locations(1..1).first
22
- signature = "#{self.class.name}##{__method__}"
23
- warn(
24
- "#{loc.path}:#{loc.lineno}: warning: passing a Hash to #{signature} " \
25
- 'is deprecated. Pass `Airbrake::Config` instead'
26
- )
27
- Config.new(config)
28
- end
29
-
30
- @sender = SyncSender.new(@config)
15
+ def initialize
16
+ @config = Airbrake::Config.instance
17
+ @sender = SyncSender.new
31
18
  end
32
19
 
33
- # @see Airbrake.create_deploy
20
+ # @see Airbrake.notify_deploy
34
21
  def notify(deploy_info, promise = Airbrake::Promise.new)
35
22
  if @config.ignored_environment?
36
23
  return promise.reject("The '#{@config.environment}' environment is ignored")
@@ -6,8 +6,9 @@ module Airbrake
6
6
  # @api private
7
7
  # @since v2.10.0
8
8
  class ExceptionAttributesFilter
9
- def initialize(logger)
10
- @logger = logger
9
+ include Loggable
10
+
11
+ def initialize
11
12
  @weight = 118
12
13
  end
13
14
 
@@ -20,13 +21,13 @@ module Airbrake
20
21
  begin
21
22
  attributes = exception.to_airbrake
22
23
  rescue StandardError => ex
23
- @logger.error(
24
+ logger.error(
24
25
  "#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}"
25
26
  )
26
27
  end
27
28
 
28
29
  unless attributes.is_a?(Hash)
29
- @logger.error(
30
+ logger.error(
30
31
  "#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}"
31
32
  )
32
33
  return
@@ -20,11 +20,11 @@ module Airbrake
20
20
  # file (checkout information is omitted)
21
21
  MIN_HEAD_COLS = 6
22
22
 
23
- # @param [Logger] logger
23
+ include Loggable
24
+
24
25
  # @param [String] root_directory
25
- def initialize(logger, root_directory)
26
+ def initialize(root_directory)
26
27
  @git_path = File.join(root_directory, '.git')
27
- @logger = logger
28
28
  @weight = 116
29
29
  @last_checkout = nil
30
30
  end
@@ -45,12 +45,13 @@ module Airbrake
45
45
 
46
46
  private
47
47
 
48
+ # rubocop:disable Metrics/AbcSize
48
49
  def last_checkout
49
50
  return unless (line = last_checkout_line)
50
51
 
51
52
  parts = line.chomp.split("\t").first.split(' ')
52
53
  if parts.size < MIN_HEAD_COLS
53
- @logger.error(
54
+ logger.error(
54
55
  "#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}"
55
56
  )
56
57
  return
@@ -64,6 +65,7 @@ module Airbrake
64
65
  time: timestamp(parts[-2].to_i)
65
66
  }
66
67
  end
68
+ # rubocop:enable Metrics/AbcSize
67
69
 
68
70
  def last_checkout_line
69
71
  head_path = File.join(@git_path, 'logs', 'HEAD')
@@ -5,7 +5,6 @@ module Airbrake
5
5
  #
6
6
  # @example
7
7
  # filter = Airbrake::Filters::KeysBlacklist.new(
8
- # Logger.new(STDOUT),
9
8
  # [:email, /credit/i, 'password']
10
9
  # )
11
10
  # airbrake.add_filter(filter)
@@ -26,16 +26,16 @@ module Airbrake
26
26
  # be modified by blacklist/whitelist filters
27
27
  FILTERABLE_CONTEXT_KEYS = %i[user headers].freeze
28
28
 
29
+ include Loggable
30
+
29
31
  # @return [Integer]
30
32
  attr_reader :weight
31
33
 
32
34
  # Creates a new KeysBlacklist or KeysWhitelist filter that uses the given
33
35
  # +patterns+ for filtering a notice's payload.
34
36
  #
35
- # @param [Logger, #error] logger
36
37
  # @param [Array<String,Regexp,Symbol>] patterns
37
- def initialize(logger, patterns)
38
- @logger = logger
38
+ def initialize(patterns)
39
39
  @patterns = patterns
40
40
  @valid_patterns = false
41
41
  end
@@ -122,7 +122,7 @@ module Airbrake
122
122
 
123
123
  return if @valid_patterns
124
124
 
125
- @logger.error(
125
+ logger.error(
126
126
  "#{LOG_LABEL} one of the patterns in #{self.class} is invalid. " \
127
127
  "Known patterns: #{@patterns}"
128
128
  )
@@ -5,7 +5,6 @@ module Airbrake
5
5
  #
6
6
  # @example
7
7
  # filter = Airbrake::Filters::KeysBlacklist.new(
8
- # Logger.new(STDOUT),
9
8
  # [:email, /credit/i, 'password']
10
9
  # )
11
10
  # airbrake.add_filter(filter)
@@ -0,0 +1,31 @@
1
+ module Airbrake
2
+ # Loggable is included into any class that wants to be able to log.
3
+ #
4
+ # By default, Loggable defines a null logger that doesn't do anything. You are
5
+ # supposed to overwrite it via the {instance} method before calling {logger}.
6
+ #
7
+ # @example
8
+ # class A
9
+ # include Loggable
10
+ #
11
+ # def initialize
12
+ # logger.debug('Initialized A')
13
+ # end
14
+ # end
15
+ #
16
+ # @since v4.0.0
17
+ # @api private
18
+ module Loggable
19
+ @instance = ::Logger.new(File::NULL)
20
+
21
+ class << self
22
+ # @return [Logger]
23
+ attr_accessor :instance
24
+ end
25
+
26
+ # @return [Logger] standard Ruby logger object
27
+ def logger
28
+ Loggable.instance
29
+ end
30
+ end
31
+ end
@@ -9,8 +9,7 @@ module Airbrake
9
9
  # can unwrap. Exceptions that have a longer cause chain will be ignored
10
10
  MAX_NESTED_EXCEPTIONS = 3
11
11
 
12
- def initialize(config, exception)
13
- @config = config
12
+ def initialize(exception)
14
13
  @exception = exception
15
14
  end
16
15
 
@@ -18,7 +17,7 @@ module Airbrake
18
17
  unwind_exceptions.map do |exception|
19
18
  { type: exception.class.name,
20
19
  message: exception.message,
21
- backtrace: Backtrace.parse(@config, exception) }
20
+ backtrace: Backtrace.parse(exception) }
22
21
  end
23
22
  end
24
23
 
@@ -50,6 +50,7 @@ module Airbrake
50
50
  DEFAULT_SEVERITY = 'error'.freeze
51
51
 
52
52
  include Ignorable
53
+ include Loggable
53
54
 
54
55
  # @since v1.7.0
55
56
  # @return [Hash{Symbol=>Object}] the hash with arbitrary objects to be used
@@ -57,11 +58,10 @@ module Airbrake
57
58
  attr_reader :stash
58
59
 
59
60
  # @api private
60
- def initialize(config, exception, params = {})
61
- @config = config
62
-
61
+ def initialize(exception, params = {})
62
+ @config = Airbrake::Config.instance
63
63
  @payload = {
64
- errors: NestedException.new(config, exception).as_json,
64
+ errors: NestedException.new(exception).as_json,
65
65
  context: context,
66
66
  environment: {
67
67
  program_name: $PROGRAM_NAME
@@ -84,7 +84,7 @@ module Airbrake
84
84
  begin
85
85
  json = @payload.to_json
86
86
  rescue *JSON_EXCEPTIONS => ex
87
- @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")
87
+ logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")
88
88
  else
89
89
  return json if json && json.bytesize <= MAX_NOTICE_SIZE
90
90
  end
@@ -152,7 +152,7 @@ module Airbrake
152
152
 
153
153
  new_max_size = @truncator.reduce_max_size
154
154
  if new_max_size == 0
155
- @config.logger.error(
155
+ logger.error(
156
156
  "#{LOG_LABEL} truncation failed. File an issue at " \
157
157
  "https://github.com/airbrake/airbrake-ruby " \
158
158
  "and attach the following payload: #{@payload}"
@@ -5,7 +5,6 @@ module Airbrake
5
5
  # @see Airbrake::Config The list of options
6
6
  # @since v1.0.0
7
7
  # @api public
8
- # rubocop:disable Metrics/ClassLength
9
8
  class NoticeNotifier
10
9
  # @return [Array<Class>] filters to be executed first
11
10
  DEFAULT_FILTERS = [
@@ -17,35 +16,14 @@ module Airbrake
17
16
  ].freeze
18
17
 
19
18
  include Inspectable
19
+ include Loggable
20
20
 
21
- # Creates a new notice notifier with the given config options.
22
- #
23
- # @example
24
- # config = Airbrake::Config.new
25
- # config.project_id = 123
26
- # config.project_key = '321'
27
- # notice_notifier = Airbrake::NoticeNotifier.new(config)
28
- #
29
- # @param [Airbrake::Config] config
30
- def initialize(config, perf_notifier = nil)
31
- @config =
32
- if config.is_a?(Config)
33
- config
34
- else
35
- loc = caller_locations(1..1).first
36
- signature = "#{self.class.name}##{__method__}"
37
- warn(
38
- "#{loc.path}:#{loc.lineno}: warning: passing a Hash to #{signature} " \
39
- 'is deprecated. Pass `Airbrake::Config` instead'
40
- )
41
- Config.new(config)
42
- end
43
-
21
+ def initialize
22
+ @config = Airbrake::Config.instance
44
23
  @context = {}
45
24
  @filter_chain = FilterChain.new
46
- @async_sender = AsyncSender.new(@config)
47
- @sync_sender = SyncSender.new(@config)
48
- @perf_notifier = perf_notifier
25
+ @async_sender = AsyncSender.new
26
+ @sync_sender = SyncSender.new
49
27
 
50
28
  add_default_filters
51
29
  end
@@ -60,15 +38,6 @@ module Airbrake
60
38
  send_notice(exception, params, @sync_sender, &block).value
61
39
  end
62
40
 
63
- # @deprecated Update the airbrake gem to v8.1.0 or higher
64
- def notify_request(request_info, promise = Promise.new)
65
- @config.logger.info(
66
- "#{LOG_LABEL} #{self.class}##{__method__} is deprecated. Update " \
67
- 'the airbrake gem to v8.1.0 or higher'
68
- )
69
- @perf_notifier.notify(Request.new(request_info), promise)
70
- end
71
-
72
41
  # @macro see_public_api_method
73
42
  def add_filter(filter = nil, &block)
74
43
  @filter_chain.add_filter(block_given? ? block : filter)
@@ -90,7 +59,7 @@ module Airbrake
90
59
  exception[:params].merge!(params)
91
60
  exception
92
61
  else
93
- Notice.new(@config, convert_to_exception(exception), params.dup)
62
+ Notice.new(convert_to_exception(exception), params.dup)
94
63
  end
95
64
  end
96
65
 
@@ -143,7 +112,7 @@ module Airbrake
143
112
  def default_sender
144
113
  return @async_sender if @async_sender.has_workers?
145
114
 
146
- @config.logger.warn(
115
+ logger.warn(
147
116
  "#{LOG_LABEL} falling back to sync delivery because there are no " \
148
117
  "running async workers"
149
118
  )
@@ -165,19 +134,15 @@ module Airbrake
165
134
  DEFAULT_FILTERS.each { |f| add_filter(f.new) }
166
135
 
167
136
  if (whitelist_keys = @config.whitelist_keys).any?
168
- add_filter(
169
- Airbrake::Filters::KeysWhitelist.new(@config.logger, whitelist_keys)
170
- )
137
+ add_filter(Airbrake::Filters::KeysWhitelist.new(whitelist_keys))
171
138
  end
172
139
 
173
140
  if (blacklist_keys = @config.blacklist_keys).any?
174
- add_filter(
175
- Airbrake::Filters::KeysBlacklist.new(@config.logger, blacklist_keys)
176
- )
141
+ add_filter(Airbrake::Filters::KeysBlacklist.new(blacklist_keys))
177
142
  end
178
143
 
179
144
  add_filter(Airbrake::Filters::ContextFilter.new(@context))
180
- add_filter(Airbrake::Filters::ExceptionAttributesFilter.new(@config.logger))
145
+ add_filter(Airbrake::Filters::ExceptionAttributesFilter.new)
181
146
 
182
147
  return unless (root_directory = @config.root_directory)
183
148
  [
@@ -189,10 +154,9 @@ module Airbrake
189
154
  end
190
155
 
191
156
  add_filter(
192
- Airbrake::Filters::GitLastCheckoutFilter.new(@config.logger, root_directory)
157
+ Airbrake::Filters::GitLastCheckoutFilter.new(root_directory)
193
158
  )
194
159
  end
195
160
  # rubocop:enable Metrics/AbcSize
196
161
  end
197
- # rubocop:enable Metrics/ClassLength
198
162
  end