honeybadger 4.3.1 → 4.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 154fe2f9fde40cf439b82bf35d5fb32474304e79b29fa6202d7de64920475435
4
- data.tar.gz: 347644c0b97c94f0b9995186bd2ebbdda67ea9a8af9933defe07370f29203980
3
+ metadata.gz: 5bd1d88346f34070874c2929f1f322ed318157fd7772d2d89848685d3e23adcb
4
+ data.tar.gz: dada0e8a7039c5750a7406e5c5c8452ae942c369ebac17e816ba15a977b1ea33
5
5
  SHA512:
6
- metadata.gz: aebf85527c70c7cf5531e12d18625ce8a61f0ad005a9a07a274ca59cc38146cea10f48c344d19091adadf64da779f93c313c18b0943d552acbeec2887f25e18a
7
- data.tar.gz: 4975a88db66f69085b14446d7d3e9ff16722d0391f4fcb4ddb4e59893c001e988a418e0928fe6e1bfae3e1d6d113adb482927d5aab92435bca7556f32f82fa2b
6
+ metadata.gz: ad7229d00efd55f9a1f02b32d510bb7f97f022df4de9713e750063539ef67db1a482841e51b079c1b7b6659e161bb2235d41273095c3e776b5b8cb536a6af755
7
+ data.tar.gz: f6ee33891464589594aa9c3b4cdf3489943b9a4accf99e199723b395d999a0b45b242f9e510d7f3466c6133aca47b8abff8927ef9ac022b58fc00f285421cb22
data/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.4.0] - 2019-07-24
9
+ ### Added
10
+ - Added the ability to store and send Breadcrumbs along with the notice.
11
+ Breadcrumbs are disabled by default in this version so they must be enabled
12
+ via the config (option `breadcrumbs.enabled`) to work.
13
+
8
14
  ## [4.3.1] - 2019-05-30
9
15
  ### Fixed
10
16
  - Add Rails 6 RC1 Support
@@ -7,6 +7,7 @@ require 'honeybadger/notice'
7
7
  require 'honeybadger/plugin'
8
8
  require 'honeybadger/logging'
9
9
  require 'honeybadger/worker'
10
+ require 'honeybadger/breadcrumbs'
10
11
 
11
12
  module Honeybadger
12
13
  # The Honeybadger agent contains all the methods for interacting with the
@@ -62,7 +63,10 @@ module Honeybadger
62
63
  end
63
64
 
64
65
  @context = opts.delete(:context)
65
- @context ||= ContextManager.new if opts.delete(:local_context)
66
+ if opts.delete(:local_context)
67
+ @context ||= ContextManager.new
68
+ @breadcrumbs = Breadcrumbs::Collector.new(config)
69
+ end
66
70
 
67
71
  @config ||= Config.new(opts)
68
72
 
@@ -120,8 +124,15 @@ module Honeybadger
120
124
 
121
125
  validate_notify_opts!(opts)
122
126
 
127
+ add_breadcrumb(
128
+ "Honeybadger Notice",
129
+ metadata: opts,
130
+ category: "notice"
131
+ ) if config[:'breadcrumbs.enabled']
132
+
123
133
  opts[:rack_env] ||= context_manager.get_rack_env
124
134
  opts[:global_context] ||= context_manager.get_context
135
+ opts[:breadcrumbs] ||= breadcrumbs.dup
125
136
 
126
137
  notice = Notice.new(config, opts)
127
138
 
@@ -214,10 +225,10 @@ module Honeybadger
214
225
  self
215
226
  end
216
227
 
217
- # @api private
218
- # Used to clear context via `#context.clear!`.
219
- def clear! # :nodoc:
228
+ # Clear all transaction scoped data.
229
+ def clear!
220
230
  context_manager.clear!
231
+ breadcrumbs.clear!
221
232
  end
222
233
 
223
234
  # Get global context for the current request.
@@ -231,6 +242,46 @@ module Honeybadger
231
242
  context_manager.get_context
232
243
  end
233
244
 
