honeybadger 5.0.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +713 -701
  3. data/LICENSE +19 -19
  4. data/README.md +57 -57
  5. data/TROUBLESHOOTING.md +3 -3
  6. data/bin/honeybadger +5 -5
  7. data/lib/honeybadger/agent.rb +488 -488
  8. data/lib/honeybadger/backend/base.rb +116 -116
  9. data/lib/honeybadger/backend/debug.rb +22 -22
  10. data/lib/honeybadger/backend/null.rb +29 -29
  11. data/lib/honeybadger/backend/server.rb +62 -62
  12. data/lib/honeybadger/backend/test.rb +46 -46
  13. data/lib/honeybadger/backend.rb +27 -27
  14. data/lib/honeybadger/backtrace.rb +181 -181
  15. data/lib/honeybadger/breadcrumbs/active_support.rb +119 -119
  16. data/lib/honeybadger/breadcrumbs/breadcrumb.rb +53 -53
  17. data/lib/honeybadger/breadcrumbs/collector.rb +82 -82
  18. data/lib/honeybadger/breadcrumbs/logging.rb +51 -51
  19. data/lib/honeybadger/breadcrumbs/ring_buffer.rb +44 -44
  20. data/lib/honeybadger/breadcrumbs.rb +8 -8
  21. data/lib/honeybadger/cli/deploy.rb +43 -43
  22. data/lib/honeybadger/cli/exec.rb +143 -143
  23. data/lib/honeybadger/cli/helpers.rb +28 -28
  24. data/lib/honeybadger/cli/heroku.rb +129 -129
  25. data/lib/honeybadger/cli/install.rb +101 -101
  26. data/lib/honeybadger/cli/main.rb +237 -237
  27. data/lib/honeybadger/cli/notify.rb +67 -67
  28. data/lib/honeybadger/cli/test.rb +267 -267
  29. data/lib/honeybadger/cli.rb +14 -14
  30. data/lib/honeybadger/config/defaults.rb +336 -333
  31. data/lib/honeybadger/config/env.rb +42 -42
  32. data/lib/honeybadger/config/ruby.rb +146 -146
  33. data/lib/honeybadger/config/yaml.rb +76 -76
  34. data/lib/honeybadger/config.rb +413 -413
  35. data/lib/honeybadger/const.rb +20 -20
  36. data/lib/honeybadger/context_manager.rb +55 -55
  37. data/lib/honeybadger/conversions.rb +16 -16
  38. data/lib/honeybadger/init/rails.rb +38 -38
  39. data/lib/honeybadger/init/rake.rb +66 -66
  40. data/lib/honeybadger/init/ruby.rb +11 -11
  41. data/lib/honeybadger/init/sinatra.rb +51 -51
  42. data/lib/honeybadger/logging.rb +177 -177
  43. data/lib/honeybadger/notice.rb +579 -568
  44. data/lib/honeybadger/plugin.rb +210 -210
  45. data/lib/honeybadger/plugins/breadcrumbs.rb +111 -111
  46. data/lib/honeybadger/plugins/delayed_job/plugin.rb +56 -56
  47. data/lib/honeybadger/plugins/delayed_job.rb +22 -22
  48. data/lib/honeybadger/plugins/faktory.rb +52 -52
  49. data/lib/honeybadger/plugins/lambda.rb +71 -71
  50. data/lib/honeybadger/plugins/local_variables.rb +44 -44
  51. data/lib/honeybadger/plugins/passenger.rb +23 -23
  52. data/lib/honeybadger/plugins/rails.rb +72 -63
  53. data/lib/honeybadger/plugins/resque.rb +72 -72
  54. data/lib/honeybadger/plugins/shoryuken.rb +52 -52
  55. data/lib/honeybadger/plugins/sidekiq.rb +71 -62
  56. data/lib/honeybadger/plugins/sucker_punch.rb +18 -18
  57. data/lib/honeybadger/plugins/thor.rb +32 -32
  58. data/lib/honeybadger/plugins/warden.rb +19 -19
  59. data/lib/honeybadger/rack/error_notifier.rb +92 -92
  60. data/lib/honeybadger/rack/user_feedback.rb +88 -88
  61. data/lib/honeybadger/rack/user_informer.rb +45 -45
  62. data/lib/honeybadger/ruby.rb +2 -2
  63. data/lib/honeybadger/singleton.rb +103 -103
  64. data/lib/honeybadger/tasks.rb +22 -22
  65. data/lib/honeybadger/templates/feedback_form.erb +84 -84
  66. data/lib/honeybadger/util/http.rb +92 -92
  67. data/lib/honeybadger/util/lambda.rb +32 -32
  68. data/lib/honeybadger/util/request_hash.rb +73 -73
  69. data/lib/honeybadger/util/request_payload.rb +41 -41
  70. data/lib/honeybadger/util/revision.rb +39 -39
  71. data/lib/honeybadger/util/sanitizer.rb +214 -214
  72. data/lib/honeybadger/util/sql.rb +34 -34
  73. data/lib/honeybadger/util/stats.rb +50 -50
  74. data/lib/honeybadger/version.rb +4 -4
  75. data/lib/honeybadger/worker.rb +253 -253
  76. data/lib/honeybadger.rb +11 -11
  77. data/resources/ca-bundle.crt +3376 -3376
  78. data/vendor/capistrano-honeybadger/lib/capistrano/honeybadger.rb +5 -5
  79. data/vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap +89 -89
  80. data/vendor/capistrano-honeybadger/lib/honeybadger/capistrano/legacy.rb +47 -47
  81. data/vendor/capistrano-honeybadger/lib/honeybadger/capistrano.rb +2 -2
  82. data/vendor/cli/inifile.rb +628 -628
  83. data/vendor/cli/thor/actions/create_file.rb +103 -103
  84. data/vendor/cli/thor/actions/create_link.rb +59 -59
  85. data/vendor/cli/thor/actions/directory.rb +118 -118
  86. data/vendor/cli/thor/actions/empty_directory.rb +135 -135
  87. data/vendor/cli/thor/actions/file_manipulation.rb +316 -316
  88. data/vendor/cli/thor/actions/inject_into_file.rb +107 -107
  89. data/vendor/cli/thor/actions.rb +319 -319
  90. data/vendor/cli/thor/base.rb +656 -656
  91. data/vendor/cli/thor/command.rb +133 -133
  92. data/vendor/cli/thor/core_ext/hash_with_indifferent_access.rb +77 -77
  93. data/vendor/cli/thor/core_ext/io_binary_read.rb +10 -10
  94. data/vendor/cli/thor/core_ext/ordered_hash.rb +98 -98
  95. data/vendor/cli/thor/error.rb +32 -32
  96. data/vendor/cli/thor/group.rb +281 -281
  97. data/vendor/cli/thor/invocation.rb +178 -178
  98. data/vendor/cli/thor/line_editor/basic.rb +35 -35
  99. data/vendor/cli/thor/line_editor/readline.rb +88 -88
  100. data/vendor/cli/thor/line_editor.rb +17 -17
  101. data/vendor/cli/thor/parser/argument.rb +73 -73
  102. data/vendor/cli/thor/parser/arguments.rb +175 -175
  103. data/vendor/cli/thor/parser/option.rb +125 -125
  104. data/vendor/cli/thor/parser/options.rb +218 -218
  105. data/vendor/cli/thor/parser.rb +4 -4
  106. data/vendor/cli/thor/rake_compat.rb +71 -71
  107. data/vendor/cli/thor/runner.rb +322 -322
  108. data/vendor/cli/thor/shell/basic.rb +421 -421
  109. data/vendor/cli/thor/shell/color.rb +149 -149
  110. data/vendor/cli/thor/shell/html.rb +126 -126
  111. data/vendor/cli/thor/shell.rb +81 -81
  112. data/vendor/cli/thor/util.rb +267 -267
  113. data/vendor/cli/thor/version.rb +3 -3
  114. data/vendor/cli/thor.rb +484 -484
  115. metadata +10 -5
