honeybadger 4.3.1 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
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