245
+ # @api private
246
+ # Direct access to the Breadcrumbs::Collector instance
247
+ def breadcrumbs
248
+ return @breadcrumbs if @breadcrumbs
249
+
250
+ Thread.current[:__hb_breadcrumbs] ||= Breadcrumbs::Collector.new(config)
251
+ end
252
+
253
+ # Appends a breadcrumb to the trace. Use this when you want to add some
254
+ # custom data to your breadcrumb trace in effort to help debugging. If a
255
+ # notice is reported to Honeybadger, all breadcrumbs within the execution
256
+ # path will be appended to the notice. You will be able to view the
257
+ # breadcrumb trace in the Honeybadger interface to see what events led up
258
+ # to the notice.
259
+ #
260
+ # @example
261
+ # Honeybadger.add_breadcrumb("Email Sent", metadata: { user: user.id, message: message })
262
+ #
263
+ # @param message [String] The message you want to send with the breadcrumb
264
+ # @param params [Hash] extra options for breadcrumb building
265
+ # @option params [Hash] :metadata Any metadata that you want to pass along
266
+ # with the breadcrumb. We only accept a hash with simple primatives as
267
+ # values (Strings, Numbers, Booleans & Symbols) (optional)
268
+ # @option params [String] :category You can provide a custom category. This
269
+ # affects how the breadcrumb is displayed, so we recommend that you pick a
270
+ # known category. (optional)
271
+ #
272
+ # @return self
273
+ def add_breadcrumb(message, metadata: {}, category: "custom")
274
+ params = Util::Sanitizer.new(max_depth: 2).sanitize({
275
+ category: category,
276
+ message: message,
277
+ metadata: metadata
278
+ })
279
+
280
+ breadcrumbs.add!(Breadcrumbs::Breadcrumb.new(params))
281
+
282
+ self
283
+ end
284
+
234
285
  # Flushes all data from workers before returning. This is most useful in
235
286
  # tests when using the test backend, where normally the asynchronous nature
236
287
  # of this library could create race conditions.