@@ -1,488 +1,488 @@
1
- require 'forwardable'
2
-
3
- require 'honeybadger/version'
4
- require 'honeybadger/config'
5
- require 'honeybadger/context_manager'
6
- require 'honeybadger/notice'
7
- require 'honeybadger/plugin'
8
- require 'honeybadger/logging'
9
- require 'honeybadger/worker'
10
- require 'honeybadger/breadcrumbs'
11
-
12
- module Honeybadger
13
- # The Honeybadger agent contains all the methods for interacting with the
14
- # Honeybadger service. It can be used to send notifications to multiple
15
- # projects in large apps. The global agent instance ({Agent.instance}) should
16
- # always be accessed through the {Honeybadger} singleton.
17
- #
18
- # === Context
19
- #
20
- # Context is global by default, meaning agents created via
21
- # +Honeybadger::Agent.new+ will share context (added via
22
- # +Honeybadger.context+ or {Honeybadger::Agent#context}) with other agents.
23
- # This also includes the Rack environment when using the
24
- # {Honeybadger::Rack::ErrorNotifier} middleware. To localize context for a
25
- # custom agent, use the +local_context: true+ option when initializing.
26
- #
27
- # @example
28
- #
29
- # # Standard usage:
30
- # OtherBadger = Honeybadger::Agent.new
31
- #
32
- # # With local context:
33
- # OtherBadger = Honeybadger::Agent.new(local_context: true)
34
- #
35
- # OtherBadger.configure do |config|
36
- # config.api_key = 'project api key'
37
- # end
38
- #
39
- # begin
40
- # # Risky operation
41
- # rescue => e
42
- # OtherBadger.notify(e)
43
- # end
44
- class Agent
45
- extend Forwardable
46
-
47
- include Logging::Helper
48
-
49
- # @api private
50
- def self.instance
51
- @instance
52
- end
53
-
54
- # @api private
55
- def self.instance=(instance)
56
- @instance = instance
57
- end
58
-
59
- def initialize(opts = {})
60
- if opts.kind_of?(Config)
61
- @config = opts
62
- opts = {}
63
- end
64
-
65
- @context = opts.delete(:context)
66
- local_context = opts.delete(:local_context)
67
-
68
- @config ||= Config.new(opts)
69
-
70
- if local_context
71
- @context ||= ContextManager.new
72
- @breadcrumbs = Breadcrumbs::Collector.new(config)
73
- else
74
- @breadcrumbs = nil
75
- end
76
-
77
- init_worker
78
- end
79
-
80
- # Sends an exception to Honeybadger. Does not report ignored exceptions by
81
- # default.
82
- #
83
- # @example
84
- # # With an exception:
85
- # begin
86
- # fail 'oops'
87
- # rescue => exception
88
- # Honeybadger.notify(exception, context: {
89
- # my_data: 'value'
90
- # }) # => '-1dfb92ae-9b01-42e9-9c13-31205b70744a'
91
- # end
92
- #
93
- # # Custom notification:
94
- # Honeybadger.notify('Something went wrong.', {
95
- # error_class: 'MyClass',
96
- # context: {my_data: 'value'}
97
- # }) # => '06220c5a-b471-41e5-baeb-de247da45a56'
98
- #
99
- # @param [Exception, Hash, Object] exception_or_opts An Exception object,
100
- # or a Hash of options which is used to build the notice. All other types
101
- # of objects will be converted to a String and used as the :error_message.
102
- # @param [Hash] opts The options Hash when the first argument is an Exception.
103
- #
104
- # @option opts [String] :error_message The error message.
105
- # @option opts [String] :error_class ('Notice') The class name of the error.
106
- # @option opts [Array] :backtrace The backtrace of the error (optional).
107
- # @option opts [String] :fingerprint The grouping fingerprint of the exception (optional).
108
- # @option opts [Boolean] :force (false) Always report the exception when true, even when ignored (optional).
109
- # @option opts [Boolean] :sync (false) Send data synchronously (skips the worker) (optional).
110
- # @option opts [String] :tags The comma-separated list of tags (optional).
111
- # @option opts [Hash] :context The context to associate with the exception (optional).
112
- # @option opts [String] :controller The controller name (such as a Rails controller) (optional).
113
- # @option opts [String] :action The action name (such as a Rails controller action) (optional).
114
- # @option opts [Hash] :parameters The HTTP request paramaters (optional).
115
- # @option opts [Hash] :session The HTTP request session (optional).
116
- # @option opts [String] :url The HTTP request URL (optional).
117
- # @option opts [Exception] :cause The cause for this error (optional).
118
- #
119
- # @return [String] UUID reference to the notice within Honeybadger.
120
- # @return [false] when ignored.
121
- def notify(exception_or_opts, opts = {})
122
- opts = opts.dup
123
-
124
- if exception_or_opts.is_a?(Exception)
125
- already_reported_notice_id = exception_or_opts.instance_variable_get(:@__hb_notice_id)
126
- return already_reported_notice_id if already_reported_notice_id
127
-
128
- opts[:exception] = exception_or_opts
129
- elsif exception_or_opts.respond_to?(:to_hash)
130
- opts.merge!(exception_or_opts.to_hash)
131
- else
132
- opts[:error_message] = exception_or_opts.to_s
133
- end
134
-
135
- validate_notify_opts!(opts)
136
-
137
- add_breadcrumb(
138
- "Honeybadger Notice",
139
- metadata: opts,
140
- category: "notice"
141
- ) if config[:'breadcrumbs.enabled']
142
-
143
- opts[:rack_env] ||= context_manager.get_rack_env
144
- opts[:global_context] ||= context_manager.get_context
145
- opts[:breadcrumbs] ||= breadcrumbs.dup
146
-
147
- notice = Notice.new(config, opts)
148
-
149
- config.before_notify_hooks.each do |hook|
150
- break if notice.halted?
151
- with_error_handling { hook.call(notice) }
152
- end
153
-
154
- unless notice.api_key =~ NOT_BLANK
155
- error { sprintf('Unable to send error report: API key is missing. id=%s', notice.id) }
156
- return false
157
- end
158
-
159
- if !opts[:force] && notice.ignore?
160
- debug { sprintf('ignore notice feature=notices id=%s', notice.id) }
161
- return false
162
- end
163
-
164
- if notice.halted?
165
- debug { 'halted notice feature=notices' }
166
- return false
167
- end
168
-
169
- info { sprintf('Reporting error id=%s', notice.id) }
170
-
171
- if opts[:sync] || config[:sync]
172
- send_now(notice)
173
- else
174
- push(notice)
175
- end
176
-
177
- if exception_or_opts.is_a?(Exception)
178
- exception_or_opts.instance_variable_set(:@__hb_notice_id, notice.id) unless exception_or_opts.frozen?
179
- end
180
-
181
- notice.id
182
- end
183
-
184
- # Perform a synchronous check_in.
185
- #
186
- # @example
187
- # Honeybadger.check_in('1MqIo1')
188
- #
189
- # @param [String] id The unique check in id (e.g. '1MqIo1') or the check in url.
190
- #
191
- # @return [Boolean] true if the check in was successful and false
192
- # otherwise.
193
- def check_in(id)
194
- # this is to allow check ins even if a url is passed
195
- check_in_id = id.to_s.strip.gsub(/\/$/, '').split('/').last
196
- response = backend.check_in(check_in_id)
197
- response.success?
198
- end
199
-
200
- # Track a new deployment
201
- #
202
- # @example
203
- # Honeybadger.track_deployment(revision: 'be2ceb6')
204
- #
205
- # @param [String] :environment The environment name. Defaults to the current configured environment.
206
- # @param [String] :revision The VCS revision being deployed. Defaults to the currently configured revision.
207
- # @param [String] :local_username The name of the user who performed the deploy.
208
- # @param [String] :repository The base URL of the VCS repository. It should be HTTPS-style.
209
- #
210
- # @return [Boolean] true if the deployment was successfully tracked and false
211
- # otherwise.
212
- def track_deployment(environment: nil, revision: nil, local_username: nil, repository: nil)
213
- opts = {
214
- environment: environment || config[:env],
215
- revision: revision || config[:revision],
216
- local_username: local_username,
217
- repository: repository
218
- }
219
- response = backend.track_deployment(opts)
220
- response.success?
221
- end
222
-
223
- # Save global context for the current request.
224
- #
225
- # @example
226
- # Honeybadger.context({my_data: 'my value'})
227
- #
228
- # # Inside a Rails controller:
229
- # before_action do
230
- # Honeybadger.context({user_id: current_user.id})
231
- # end
232
- #
233
- # # Explicit conversion
234
- # class User < ActiveRecord::Base
235
- # def to_honeybadger_context
236
- # { user_id: id, user_email: email }
237
- # end
238
- # end
239
- #
240
- # user = User.first
241
- # Honeybadger.context(user)
242
- #
243
- # # Clearing global context:
244
- # Honeybadger.context.clear!
245
- #
246
- # @param [Hash] context A Hash of data which will be sent to Honeybadger
247
- # when an error occurs. If the object responds to +#to_honeybadger_context+,
248
- # the return value of that method will be used (explicit conversion). Can
249
- # include any key/value, but a few keys have a special meaning in
250
- # Honeybadger.
251
- #
252
- # @option context [String] :user_id The user ID used by Honeybadger
253
- # to aggregate user data across occurrences on the error page (optional).
254
- # @option context [String] :user_email The user email address (optional).
255
- # @option context [String] :tags The comma-separated list of tags.
256
- # When present, tags will be applied to errors with this context
257
- # (optional).
258
- #
259
- # @return [self] so that method calls can be chained.
260
- def context(context = nil)
261
- context_manager.set_context(context) unless context.nil?
262
- self
263
- end
264
-
265
- # Clear all transaction scoped data.
266
- def clear!
267
- context_manager.clear!
268
- breadcrumbs.clear!
269
- end
270
-
271
- # Get global context for the current request.
272
- #
273
- # @example
274
- # Honeybadger.context({my_data: 'my value'})
275
- # Honeybadger.get_context # => {my_data: 'my value'}
276
- #
277
- # @return [Hash, nil]
278
- def get_context
279
- context_manager.get_context
280
- end
281
-
282
- # @api private
283
- # Direct access to the Breadcrumbs::Collector instance
284
- def breadcrumbs
285
- return @breadcrumbs if @breadcrumbs
286
-
287
- Thread.current[:__hb_breadcrumbs] ||= Breadcrumbs::Collector.new(config)
288
- end
289
-
290
- # Appends a breadcrumb to the trace. Use this when you want to add some
291
- # custom data to your breadcrumb trace in effort to help debugging. If a
292
- # notice is reported to Honeybadger, all breadcrumbs within the execution
293
- # path will be appended to the notice. You will be able to view the
294
- # breadcrumb trace in the Honeybadger interface to see what events led up
295
- # to the notice.
296
- #
297
- # @example
298
- # Honeybadger.add_breadcrumb("Email Sent", metadata: { user: user.id, message: message })
299
- #
300
- # @param message [String] The message you want to send with the breadcrumb
301
- # @param params [Hash] extra options for breadcrumb building
302
- # @option params [Hash] :metadata Any metadata that you want to pass along
303
- # with the breadcrumb. We only accept a hash with simple primatives as
304
- # values (Strings, Numbers, Booleans & Symbols) (optional)
305
- # @option params [String] :category You can provide a custom category. This
306
- # affects how the breadcrumb is displayed, so we recommend that you pick a
307
- # known category. (optional)
308
- #
309
- # @return self
310
- def add_breadcrumb(message, metadata: {}, category: "custom")
311
- params = Util::Sanitizer.new(max_depth: 2).sanitize({
312
- category: category,
313
- message: message,
314
- metadata: metadata
315
- })
316
-
317
- breadcrumbs.add!(Breadcrumbs::Breadcrumb.new(**params))
318
-
319
- self
320
- end
321
-
322
- # Flushes all data from workers before returning. This is most useful in
323
- # tests when using the test backend, where normally the asynchronous nature
324
- # of this library could create race conditions.
325
- #
326
- # @example
327
- # # Without a block:
328
- # it "sends a notification to Honeybadger" do
329
- # expect {
330
- # Honeybadger.notify(StandardError.new('test backend'))
331
- # Honeybadger.flush
332
- # }.to change(Honeybadger::Backend::Test.notifications[:notices], :size).by(0)
333
- # end
334
- #
335
- # # With a block:
336
- # it "sends a notification to Honeybadger" do
337
- # expect {
338
- # Honeybadger.flush do
339
- # 49.times do
340
- # Honeybadger.notify(StandardError.new('test backend'))
341
- # end
342
- # end
343
- # }.to change(Honeybadger::Backend::Test.notifications[:notices], :size).by(49)
344
- # end
345
- #
346
- # @yield An optional block to execute (exceptions will propagate after
347
- # data is flushed).
348
- #
349
- # @return [Object, Boolean] value of block if block is given, otherwise true
350
- # on success or false if Honeybadger isn't running.
351
- def flush
352
- return true unless block_given?
353
- yield
354
- ensure
355
- worker.flush
356
- end
357
-
358
- # Stops the Honeybadger service.
359
- #
360
- # @example
361
- # Honeybadger.stop # => nil
362
- def stop(force = false)
363
- worker.shutdown(force)
364
- true
365
- end
366
-
367
- # @api private
368
- attr_reader :config
369
-
370
- # Configure the Honeybadger agent via Ruby.
371
- #
372
- # @example
373
- # Honeybadger.configure do |config|
374
- # config.api_key = 'project api key'
375
- # config.exceptions.ignore += [CustomError]
376
- # end
377
- #
378
- # @!method configure
379
- # @yield [Config::Ruby] configuration object.
380
- def_delegator :config, :configure
381
-
382
- # DEPRECATED: Callback to ignore exceptions.
383
- #
384
- # See public API documentation for {Honeybadger::Notice} for available attributes.
385
- #
386
- # @example
387
- # # Ignoring based on error message:
388
- # Honeybadger.exception_filter do |notice|
389
- # notice.error_message =~ /sensitive data/
390
- # end
391
- #
392
- # # Ignore an entire class of exceptions:
393
- # Honeybadger.exception_filter do |notice|
394
- # notice.exception.class < MyError
395
- # end
396
- #
397
- # @!method exception_filter
398
- # @yieldreturn [Boolean] true (to ignore) or false (to send).
399
- def_delegator :config, :exception_filter
400
-
401
- # DEPRECATED: Callback to add a custom grouping strategy for exceptions. The return
402
- # value is hashed and sent to Honeybadger. Errors with the same fingerprint
403
- # will be grouped.
404
- #
405
- # See public API documentation for {Honeybadger::Notice} for available attributes.
406
- #
407
- # @example
408
- # Honeybadger.exception_fingerprint do |notice|
409
- # [notice.error_class, notice.component, notice.backtrace.to_s].join(':')
410
- # end
411
- #
412
- # @!method exception_fingerprint
413
- # @yieldreturn [#to_s] The fingerprint of the error.
414
- def_delegator :config, :exception_fingerprint
415
-
416
- # DEPRECATED: Callback to filter backtrace lines. One use for this is to make
417
- # additional [PROJECT_ROOT] or [GEM_ROOT] substitutions, which are used by
418
- # Honeybadger when grouping errors and displaying application traces.
419
- #
420
- # @example
421
- # Honeybadger.backtrace_filter do |line|
422
- # line.gsub(/^\/my\/unknown\/bundle\/path/, "[GEM_ROOT]")
423
- # end
424
- #
425
- # @!method backtrace_filter
426
- # @yieldparam [String] line The backtrace line to modify.
427
- # @yieldreturn [String] The new (modified) backtrace line.
428
- def_delegator :config, :backtrace_filter
429
-
430
- # @api private
431
- def with_rack_env(rack_env, &block)
432
- context_manager.set_rack_env(rack_env)
433
- yield
434
- ensure
435
- context_manager.set_rack_env(nil)
436
- end
437
-
438
- # @api private
439
- attr_reader :worker
440
-
441
- # @api private
442
- # @!method init!(...)
443
- # @see Config#init!
444
- def_delegators :config, :init!
445
-
446
- # @api private
447
- # @!method backend
448
- # @see Config#backend
449
- def_delegators :config, :backend
450
-
451
- private
452
-
453
- def validate_notify_opts!(opts)
454
- return if opts.has_key?(:exception)
455
- return if opts.has_key?(:error_message)
456
- msg = sprintf('`Honeybadger.notify` was called with invalid arguments. You must pass either an Exception or options Hash containing the `:error_message` key. location=%s', caller[caller.size-1])
457
- raise ArgumentError.new(msg) if config.dev?
458
- warn(msg)
459
- end
460
-
461
- def context_manager
462
- return @context if @context
463
- ContextManager.current
464
- end
465
-
466
- def push(object)
467
- worker.push(object)
468
- true
469
- end
470
-
471
- def send_now(object)
472
- worker.send_now(object)
473
- true
474
- end
475
-
476
- def init_worker
477
- @worker = Worker.new(config)
478
- end
479
-
480
- def with_error_handling
481
- yield
482
- rescue => ex
483
- error { "Rescued an error in a before notify hook: #{ex.message}" }
484
- end
485
-
486
- @instance = new(Config.new)
487
- end
488
- end
1
+ require 'forwardable'
2
+
3
+ require 'honeybadger/version'
4
+ require 'honeybadger/config'
5
+ require 'honeybadger/context_manager'
6
+ require 'honeybadger/notice'
7
+ require 'honeybadger/plugin'
8
+ require 'honeybadger/logging'
9
+ require 'honeybadger/worker'
10
+ require 'honeybadger/breadcrumbs'
11
+
12
+ module Honeybadger
13
+ # The Honeybadger agent contains all the methods for interacting with the
14
+ # Honeybadger service. It can be used to send notifications to multiple
15
+ # projects in large apps. The global agent instance ({Agent.instance}) should
16
+ # always be accessed through the {Honeybadger} singleton.
17
+ #
18
+ # === Context
19
+ #
20
+ # Context is global by default, meaning agents created via
21
+ # +Honeybadger::Agent.new+ will share context (added via
22
+ # +Honeybadger.context+ or {Honeybadger::Agent#context}) with other agents.
23
+ # This also includes the Rack environment when using the
24
+ # {Honeybadger::Rack::ErrorNotifier} middleware. To localize context for a
25
+ # custom agent, use the +local_context: true+ option when initializing.
26
+ #
27
+ # @example
28
+ #
29
+ # # Standard usage:
30
+ # OtherBadger = Honeybadger::Agent.new
31
+ #
32
+ # # With local context:
33
+ # OtherBadger = Honeybadger::Agent.new(local_context: true)
34
+ #
35
+ # OtherBadger.configure do |config|
36
+ # config.api_key = 'project api key'
37
+ # end
38
+ #
39
+ # begin
40
+ # # Risky operation
41
+ # rescue => e
42
+ # OtherBadger.notify(e)
43
+ # end
44
+ class Agent
45
+ extend Forwardable
46
+
47
+ include Logging::Helper
48
+
49
+ # @api private
50
+ def self.instance
51
+ @instance
52
+ end
53
+
54
+ # @api private
55
+ def self.instance=(instance)
56
+ @instance = instance
57
+ end
58
+
59
+ def initialize(opts = {})
60
+ if opts.kind_of?(Config)
61
+ @config = opts
62
+ opts = {}
63
+ end
64
+
65
+ @context = opts.delete(:context)
66
+ local_context = opts.delete(:local_context)
67
+
68
+ @config ||= Config.new(opts)
69
+
70
+ if local_context
71
+ @context ||= ContextManager.new
72
+ @breadcrumbs = Breadcrumbs::Collector.new(config)
73
+ else
74
+ @breadcrumbs = nil
75
+ end
76
+
77
+ init_worker
78
+ end
79
+
80
+ # Sends an exception to Honeybadger. Does not report ignored exceptions by
81
+ # default.
82
+ #
83
+ # @example
84
+ # # With an exception:
85
+ # begin
86
+ # fail 'oops'
87
+ # rescue => exception
88
+ # Honeybadger.notify(exception, context: {
89
+ # my_data: 'value'
90
+ # }) # => '-1dfb92ae-9b01-42e9-9c13-31205b70744a'
91
+ # end
92
+ #
93
+ # # Custom notification:
94
+ # Honeybadger.notify('Something went wrong.', {
95
+ # error_class: 'MyClass',
96
+ # context: {my_data: 'value'}
97
+ # }) # => '06220c5a-b471-41e5-baeb-de247da45a56'
98
+ #
99
+ # @param [Exception, Hash, Object] exception_or_opts An Exception object,
100
+ # or a Hash of options which is used to build the notice. All other types
101
+ # of objects will be converted to a String and used as the :error_message.
102
+ # @param [Hash] opts The options Hash when the first argument is an Exception.
103
+ #
104
+ # @option opts [String] :error_message The error message.
105
+ # @option opts [String] :error_class ('Notice') The class name of the error.
106
+ # @option opts [Array] :backtrace The backtrace of the error (optional).
107
+ # @option opts [String] :fingerprint The grouping fingerprint of the exception (optional).
108
+ # @option opts [Boolean] :force (false) Always report the exception when true, even when ignored (optional).
109
+ # @option opts [Boolean] :sync (false) Send data synchronously (skips the worker) (optional).
110
+ # @option opts [String] :tags The comma-separated list of tags (optional).
111
+ # @option opts [Hash] :context The context to associate with the exception (optional).
112
+ # @option opts [String] :controller The controller name (such as a Rails controller) (optional).
113
+ # @option opts [String] :action The action name (such as a Rails controller action) (optional).
114
+ # @option opts [Hash] :parameters The HTTP request paramaters (optional).
115
+ # @option opts [Hash] :session The HTTP request session (optional).
116
+ # @option opts [String] :url The HTTP request URL (optional).
117
+ # @option opts [Exception] :cause The cause for this error (optional).
118
+ #
119
+ # @return [String] UUID reference to the notice within Honeybadger.
120
+ # @return [false] when ignored.
121
+ def notify(exception_or_opts, opts = {})
122
+ opts = opts.dup
123
+
124
+ if exception_or_opts.is_a?(Exception)
125
+ already_reported_notice_id = exception_or_opts.instance_variable_get(:@__hb_notice_id)
126
+ return already_reported_notice_id if already_reported_notice_id
127
+
128
+ opts[:exception] = exception_or_opts
129
+ elsif exception_or_opts.respond_to?(:to_hash)
130
+ opts.merge!(exception_or_opts.to_hash)
131
+ else
132
+ opts[:error_message] = exception_or_opts.to_s
133
+ end
134
+
135
+ validate_notify_opts!(opts)
136
+
137
+ add_breadcrumb(
138
+ "Honeybadger Notice",
139
+ metadata: opts,
140
+ category: "notice"
141
+ ) if config[:'breadcrumbs.enabled']
142
+
143
+ opts[:rack_env] ||= context_manager.get_rack_env
144
+ opts[:global_context] ||= context_manager.get_context
145
+ opts[:breadcrumbs] ||= breadcrumbs.dup
146
+
147
+ notice = Notice.new(config, opts)
148
+
149
+ config.before_notify_hooks.each do |hook|
150
+ break if notice.halted?
151
+ with_error_handling { hook.call(notice) }
152
+ end
153
+
154
+ unless notice.api_key =~ NOT_BLANK
155
+ error { sprintf('Unable to send error report: API key is missing. id=%s', notice.id) }
156
+ return false
157
+ end
158
+
159
+ if !opts[:force] && notice.ignore?
160
+ debug { sprintf('ignore notice feature=notices id=%s', notice.id) }
161
+ return false
162
+ end
163
+
164
+ if notice.halted?
165
+ debug { 'halted notice feature=notices' }
166
+ return false
167
+ end
168
+
169
+ info { sprintf('Reporting error id=%s', notice.id) }
170
+
171
+ if opts[:sync] || config[:sync]
172
+ send_now(notice)
173
+ else
174
+ push(notice)
175
+ end
176
+
177
+ if exception_or_opts.is_a?(Exception)
178
+ exception_or_opts.instance_variable_set(:@__hb_notice_id, notice.id) unless exception_or_opts.frozen?
179
+ end
180
+
181
+ notice.id
182
+ end
183
+
184
+ # Perform a synchronous check_in.
185
+ #
186
+ # @example
187
+ # Honeybadger.check_in('1MqIo1')
188
+ #
189
+ # @param [String] id The unique check in id (e.g. '1MqIo1') or the check in url.
190
+ #
191
+ # @return [Boolean] true if the check in was successful and false
192
+ # otherwise.
193
+ def check_in(id)
194
+ # this is to allow check ins even if a url is passed
195
+ check_in_id = id.to_s.strip.gsub(/\/$/, '').split('/').last
196
+ response = backend.check_in(check_in_id)
197
+ response.success?
198
+ end
199
+
200
+ # Track a new deployment
201
+ #
202
+ # @example
203
+ # Honeybadger.track_deployment(revision: 'be2ceb6')
204
+ #
205
+ # @param [String] :environment The environment name. Defaults to the current configured environment.
206
+ # @param [String] :revision The VCS revision being deployed. Defaults to the currently configured revision.
207
+ # @param [String] :local_username The name of the user who performed the deploy.
208
+ # @param [String] :repository The base URL of the VCS repository. It should be HTTPS-style.
209
+ #
210
+ # @return [Boolean] true if the deployment was successfully tracked and false
211
+ # otherwise.
212
+ def track_deployment(environment: nil, revision: nil, local_username: nil, repository: nil)
213
+ opts = {
214
+ environment: environment || config[:env],
215
+ revision: revision || config[:revision],
216
+ local_username: local_username,
217
+ repository: repository
218
+ }
219
+ response = backend.track_deployment(opts)
220
+ response.success?
221
+ end
222
+
223
+ # Save global context for the current request.
224
+ #
225
+ # @example
226
+ # Honeybadger.context({my_data: 'my value'})
227
+ #
228
+ # # Inside a Rails controller:
229
+ # before_action do
230
+ # Honeybadger.context({user_id: current_user.id})
231
+ # end
232
+ #
233
+ # # Explicit conversion
234
+ # class User < ActiveRecord::Base
235
+ # def to_honeybadger_context
236
+ # { user_id: id, user_email: email }
237
+ # end
238
+ # end
239
+ #
240
+ # user = User.first
241
+ # Honeybadger.context(user)
242
+ #
243
+ # # Clearing global context:
244
+ # Honeybadger.context.clear!
245
+ #
246
+ # @param [Hash] context A Hash of data which will be sent to Honeybadger
247
+ # when an error occurs. If the object responds to +#to_honeybadger_context+,
248
+ # the return value of that method will be used (explicit conversion). Can
249
+ # include any key/value, but a few keys have a special meaning in
250
+ # Honeybadger.
251
+ #
252
+ # @option context [String] :user_id The user ID used by Honeybadger
253
+ # to aggregate user data across occurrences on the error page (optional).
254
+ # @option context [String] :user_email The user email address (optional).
255
+ # @option context [String] :tags The comma-separated list of tags.
256
+ # When present, tags will be applied to errors with this context
257
+ # (optional).
258
+ #
259
+ # @return [self] so that method calls can be chained.
260
+ def context(context = nil)
261
+ context_manager.set_context(context) unless context.nil?
262
+ self
263
+ end
264
+
265
+ # Clear all transaction scoped data.
266
+ def clear!
267
+ context_manager.clear!
268
+ breadcrumbs.clear!
269
+ end
270
+
271
+ # Get global context for the current request.
272
+ #
273
+ # @example
274
+ # Honeybadger.context({my_data: 'my value'})
275
+ # Honeybadger.get_context # => {my_data: 'my value'}
276
+ #
277
+ # @return [Hash, nil]
278
+ def get_context
279
+ context_manager.get_context
280
+ end
281
+
282
+ # @api private
283
+ # Direct access to the Breadcrumbs::Collector instance
284
+ def breadcrumbs
285
+ return @breadcrumbs if @breadcrumbs
286
+
287
+ Thread.current[:__hb_breadcrumbs] ||= Breadcrumbs::Collector.new(config)
288
+ end
289
+
290
+ # Appends a breadcrumb to the trace. Use this when you want to add some
291
+ # custom data to your breadcrumb trace in effort to help debugging. If a
292
+ # notice is reported to Honeybadger, all breadcrumbs within the execution
293
+ # path will be appended to the notice. You will be able to view the
294
+ # breadcrumb trace in the Honeybadger interface to see what events led up
295
+ # to the notice.
296
+ #
297
+ # @example
298
+ # Honeybadger.add_breadcrumb("Email Sent", metadata: { user: user.id, message: message })
299
+ #
300
+ # @param message [String] The message you want to send with the breadcrumb
301
+ # @param params [Hash] extra options for breadcrumb building
302
+ # @option params [Hash] :metadata Any metadata that you want to pass along
303
+ # with the breadcrumb. We only accept a hash with simple primatives as
304
+ # values (Strings, Numbers, Booleans & Symbols) (optional)
305
+ # @option params [String] :category You can provide a custom category. This
306
+ # affects how the breadcrumb is displayed, so we recommend that you pick a
307
+ # known category. (optional)
308
+ #
309
+ # @return self
310
+ def add_breadcrumb(message, metadata: {}, category: "custom")
311
+ params = Util::Sanitizer.new(max_depth: 2).sanitize({
312
+ category: category,
313
+ message: message,
314
+ metadata: metadata
315
+ })
316
+
317
+ breadcrumbs.add!(Breadcrumbs::Breadcrumb.new(**params))
318
+
319
+ self
320
+ end
321
+
322
+ # Flushes all data from workers before returning. This is most useful in
323
+ # tests when using the test backend, where normally the asynchronous nature
324
+ # of this library could create race conditions.
325
+ #
326
+ # @example
327
+ # # Without a block:
328
+ # it "sends a notification to Honeybadger" do
329
+ # expect {
330
+ # Honeybadger.notify(StandardError.new('test backend'))
331
+ # Honeybadger.flush
332
+ # }.to change(Honeybadger::Backend::Test.notifications[:notices], :size).by(0)
333
+ # end
334
+ #
335
+ # # With a block:
336
+ # it "sends a notification to Honeybadger" do
337
+ # expect {
338
+ # Honeybadger.flush do
339
+ # 49.times do
340
+ # Honeybadger.notify(StandardError.new('test backend'))
341
+ # end
342
+ # end
343
+ # }.to change(Honeybadger::Backend::Test.notifications[:notices], :size).by(49)
344
+ # end
345
+ #
346
+ # @yield An optional block to execute (exceptions will propagate after
347
+ # data is flushed).
348
+ #
349
+ # @return [Object, Boolean] value of block if block is given, otherwise true
350
+ # on success or false if Honeybadger isn't running.
351
+ def flush
352
+ return true unless block_given?
353
+ yield
354
+ ensure
355
+ worker.flush
356
+ end
357
+
358
+ # Stops the Honeybadger service.
359
+ #
360
+ # @example
361
+ # Honeybadger.stop # => nil
362
+ def stop(force = false)
363
+ worker.shutdown(force)
364
+ true
365
+ end
366
+
367
+ # @api private
368
+ attr_reader :config
369
+
370
+ # Configure the Honeybadger agent via Ruby.
371
+ #
372
+ # @example
373
+ # Honeybadger.configure do |config|
374
+ # config.api_key = 'project api key'
375
+ # config.exceptions.ignore += [CustomError]
376
+ # end
377
+ #
378
+ # @!method configure
379
+ # @yield [Config::Ruby] configuration object.
380
+ def_delegator :config, :configure
381
+
382
+ # DEPRECATED: Callback to ignore exceptions.
383
+ #
384
+ # See public API documentation for {Honeybadger::Notice} for available attributes.
385
+ #
386
+ # @example
387
+ # # Ignoring based on error message:
388
+ # Honeybadger.exception_filter do |notice|
389
+ # notice.error_message =~ /sensitive data/
390
+ # end
391
+ #
392
+ # # Ignore an entire class of exceptions:
393
+ # Honeybadger.exception_filter do |notice|
394
+ # notice.exception.class < MyError
395
+ # end
396
+ #
397
+ # @!method exception_filter
398
+ # @yieldreturn [Boolean] true (to ignore) or false (to send).
399
+ def_delegator :config, :exception_filter
400
+
401
+ # DEPRECATED: Callback to add a custom grouping strategy for exceptions. The return
402
+ # value is hashed and sent to Honeybadger. Errors with the same fingerprint
403
+ # will be grouped.
404
+ #
405
+ # See public API documentation for {Honeybadger::Notice} for available attributes.
406
+ #
407
+ # @example
408
+ # Honeybadger.exception_fingerprint do |notice|
409
+ # [notice.error_class, notice.component, notice.backtrace.to_s].join(':')
410
+ # end
411
+ #
412
+ # @!method exception_fingerprint
413
+ # @yieldreturn [#to_s] The fingerprint of the error.
414
+ def_delegator :config, :exception_fingerprint
415
+
416
+ # DEPRECATED: Callback to filter backtrace lines. One use for this is to make
417
+ # additional [PROJECT_ROOT] or [GEM_ROOT] substitutions, which are used by
418
+ # Honeybadger when grouping errors and displaying application traces.
419
+ #
420
+ # @example
421
+ # Honeybadger.backtrace_filter do |line|
422
+ # line.gsub(/^\/my\/unknown\/bundle\/path/, "[GEM_ROOT]")
423
+ # end
424
+ #
425
+ # @!method backtrace_filter
426
+ # @yieldparam [String] line The backtrace line to modify.
427
+ # @yieldreturn [String] The new (modified) backtrace line.
428
+ def_delegator :config, :backtrace_filter
429
+
430
+ # @api private
431
+ def with_rack_env(rack_env, &block)
432
+ context_manager.set_rack_env(rack_env)
433
+ yield
434
+ ensure
435
+ context_manager.set_rack_env(nil)
436
+ end
437
+
438
+ # @api private
439
+ attr_reader :worker
440
+
441
+ # @api private
442
+ # @!method init!(...)
443
+ # @see Config#init!
444
+ def_delegators :config, :init!
445
+
446
+ # @api private
447
+ # @!method backend
448
+ # @see Config#backend
449
+ def_delegators :config, :backend
450
+
451
+ private
452
+
453
+ def validate_notify_opts!(opts)
454
+ return if opts.has_key?(:exception)
455
+ return if opts.has_key?(:error_message)
456
+ msg = sprintf('`Honeybadger.notify` was called with invalid arguments. You must pass either an Exception or options Hash containing the `:error_message` key. location=%s', caller[caller.size-1])
457
+ raise ArgumentError.new(msg) if config.dev?
458
+ warn(msg)
459
+ end
460
+
461
+ def context_manager
462
+ return @context if @context
463
+ ContextManager.current
464
+ end
465
+
466
+ def push(object)
467
+ worker.push(object)
468
+ true
469
+ end
470
+
471
+ def send_now(object)
472
+ worker.send_now(object)
473
+ true
474
+ end
475
+
476
+ def init_worker
477
+ @worker = Worker.new(config)
478
+ end
479
+
480
+ def with_error_handling
481
+ yield
482
+ rescue => ex
483
+ error { "Rescued an error in a before notify hook: #{ex.message}" }
484
+ end
485
+
486
+ @instance = new(Config.new)
487
+ end
488
+ end