@@ -0,0 +1,8 @@
1
+ require 'honeybadger/breadcrumbs/ring_buffer'
2
+ require 'honeybadger/breadcrumbs/breadcrumb'
3
+ require 'honeybadger/breadcrumbs/collector'
4
+
5
+ module Honeybadger
6
+ module Breadcrumbs
7
+ end
8
+ end
@@ -0,0 +1,108 @@
1
+ require 'honeybadger/util/sql'
2
+
3
+ module Honeybadger
4
+ module Breadcrumbs
5
+ class ActiveSupport
6
+ def self.default_notifications
7
+ {
8
+ # ActiveRecord Actions
9
+ #
10
+ "sql.active_record" => {
11
+ message: lambda do |data|
12
+ # Disregard empty string names
13
+ name = data[:name] if data[:name] && !data[:name].strip.empty?
14
+
15
+ ["Active Record", name].compact.join(" - ")
16
+ end,
17
+ category: "query",
18
+ select_keys: [:sql, :name, :connection_id, :cached],
19
+ transform: lambda do |data|
20
+ if data[:sql]
21
+ adapter = ::ActiveRecord::Base.connection_config[:adapter]
22
+ data[:sql] = Util::SQL.obfuscate(data[:sql], adapter)
23
+ end
24
+ data
25
+ end,
26
+ exclude_when: lambda do |data|
27
+ # Ignore schema, begin, and commit transaction queries
28
+ data[:name] == "SCHEMA" || (data[:sql] && (data[:sql] =~ /^(begin|commit)( transaction)?$/i))
29
+ end
30
+ },
31
+
32
+ # ActionCable Actions
33
+ #
34
+ "perform_action.action_cable" => {
35
+ message: "Action Cable Perform Action",
36
+ select_keys: [:channel_class, :action],
37
+ category: "render"
38
+ },
39
+
40
+ # ActiveJob Actions
41
+ #
42
+ "enqueue.active_job" => {
43
+ message: "Active Job Enqueue",
44
+ select_keys: [],
45
+ category: "job"
46
+ },
47
+ "perform_start.active_job" => {
48
+ message: "Active Job Perform Start",
49
+ select_keys: [],
50
+ category: "job",
51
+ },
52
+
53
+ # ActiveSupport Actions
54
+ #
55
+ "cache_read.active_support" => {
56
+ message: "Active Support Cache Read",
57
+ category: "query"
58
+ },
59
+ "cache_fetch_hit.active_support" => {
60
+ message: "Active Support Cache Fetch Hit",
61
+ category: "query"
62
+ },
63
+
64
+ # Controller Actions
65
+ #
66
+ "halted_callback.action_controller" => {
67
+ message: "Action Controller Callback Halted",
68
+ category: "request",
69
+ },
70
+ "process_action.action_controller" => {
71
+ message: "Action Controller Action Process",
72
+ select_keys: [:controller, :action, :format, :method, :path, :status, :view_runtime, :db_runtime],
73
+ category: "request",
74
+ },
75
+ "start_processing.action_controller" => {
76
+ message: "Action Controller Start Process",
77
+ select_keys: [:controller, :action, :format, :method, :path],
78
+ category: "request",
79
+ },
80
+ "redirect_to.action_controller" => {
81
+ message: "Action Controller Redirect",
82
+ category: "request",
83
+ },
84
+
85
+ # View Actions
86
+ #
87
+ "render_template.action_view" => {
88
+ message: "Action View Template Render",
89
+ category: "render",
90
+ },
91
+ "render_partial.action_view" => {
92
+ message: "Action View Partial Render",
93
+ category: "render",
94
+ },
95
+
96
+ # Mailer actions
97
+ #
98
+ "deliver.action_mailer" => {
99
+ message: "Action Mailer Deliver",
100
+ select_keys: [:mailer, :message_id, :from, :date],
101
+ category: "render"
102
+ }
103
+ }
104
+ end
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,53 @@
1
+ require 'time'
2
+
3
+ module Honeybadger
4
+ module Breadcrumbs
5
+ class Breadcrumb
6
+ # Raw breadcrumb data structure
7
+ #
8
+ attr_reader :category, :timestamp
9
+ attr_accessor :message, :metadata, :active
10
+
11
+ include Comparable
12
+
13
+ def initialize(category: "custom", message: nil, metadata: {})
14
+ @active = true
15
+ @timestamp = Time.now.utc
16
+
17
+ @category = category
18
+ @message = message
19
+ @metadata = metadata.is_a?(Hash) ? metadata : {}
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ category: category,
25
+ message: message,
26
+ metadata: metadata,
27
+ timestamp: timestamp.iso8601(3)
28
+ }
29
+ end
30
+
31
+ def <=>(other)
32
+ to_h <=> other.to_h
33
+ end
34
+
35
+
36
+ # Is the Breadcrumb active or not. Inactive Breadcrumbs not be included
37
+ # with any outgoing payloads.
38
+ #
39
+ # @return [Boolean]
40
+ def active?
41
+ @active
42
+ end
43
+
44
+ # Sets the breadcrumb to inactive
45
+ #
46
+ # @return self
47
+ def ignore!
48
+ @active = false
49
+ self
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,82 @@
1
+ require 'forwardable'
2
+
3
+ module Honeybadger
4
+ module Breadcrumbs
5
+ class Collector
6
+ include Enumerable
7
+ extend Forwardable
8
+ # The Collector manages breadcrumbs and provides an interface for accessing
9
+ # and affecting breadcrumbs
10
+ #
11
+ # Most actions are delegated to the current buffer implementation. A
12
+ # Buffer must implement all delegated methods to work with the Collector.
13
+
14
+ # Flush all breadcrumbs, delegates to buffer
15
+ def_delegator :@buffer, :clear!
16
+
17
+ # Iterate over all Breadcrumbs and satify Enumerable, delegates to buffer
18
+ # @yield [Object] sequentially gives breadcrumbs to the block
19
+ def_delegator :@buffer, :each
20
+
21
+ # Raw Array of Breadcrumbs, delegates to buffer
22
+ # @return [Array] Raw set of breadcrumbs
23
+ def_delegator :@buffer, :to_a
24
+
25
+ # Last item added to the buffer
26
+ # @return [Breadcrumb]
27
+ def_delegator :@buffer, :previous
28
+
29
+ def initialize(config, buffer = RingBuffer.new)
30
+ @config = config
31
+ @buffer = buffer
32
+ end
33
+
34
+ # Add Breadcrumb to stack
35
+ #
36
+ # @return [self] Filtered breadcrumbs
37
+ def add!(breadcrumb)
38
+ return unless @config[:'breadcrumbs.enabled']
39
+ @buffer.add!(breadcrumb)
40
+
41
+ self
42
+ end
43
+
44
+ alias_method :<<, :add!
45
+
46
+ # @api private
47
+ # Removes the prevous breadcrumb from the buffer if the supplied
48
+ # block returns a falsy value
49
+ #
50
+ def drop_previous_breadcrumb_if
51
+ @buffer.drop if (previous && block_given? && yield(previous))
52
+ end
53
+
54
+ # All active breadcrumbs you want to remove a breadcrumb from the trail,
55
+ # then you can selectively ignore breadcrumbs while building a notice.
56
+ #
57
+ # @return [Array] Active breadcrumbs
58
+ def trail
59
+ select(&:active?)
60
+ end
61
+
62
+ def to_h
63
+ {
64
+ enabled: @config[:'breadcrumbs.enabled'],
65
+ trail: trail.map(&:to_h)
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ # @api private
72
+ # Since the collector is shared with the worker thread, there is a chance
73
+ # it can be cleared before we have prepared the request. We provide the
74
+ # ability to duplicate a collector which should also duplicate the buffer
75
+ # instance, as that holds the breadcrumbs.
76
+ def initialize_dup(source)
77
+ @buffer = source.instance_variable_get(:@buffer).dup
78
+ super
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,50 @@
1
+ module Honeybadger
2
+ module Breadcrumbs
3
+ # @api private
4
+ #
5
+ module LogWrapper
6
+ def add(severity, message = nil, progname = nil)
7
+ message, progname = [progname, nil] if message.nil?
8
+ message = message && message.strip
9
+ unless should_ignore_log?(message, progname)
10
+ Honeybadger.add_breadcrumb(message, category: :log, metadata: {
11
+ severity: format_severity(severity),
12
+ progname: progname
13
+ })
14
+ end
15
+
16
+ super
17
+ end
18
+
19
+ private
20
+
21
+ def should_ignore_log?(message, progname)
22
+ message.nil? ||
23
+ message == "" ||
24
+ Thread.current[:__hb_within_log_subscriber] ||
25
+ progname == "honeybadger"
26
+ end
27
+ end
28
+
29
+ # @api private
30
+ #
31
+ # This module is designed to be prepended into the
32
+ # ActiveSupport::LogSubscriber for the sole purpose of silencing breadcrumb
33
+ # log events. Since we already have specific breadcrumb events for each
34
+ # class that provides LogSubscriber events, we want to filter out those
35
+ # logs as they just become noise.
36
+ module LogSubscriberInjector
37
+ %w(info debug warn error fatal unknown).each do |level|
38
+ define_method(level) do |*args|
39
+ begin
40
+ Thread.current[:__hb_within_log_subscriber] = true
41
+ super(*args)
42
+ ensure
43
+ Thread.current[:__hb_within_log_subscriber] = false
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,44 @@
1
+ module Honeybadger
2
+ module Breadcrumbs
3
+ class RingBuffer
4
+ # Simple ring buffer implementation that keeps item count constrained using
5
+ # a rolling window. Items from the front of the buffer are dropped as more
6
+ # are pushed on the end of the stack.
7
+ include Enumerable
8
+
9
+ attr_reader :buffer
10
+
11
+ def initialize(buffer_size = 40)
12
+ @buffer_size = buffer_size
13
+ clear!
14
+ end
15
+
16
+ def add!(item)
17
+ @buffer << item
18
+ @ct += 1
19
+ @buffer.shift(1) if @ct > @buffer_size
20
+ end
21
+
22
+ def clear!
23
+ @buffer = []
24
+ @ct = 0
25
+ end
26
+
27
+ def to_a
28
+ @buffer
29
+ end
30
+
31
+ def each(&blk)
32
+ @buffer.each(&blk)
33
+ end
34
+
35
+ def previous
36
+ @buffer.last
37
+ end
38
+
39
+ def drop
40
+ @buffer.pop
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,4 +1,5 @@
1
1
  require 'socket'
2
+ require 'honeybadger/breadcrumbs/active_support'
2
3
 
3
4
  module Honeybadger
4
5
  class Config
@@ -287,6 +288,21 @@ module Honeybadger
287
288
  description: 'Send exceptions when retrying job.',
288
289
  default: true,
289
290
  type: Boolean
291
+ },
292
+ :'breadcrumbs.enabled' => {
293
+ description: 'Enable/Disable breadcrumb functionality.',
294
+ default: false,
295
+ type: Boolean
296
+ },
297
+ :'breadcrumbs.active_support_notifications' => {
298
+ description: 'Configuration for automatic Active Support Instrumentation events.',
299
+ default: Breadcrumbs::ActiveSupport.default_notifications,
300
+ type: Hash
301
+ },
302
+ :'breadcrumbs.logging.enabled' => {
303
+ description: 'Enable/Disable automatic breadcrumbs from log messages.',
304
+ default: true,
305
+ type: Boolean
290
306
  }
291
307
  }.freeze
292
308
 
@@ -1,9 +1,12 @@
1
+ require 'set'
2
+
1
3
  module Honeybadger
2
4
  class Config
3
5
  module Env
4
6
  CONFIG_KEY = /\AHONEYBADGER_(.+)\Z/.freeze
5
7
  CONFIG_MAPPING = Hash[DEFAULTS.keys.map {|k| [k.to_s.upcase.gsub(KEY_REPLACEMENT, '_'), k] }].freeze
6
8
  ARRAY_VALUES = Regexp.new('\s*,\s*').freeze
9
+ IGNORED_TYPES = Set[Hash]
7
10
 
8
11
  def self.new(env = ENV)
9
12
  hash = {}
@@ -11,7 +14,9 @@ module Honeybadger
11
14
  env.each_pair do |k,v|
12
15
  next unless k.match(CONFIG_KEY)
13
16
  next unless config_key = CONFIG_MAPPING[$1]
14
- hash[config_key] = cast_value(v, OPTIONS[config_key][:type])
17
+ type = OPTIONS[config_key][:type]
18
+ next if IGNORED_TYPES.include?(type)
19
+ hash[config_key] = cast_value(v, type)
15
20
  end
16
21
 
17
22
  hash
@@ -16,7 +16,7 @@ module Honeybadger
16
16
  Honeybadger.notify(ex, origin: :rake, component: reconstruct_command_line)
17
17
  display_error_message_without_honeybadger(ex)
18
18
  ensure
19
- Honeybadger.context.clear!
19
+ Honeybadger.clear!
20
20
  end
21
21
 
22
22
  def reconstruct_command_line
@@ -7,6 +7,7 @@ module Honeybadger
7
7
  # @api private
8
8
  module Logging
9
9
  PREFIX = '** [Honeybadger] '.freeze
10
+ LOGGER_PROG = "honeybadger".freeze
10
11
 
11
12
  # Logging helper methods. Requires a Honeybadger::Config @config instance
12
13
  # variable to exist and/or #logger to be defined. Each method is
@@ -93,12 +94,16 @@ module Honeybadger
93
94
  @logger = logger
94
95
  end
95
96
 
96
- def_delegators :@logger, :level, :add, :debug?, :info?, :warn?, :error?
97
+ def add(severity, msg, progname=LOGGER_PROG)
98
+ @logger.add(severity, msg, progname)
99
+ end
100
+
101
+ def_delegators :@logger, :level, :debug?, :info?, :warn?, :error?
97
102
  end
98
103
 
99
104
  class FormattedLogger < StandardLogger
100
- def add(severity, msg)
101
- super(severity, format_message(msg))
105
+ def add(severity, msg, progname=LOGGER_PROG)
106
+ super(severity, format_message(msg), progname)
102
107
  end
103
108
 
104
109
  private
@@ -115,6 +115,9 @@ module Honeybadger
115
115
  # Deprecated: Excerpt from source file.
116
116
  attr_reader :source
117
117
 
118
+ # @return [Breadcrumbs::Collector] The collection of captured breadcrumbs
119
+ attr_accessor :breadcrumbs
120
+
118
121
  # @api private
119
122
  # Cache project path substitutions for backtrace lines.
120
123
  PROJECT_ROOT_CACHE = {}
@@ -184,6 +187,8 @@ module Honeybadger
184
187
 
185
188
  self.session = opts[:session][:data] if opts[:session] && opts[:session][:data]
186
189
 
190
+ self.breadcrumbs = opts[:breadcrumbs] || Breadcrumbs::Collector.new(config)
191
+
187
192
  # Fingerprint must be calculated last since callback operates on `self`.
188
193
  self.fingerprint = fingerprint_from_opts(opts)
189
194
  end
@@ -200,6 +205,7 @@ module Honeybadger
200
205
  {
201
206
  api_key: s(api_key),
202
207
  notifier: NOTIFIER,
208
+ breadcrumbs: sanitized_breadcrumbs,
203
209
  error: {
204
210
  token: id,
205
211
  class: s(error_class),
@@ -354,6 +360,12 @@ module Honeybadger
354
360
  Context(object)
355
361
  end
356
362
 
363
+ # Sanitize at the depth of 4 since we are sanitizing the breadcrumb root
364
+ # hash data structure.
365
+ def sanitized_breadcrumbs
366
+ Util::Sanitizer.new(max_depth: 4).sanitize(breadcrumbs.to_h)
367
+ end
368
+
357
369
  def construct_context_hash(opts, exception)
358
370
  context = {}
359
371
  context.merge!(Context(opts[:global_context]))
@@ -0,0 +1,109 @@
1
+ require 'honeybadger/plugin'
2
+ require 'honeybadger/breadcrumbs/logging'
3
+
4
+ module Honeybadger
5
+ module Plugins
6
+ # @api private
7
+ #
8
+ # This plugin pounces on the dynamic nature of Ruby / Rails to inject into
9
+ # the runtime and provide automatic breadcrumb events.
10
+ #
11
+ # === Log events
12
+ #
13
+ # All log messages within the execution path will automatically be appened
14
+ # to the breadcrumb trace. You can disable all log events in the
15
+ # Honeybadger config:
16
+ #
17
+ # @example
18
+ #
19
+ # Honeybadger.configure do |config|
20
+ # config.breadcrumbs.logging.enabled = false
21
+ # end
22
+ #
23
+ # === ActiveSupport Breadcrumbs
24
+ #
25
+ # We hook into Rails's ActiveSupport Instrumentation system to provide
26
+ # automatic breadcrumb event generation. You can customize these events by
27
+ # passing a Hash into the honeybadger configuration. The simplest method is
28
+ # to alter the current defaults:
29
+ #
30
+ # @example
31
+ # notifications = Honeybadger::Breadcrumbs::ActiveSupport.default_notifications
32
+ # notifications.delete("sql.active_record")
33
+ # notifications["enqueue.active_job"][:exclude_when] = lambda do |data|
34
+ # data[:job].topic == "salmon_activity"
35
+ # end
36
+ #
37
+ # Honeybadger.configure do |config|
38
+ # config.breadcrumbs.active_support_notifications = notifications
39
+ # end
40
+ #
41
+ # See RailsBreadcrumbs.send_breadcrumb_notification for specifics on the
42
+ # options for customization
43
+ Plugin.register :breadcrumbs do
44
+ requirement { config[:'breadcrumbs.enabled'] }
45
+
46
+ execution do
47
+ # Rails specific breadcrumb events
48
+ #
49
+ if defined?(::Rails.application) && ::Rails.application
50
+ config[:'breadcrumbs.active_support_notifications'].each do |name, config|
51
+ RailsBreadcrumbs.subscribe_to_notification(name, config)
52
+ end
53
+ ActiveSupport::LogSubscriber.prepend(Honeybadger::Breadcrumbs::LogSubscriberInjector) if config[:'breadcrumbs.logging.enabled']
54
+ end
55
+
56
+ ::Logger.prepend(Honeybadger::Breadcrumbs::LogWrapper) if config[:'breadcrumbs.logging.enabled']
57
+ end
58
+ end
59
+
60
+ class RailsBreadcrumbs
61
+ # @api private
62
+ # Used internally for sending out Rails Instrumentation breadcrumbs.
63
+ #
64
+ # @param [String] name The ActiveSupport instrumentation key
65
+ # @param [Number] duration The time spent in the instrumentation event
66
+ # @param [Hash] notification_config The instrumentation event configuration
67
+ # @param [Hash] data Custom metadata from the instrumentation event
68
+ #
69
+ # @option notification_config [String | Proc] :message A message that describes the event. You can dynamically build the message by passing a proc that accepts the event metadata.
70
+ # @option notification_config [Symbol] :category A key to group specific types of events
71
+ # @option notification_config [Array] :select_keys A set of keys that filters what data we select from the instrumentation data (optional)
72
+ # @option notification_config [Proc] :exclude_when A proc that accepts the data payload. A truthy return value will exclude this event from the payload (optional)
73
+ # @option notification_config [Proc] :transform A proc that accepts the data payload. The return value will replace the current data hash (optional)
74
+ #
75
+ def self.send_breadcrumb_notification(name, duration, notification_config, data = {})
76
+ return if notification_config[:exclude_when] && notification_config[:exclude_when].call(data)
77
+
78
+ message =
79
+ case (m = notification_config[:message])
80
+ when Proc
81
+ m.call(data)
82
+ when String
83
+ m
84
+ else
85
+ name
86
+ end
87
+
88
+ data = data.slice(*notification_config[:select_keys]) if notification_config[:select_keys]
89
+ data = notification_config[:transform].call(data) if notification_config[:transform]
90
+ data = data.is_a?(Hash) ? data : {}
91
+
92
+ data[:duration] = duration
93
+
94
+ Honeybadger.add_breadcrumb(
95
+ message,
96
+ category: notification_config[:category] || :custom,
97
+ metadata: data
98
+ )
99
+ end
100
+
101
+ # @api private
102
+ def self.subscribe_to_notification(name, notification_config)
103
+ ActiveSupport::Notifications.subscribe(name) do |_, started, finished, _, data|
104
+ send_breadcrumb_notification(name, finished - started, notification_config, data)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -46,7 +46,7 @@ module Honeybadger
46
46
  ) if job.attempts.to_i >= ::Honeybadger.config[:'delayed_job.attempt_threshold'].to_i
47
47
  raise error
48
48
  ensure
49
- ::Honeybadger.context.clear!
49
+ ::Honeybadger.clear!
50
50
  end
51
51
  end
52
52
  end
@@ -10,7 +10,7 @@ module Honeybadger
10
10
  def around_perform_with_honeybadger(*args)
11
11
  Honeybadger.flush { yield }
12
12
  ensure
13
- Honeybadger.context.clear!
13
+ Honeybadger.clear!
14
14
  end
15
15
 
16
16
  # Error notifications must be synchronous as the +on_failure+ hook is
@@ -16,7 +16,7 @@ module Honeybadger
16
16
  raise e
17
17
  end
18
18
  ensure
19
- Honeybadger.context.clear!
19
+ Honeybadger.clear!
20
20
  end
21
21
 
22
22
  private
@@ -6,7 +6,7 @@ module Honeybadger
6
6
  module Sidekiq
7
7
  class Middleware
8
8
  def call(worker, msg, queue)
9
- Honeybadger.context.clear!
9
+ Honeybadger.clear!
10
10
  yield
11
11
  end
12
12
  end
@@ -23,7 +23,7 @@ module Honeybadger
23
23
 
24
24
  def initialize(app, agent = nil)
25
25
  @app = app
26
- @agent = agent.kind_of?(Agent) ? agent : Honeybadger::Agent.instance
26
+ @agent = agent.kind_of?(Agent) && agent
27
27
  end
28
28
 
29
29
  def call(env)
@@ -44,15 +44,18 @@ module Honeybadger
44
44
  response
45
45
  end
46
46
  ensure
47
- agent.context.clear!
47
+ agent.clear!
48
48
  end
49
49
 
50
50
  private
51
51
 
52
- attr_reader :agent
53
52
  def_delegator :agent, :config
54
53
  def_delegator :config, :logger
55
54
 
55
+ def agent
56
+ @agent || Honeybadger::Agent.instance
57
+ end
58
+
56
59
  def ignored_user_agent?(env)
57
60
  true if config[:'exceptions.ignored_user_agents'].
58
61
  flatten.
@@ -61,6 +64,22 @@ module Honeybadger
61
64
 
62
65
  def notify_honeybadger(exception, env)
63
66
  return if ignored_user_agent?(env)
67
+
68
+ if config[:'breadcrumbs.enabled']
69
+ # Drop the last breadcrumb only if the message contains the error class name
70
+ agent.breadcrumbs.drop_previous_breadcrumb_if do |bc|
71
+ bc.category == "log" && bc.message.include?(exception.class.to_s)
72
+ end
73
+
74
+ agent.add_breadcrumb(
75
+ exception.class,
76
+ metadata: {
77
+ exception_message: exception.message
78
+ },
79
+ category: "error"
80
+ )
81
+ end
82
+
64
83
  agent.notify(exception)
65
84
  end
66
85
 
@@ -23,7 +23,7 @@ module Honeybadger
23
23
 
24
24
  def initialize(app, agent = nil)
25
25
  @app = app
26
- @agent = agent.kind_of?(Agent) ? agent : Honeybadger::Agent.instance
26
+ @agent = agent.kind_of?(Agent) && agent
27
27
  end
28
28
 
29
29
  def call(env)
@@ -76,9 +76,13 @@ module Honeybadger
76
76
 
77
77
  private
78
78
 
79
- attr_reader :agent
80
79
  def_delegator :agent, :config
81
80
  def_delegator :config, :logger
81
+
82
+ def agent
83
+ @agent || Honeybadger::Agent.instance
84
+ end
85
+
82
86
  end
83
87
  end
84
88
  end
@@ -9,7 +9,7 @@ module Honeybadger
9
9
 
10
10
  def initialize(app, agent = nil)
11
11
  @app = app
12
- @agent = agent.kind_of?(Agent) ? agent : Honeybadger::Agent.instance
12
+ @agent = agent.kind_of?(Agent) && agent
13
13
  end
14
14
 
15
15
  def replacement(with)
@@ -34,9 +34,12 @@ module Honeybadger
34
34
 
35
35
  private
36
36
 
37
- attr_reader :agent
38
37
  def_delegator :agent, :config
39
38
  def_delegator :config, :logger
39
+
40
+ def agent
41
+ @agent || Honeybadger::Agent.instance
42
+ end
40
43
  end
41
44
  end
42
45
  end
@@ -34,6 +34,9 @@ module Honeybadger
34
34
  def_delegator :'Honeybadger::Agent.instance', :exception_filter
35
35
  def_delegator :'Honeybadger::Agent.instance', :exception_fingerprint
36
36
  def_delegator :'Honeybadger::Agent.instance', :backtrace_filter
37
+ def_delegator :'Honeybadger::Agent.instance', :add_breadcrumb
38
+ def_delegator :'Honeybadger::Agent.instance', :breadcrumbs
39
+ def_delegator :'Honeybadger::Agent.instance', :clear!
37
40
 
38
41
  # @!macro [attach] def_delegator
39
42
  # @!method $2(...)
@@ -0,0 +1,25 @@
1
+ module Honeybadger
2
+ module Util
3
+ class SQL
4
+ EscapedQuotes = /(\\"|\\')/.freeze
5
+ SQuotedData = /'(?:[^']|'')*'/.freeze
6
+ DQuotedData = /"(?:[^"]|"")*"/.freeze
7
+ NumericData = /\b\d+\b/.freeze
8
+ Newline = /\n/.freeze
9
+ Replacement = "?".freeze
10
+ EmptyReplacement = "".freeze
11
+ DoubleQuoters = /(postgres|sqlite|postgis)/.freeze
12
+
13
+ def self.obfuscate(sql, adapter)
14
+ sql.dup.tap do |s|
15
+ s.gsub!(EscapedQuotes, EmptyReplacement)
16
+ s.gsub!(SQuotedData, Replacement)
17
+ s.gsub!(DQuotedData, Replacement) if adapter =~ DoubleQuoters
18
+ s.gsub!(NumericData, Replacement)
19
+ s.gsub!(Newline, EmptyReplacement)
20
+ s.squeeze!(' ')
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,4 @@
1
1
  module Honeybadger
2
2
  # The current String Honeybadger version.
3
- VERSION = '4.3.1'.freeze
3
+ VERSION = '4.4.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeybadger
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.1
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Honeybadger Industries LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-31 00:00:00.000000000 Z
11
+ date: 2019-07-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Make managing application errors a more pleasant experience.
14
14
  email:
@@ -32,6 +32,12 @@ files:
32
32
  - lib/honeybadger/backend/server.rb
33
33
  - lib/honeybadger/backend/test.rb
34
34
  - lib/honeybadger/backtrace.rb
35
+ - lib/honeybadger/breadcrumbs.rb
36
+ - lib/honeybadger/breadcrumbs/active_support.rb
37
+ - lib/honeybadger/breadcrumbs/breadcrumb.rb
38
+ - lib/honeybadger/breadcrumbs/collector.rb
39
+ - lib/honeybadger/breadcrumbs/logging.rb
40
+ - lib/honeybadger/breadcrumbs/ring_buffer.rb
35
41
  - lib/honeybadger/cli.rb
36
42
  - lib/honeybadger/cli/deploy.rb
37
43
  - lib/honeybadger/cli/exec.rb
@@ -56,6 +62,7 @@ files:
56
62
  - lib/honeybadger/logging.rb
57
63
  - lib/honeybadger/notice.rb
58
64
  - lib/honeybadger/plugin.rb
65
+ - lib/honeybadger/plugins/breadcrumbs.rb
59
66
  - lib/honeybadger/plugins/delayed_job.rb
60
67
  - lib/honeybadger/plugins/delayed_job/plugin.rb
61
68
  - lib/honeybadger/plugins/local_variables.rb
@@ -79,6 +86,7 @@ files:
79
86
  - lib/honeybadger/util/request_payload.rb
80
87
  - lib/honeybadger/util/revision.rb
81
88
  - lib/honeybadger/util/sanitizer.rb
89
+ - lib/honeybadger/util/sql.rb
82
90
  - lib/honeybadger/util/stats.rb
83
91
  - lib/honeybadger/version.rb
84
92
  - lib/honeybadger/worker.rb