honeybadger 5.10.2 → 5.11.1

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: 731b775f529f5438fd4062d5bebb966c9725ecc6cbbd8a1f1f62a92ae55ca9dd
4
- data.tar.gz: 1ce0d3753f7d9b8500cba7453a282ae30f2017331bb03af8c604a19a3376f3e4
3
+ metadata.gz: 238281f2485599809a1cbde2313f3b3dbcce4e8ef2fb251b47190d6e2f5c5ce4
4
+ data.tar.gz: 82c41d922f89c1e9f0d3392086d6e3f2c2437842dba791715df324eacb3bf150
5
5
  SHA512:
6
- metadata.gz: 6c59f15225ef73eb3dc066a88eb442a2f3cc64e475ab6c32b8997298d084318d56e994a81f38263ff6bf813a569dcb11d2850374dba0d4bbc86f8ff6c4b87895
7
- data.tar.gz: 21e79a0a3483a5cbf6a1fbe70508d9d550b38a392c716fe9a23500050c01e42642b2ca1a73cc29708931d92b52375b801c1cf5c8141a6d8395c3536e4c1116f6
6
+ metadata.gz: 9186cbd806cd63e77c15b355502a1f3f666469b294324c01c55a92b85f29aaa7d5e5ba51b008f57e8f484ac9089679236cd7038dd8af40a7f97b91f05ef0cfde
7
+ data.tar.gz: 0f95f4ed9bd07fa00d57c0443b279283275824acd6d7dba60dc36b04cd26c1575b706ae633c7bb579a6d6cc44a328b70c898de68998940512b70563608fca442
data/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # Change Log
2
2
 
3
3
 
4
+ ## [5.11.1](https://github.com/honeybadger-io/honeybadger-ruby/compare/v5.11.0...v5.11.1) (2024-06-07)
5
+
6
+
7
+ ### Bug Fixes
8
+
9
+ * do GoodJob.on_thread_error check via hash instead of method ([#558](https://github.com/honeybadger-io/honeybadger-ruby/issues/558)) ([d2aa464](https://github.com/honeybadger-io/honeybadger-ruby/commit/d2aa4640e371e3985310fb30ad5a356807d2bab3))
10
+
11
+ ## [5.11.0](https://github.com/honeybadger-io/honeybadger-ruby/compare/v5.10.2...v5.11.0) (2024-06-04)
12
+
13
+
14
+ ### Features
15
+
16
+ * add insights instrumentation - events and metrics ([#539](https://github.com/honeybadger-io/honeybadger-ruby/issues/539)) ([d173ac5](https://github.com/honeybadger-io/honeybadger-ruby/commit/d173ac53b45be6b9036c292d8efc5002d8b354b1))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * access GoodJob config via Rails.application.config ([#554](https://github.com/honeybadger-io/honeybadger-ruby/issues/554)) ([37b7786](https://github.com/honeybadger-io/honeybadger-ruby/commit/37b7786e9fefdaa23ccd45ca55a0573b0a832f58))
22
+
4
23
  ## [5.10.2](https://github.com/honeybadger-io/honeybadger-ruby/compare/v5.10.1...v5.10.2) (2024-05-24)
5
24
 
6
25
 
@@ -8,7 +8,10 @@ require 'honeybadger/plugin'
8
8
  require 'honeybadger/logging'
9
9
  require 'honeybadger/worker'
10
10
  require 'honeybadger/events_worker'
11
+ require 'honeybadger/metrics_worker'
11
12
  require 'honeybadger/breadcrumbs'
13
+ require 'honeybadger/registry'
14
+ require 'honeybadger/registry_execution'
12
15
 
13
16
  module Honeybadger
14
17
  # The Honeybadger agent contains all the methods for interacting with the
@@ -358,6 +361,7 @@ module Honeybadger
358
361
  ensure
359
362
  worker.flush
360
363
  events_worker&.flush
364
+ metrics_worker&.flush
361
365
  end
362
366
 
363
367
  # Stops the Honeybadger service.
@@ -367,6 +371,7 @@ module Honeybadger
367
371
  def stop(force = false)
368
372
  worker.shutdown(force)
369
373
  events_worker&.shutdown(force)
374
+ metrics_worker&.shutdown(force)
370
375
  true
371
376
  end
372
377
 
@@ -387,20 +392,46 @@ module Honeybadger
387
392
  def event(event_type, payload = {})
388
393
  init_events_worker
389
394
 
390
- ts = DateTime.now.new_offset(0).rfc3339
395
+ ts = Time.now.utc.strftime("%FT%T.%LZ")
391
396
  merged = {ts: ts}
392
397
 
393
398
  if event_type.is_a?(String)
394
- merged.merge!(event_type: event_type)
399
+ merged[:event_type] = event_type
395
400
  else
396
401
  merged.merge!(Hash(event_type))
397
402
  end
398
403
 
404
+ if (request_id = context_manager.get_request_id)
405
+ merged[:request_id] = request_id
406
+ end
407
+
408
+ if config[:'events.attach_hostname']
409
+ merged[:hostname] = config[:hostname].to_s
410
+ end
411
+
399
412
  merged.merge!(Hash(payload))
400
413
 
414
+ return if config.ignored_events.any? { |check| merged[:event_type]&.match?(check) }
415
+
401
416
  events_worker.push(merged)
402
417
  end
403
418
 
419
+ # @api private
420
+ def collect(collector)
421
+ return unless config.insights_enabled?
422
+
423
+ init_metrics_worker
424
+ metrics_worker.push(collector)
425
+ end
426
+
427
+ # @api private
428
+ def registry
429
+ return @registry if defined?(@registry)
430
+ @registry = Honeybadger::Registry.new.tap do |r|
431
+ collect(Honeybadger::RegistryExecution.new(r, config, {}))
432
+ end
433
+ end
434
+
404
435
  # @api private
405
436
  attr_reader :config
406
437
 
@@ -467,13 +498,15 @@ module Honeybadger
467
498
  # @api private
468
499
  def with_rack_env(rack_env, &block)
469
500
  context_manager.set_rack_env(rack_env)
501
+ context_manager.set_request_id(rack_env["action_dispatch.request_id"] || SecureRandom.uuid)
470
502
  yield
471
503
  ensure
472
504
  context_manager.set_rack_env(nil)
505
+ context_manager.set_request_id(nil)
473
506
  end
474
507
 
475
508
  # @api private
476
- attr_reader :worker, :events_worker
509
+ attr_reader :worker, :events_worker, :metrics_worker
477
510
 
478
511
  # @api private
479
512
  # @!method init!(...)
@@ -485,6 +518,35 @@ module Honeybadger
485
518
  # @see Config#backend
486
519
  def_delegators :config, :backend
487
520
 
521
+ # @api private
522
+ # @!method time
523
+ # @see Honeybadger::Instrumentation#time
524
+ def_delegator :instrumentation, :time
525
+
526
+ # @api private
527
+ # @!method histogram
528
+ # @see Honeybadger::Instrumentation#histogram
529
+ def_delegator :instrumentation, :histogram
530
+
531
+ # @api private
532
+ # @!method gauge
533
+ # @see Honeybadger::Instrumentation#gauge
534
+ def_delegator :instrumentation, :gauge
535
+
536
+ # @api private
537
+ # @!method increment_counter
538
+ # @see Honeybadger::Instrumentation#increment_counter
539
+ def_delegator :instrumentation, :increment_counter
540
+
541
+ # @api private
542
+ # @!method decrement_counter
543
+ # @see Honeybadger::Instrumentation#decrement_counter
544
+ def_delegator :instrumentation, :decrement_counter
545
+
546
+ def instrumentation
547
+ @instrumentation ||= Honeybadger::Instrumentation.new(self)
548
+ end
549
+
488
550
  private
489
551
 
490
552
  def validate_notify_opts!(opts)
@@ -520,6 +582,11 @@ module Honeybadger
520
582
  @events_worker = EventsWorker.new(config)
521
583
  end
522
584
 
585
+ def init_metrics_worker
586
+ return if @metrics_worker
587
+ @metrics_worker = MetricsWorker.new(config)
588
+ end
589
+
523
590
  def with_error_handling
524
591
  yield
525
592
  rescue => ex
@@ -91,9 +91,14 @@ module Honeybadger
91
91
  default: 100,
92
92
  type: Integer
93
93
  },
94
+ :'events.max_queue_size' => {
95
+ description: 'Maximum number of event for the event worker queue.',
96
+ default: 10000,
97
+ type: Integer
98
+ },
94
99
  :'events.batch_size' => {
95
100
  description: 'Send events batch if n events have accumulated',
96
- default: 100,
101
+ default: 1000,
97
102
  type: Integer
98
103
  },
99
104
  :'events.timeout' => {
@@ -101,6 +106,16 @@ module Honeybadger
101
106
  default: 30_000,
102
107
  type: Integer
103
108
  },
109
+ :'events.attach_hostname' => {
110
+ description: 'Add the hostname to all event paylaods.',
111
+ default: true,
112
+ type: Boolean
113
+ },
114
+ :'events.ignore' => {
115
+ description: 'A list of events to ignore. Use a string to specify exact matches, or regex for more flexibility.',
116
+ default: [],
117
+ type: Array
118
+ },
104
119
  plugins: {
105
120
  description: 'An optional list of plugins to load. Default is to load all plugins.',
106
121
  default: nil,
@@ -311,6 +326,36 @@ module Honeybadger
311
326
  default: true,
312
327
  type: Boolean
313
328
  },
329
+ :'sidekiq.insights.cluster_collection' => {
330
+ description: 'Collect cluster based metrics for Sidekiq.',
331
+ default: true,
332
+ type: Boolean
333
+ },
334
+ :'sidekiq.insights.collection_interval' => {
335
+ description: 'The frequency in which Sidekiq cluster metrics are sampled.',
336
+ default: 60,
337
+ type: Integer
338
+ },
339
+ :'solid_queue.insights.cluster_collection' => {
340
+ description: 'Collect cluster based metrics for SolidQueue.',
341
+ default: true,
342
+ type: Boolean
343
+ },
344
+ :'solid_queue.insights.collection_interval' => {
345
+ description: 'The frequency in which SolidQueue cluster metrics are sampled.',
346
+ default: 60,
347
+ type: Integer
348
+ },
349
+ :'net_http.insights.enabled' => {
350
+ description: 'Allow automatic instrumentation of Net::HTTP requests.',
351
+ default: true,
352
+ type: Boolean
353
+ },
354
+ :'net_http.insights.full_url' => {
355
+ description: 'Record the full request url during instrumentation.',
356
+ default: false,
357
+ type: Boolean
358
+ },
314
359
  :'sinatra.enabled' => {
315
360
  description: 'Enable Sinatra auto-initialization.',
316
361
  default: true,
@@ -343,6 +388,16 @@ module Honeybadger
343
388
  description: 'Enable/Disable automatic breadcrumbs from log messages.',
344
389
  default: true,
345
390
  type: Boolean
391
+ },
392
+ :'insights.enabled' => {
393
+ description: "Enable/Disable Honeybadger Insights built-in instrumentation.",
394
+ default: false,
395
+ type: Boolean
396
+ },
397
+ :'insights.registry_flush_interval' => {
398
+ description: "Number of seconds between registry flushes.",
399
+ default: 60,
400
+ type: Integer
346
401
  }
347
402
  }.freeze
348
403
 
@@ -188,6 +188,12 @@ module Honeybadger
188
188
  DEFAULTS[:'exceptions.ignore'] | Array(ignore)
189
189
  end
190
190
 
191
+ def ignored_events
192
+ self[:'events.ignore'].map do |check|
193
+ check.is_a?(String) ? /^#{check}$/ : check
194
+ end
195
+ end
196
+
191
197
  def ca_bundle_path
192
198
  if self[:'connection.system_ssl_cert_chain'] && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
193
199
  OpenSSL::X509::DEFAULT_CERT_FILE
@@ -224,6 +230,10 @@ module Honeybadger
224
230
  self[:max_queue_size]
225
231
  end
226
232
 
233
+ def events_max_queue_size
234
+ self[:'events.max_queue_size']
235
+ end
236
+
227
237
  def events_batch_size
228
238
  self[:'events.batch_size']
229
239
  end
@@ -262,6 +272,28 @@ module Honeybadger
262
272
  includes_token?(self[:plugins], name)
263
273
  end
264
274
 
275
+ def insights_enabled?
276
+ return false if defined?(::Rails.application) && ::Rails.const_defined?("Console")
277
+ !!self[:'insights.enabled']
278
+ end
279
+
280
+ def cluster_collection?(name)
281
+ return false unless insights_enabled?
282
+ return true if self[:"#{name}.insights.cluster_collection"].nil?
283
+ !!self[:"#{name}.insights.cluster_collection"]
284
+ end
285
+
286
+ def collection_interval(name)
287
+ return nil unless insights_enabled?
288
+ self[:"#{name}.insights.collection_interval"]
289
+ end
290
+
291
+ def load_plugin_insights?(name)
292
+ return false unless insights_enabled?
293
+ return true if self[:"#{name}.insights.enabled"].nil?
294
+ !!self[:"#{name}.insights.enabled"]
295
+ end
296
+
265
297
  def root_regexp
266
298
  return @root_regexp if @root_regexp
267
299
  return nil if @no_root
@@ -61,15 +61,24 @@ module Honeybadger
61
61
  @mutex.synchronize { @rack_env }
62
62
  end
63
63
 
64
+ def set_request_id(request_id)
65
+ @mutex.synchronize { @request_id = request_id }
66
+ end
67
+
68
+ def get_request_id
69
+ @mutex.synchronize { @request_id }
70
+ end
71
+
64
72
  private
65
73
 
66
- attr_accessor :custom, :rack_env
74
+ attr_accessor :custom, :rack_env, :request_id
67
75
 
68
76
  def _initialize
69
77
  @mutex.synchronize do
70
78
  @global_context = nil
71
79
  @local_context = nil
72
80
  @rack_env = nil
81
+ @request_id = nil
73
82
  end
74
83
  end
75
84
  end
@@ -0,0 +1,18 @@
1
+ require 'honeybadger/metric'
2
+
3
+ module Honeybadger
4
+ class Counter < Metric
5
+ def count(by=1)
6
+ return unless by
7
+
8
+ @samples += 1
9
+
10
+ @counter ||= 0
11
+ @counter = @counter + by
12
+ end
13
+
14
+ def payloads
15
+ [{ counter: @counter }]
16
+ end
17
+ end
18
+ end
@@ -44,7 +44,7 @@ module Honeybadger
44
44
  def push(msg)
45
45
  return false unless start
46
46
 
47
- if queue.size >= config.max_queue_size
47
+ if queue.size >= config.events_max_queue_size
48
48
  warn { sprintf('Unable to send event; reached max queue size of %s.', queue.size) }
49
49
  return false
50
50
  end
@@ -0,0 +1,30 @@
1
+ require 'honeybadger/metric'
2
+
3
+ module Honeybadger
4
+ class Gauge < Metric
5
+ def record(value)
6
+ return unless value
7
+
8
+ @samples += 1
9
+
10
+ @total ||= 0
11
+ @total = @total + value
12
+
13
+ @min = value if @min.nil? || @min > value
14
+ @max = value if @max.nil? || @max < value
15
+ @avg = @total.to_f / @samples
16
+ @latest = value
17
+ end
18
+
19
+ def payloads
20
+ [
21
+ {
22
+ min: @min,
23
+ max: @max,
24
+ avg: @avg,
25
+ latest: @latest
26
+ }
27
+ ]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ require 'honeybadger/metric'
2
+
3
+ module Honeybadger
4
+ class Histogram < Metric
5
+ DEFAULT_BINS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
6
+ INFINITY = 1e20.to_f # not quite, but pretty much
7
+
8
+ def record(value)
9
+ return unless value
10
+
11
+ @samples += 1
12
+ @bin_counts ||= Hash.new(0)
13
+ @bin_counts[find_bin(value)] += 1
14
+ end
15
+
16
+ def find_bin(value)
17
+ bin = bins.find {|b| b >= value }
18
+ bin = INFINITY if bin.nil?
19
+ bin
20
+ end
21
+
22
+ def bins
23
+ @attributes.fetch(:bins, DEFAULT_BINS).sort
24
+ end
25
+
26
+ def payloads
27
+ [{
28
+ bins: (bins + [INFINITY]).map { |bin| [bin.to_f, @bin_counts[bin]] }
29
+ }]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,124 @@
1
+ require 'honeybadger/histogram'
2
+ require 'honeybadger/timer'
3
+ require 'honeybadger/counter'
4
+ require 'honeybadger/gauge'
5
+
6
+ module Honeybadger
7
+ # +Honeybadger::Instrumentation+ defines the API for collecting metric data from anywhere
8
+ # in an application. These class methods may be used directly, or from the Honeybadger singleton
9
+ # instance. There are three usage variations as show in the example below:
10
+ #
11
+ # @example
12
+ #
13
+ # class TicketsController < ApplicationController
14
+ # def create
15
+ # # pass a block
16
+ # Honeybadger.time('create.ticket') { Ticket.create(params[:ticket]) }
17
+ #
18
+ # # pass a lambda argument
19
+ # Honeybadger.time 'create.ticket', ->{ Ticket.create(params[:ticket]) }
20
+ #
21
+ # # pass the duration argument
22
+ # duration = timing_method { Ticket.create(params[:ticket]) }
23
+ # Honeybadger.time 'create.ticket', duration: duration
24
+ # end
25
+ # end
26
+ #
27
+ #
28
+ class Instrumentation
29
+ attr_reader :agent
30
+
31
+ def initialize(agent)
32
+ @agent = agent
33
+ end
34
+
35
+ def registry
36
+ agent.registry
37
+ end
38
+
39
+ # returns two parameters, the first is the duration of the execution, and the second is
40
+ # the return value of the passed block
41
+ def monotonic_timer
42
+ start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
43
+ result = yield
44
+ finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
45
+ [((finish_time - start_time) * 1000).round(2), result]
46
+ end
47
+
48
+ def time(name, *args)
49
+ attributes = extract_attributes(args)
50
+ callable = extract_callable(args)
51
+ duration = attributes.delete(:duration)
52
+
53
+ if callable
54
+ duration = monotonic_timer{ callable.call }[0]
55
+ elsif block_given?
56
+ duration = monotonic_timer{ yield }[0]
57
+ end
58
+
59
+ raise 'No duration found' if duration.nil?
60
+
61
+ Honeybadger::Timer.register(registry, name, attributes).tap do |timer|
62
+ timer.record(duration)
63
+ end
64
+ end
65
+
66
+ def histogram(name, *args)
67
+ attributes = extract_attributes(args)
68
+ callable = extract_callable(args)
69
+ duration = attributes.delete(:duration)
70
+
71
+ if callable
72
+ duration = monotonic_timer{ callable.call }[0]
73
+ elsif block_given?
74
+ duration = monotonic_timer{ yield }[0]
75
+ end
76
+
77
+ raise 'No duration found' if duration.nil?
78
+
79
+ Honeybadger::Histogram.register(registry, name, attributes).tap do |histogram|
80
+ histogram.record(duration)
81
+ end
82
+ end
83
+
84
+ def increment_counter(name, *args)
85
+ attributes = extract_attributes(args)
86
+ by = extract_callable(args)&.call || attributes.delete(:by) || 1
87
+ by = yield if block_given?
88
+
89
+ Honeybadger::Counter.register(registry, name, attributes).tap do |counter|
90
+ counter.count(by)
91
+ end
92
+ end
93
+
94
+ def decrement_counter(name, *args)
95
+ attributes = extract_attributes(args)
96
+ by = extract_callable(args)&.call || attributes.delete(:by) || 1
97
+ by = yield if block_given?
98
+
99
+ Honeybadger::Counter.register(registry, name, attributes).tap do |counter|
100
+ counter.count(by * -1)
101
+ end
102
+ end
103
+
104
+ def gauge(name, *args)
105
+ attributes = extract_attributes(args)
106
+ value = extract_callable(args)&.call || attributes.delete(:value)
107
+ value = yield if block_given?
108
+
109
+ Honeybadger::Gauge.register(registry, name, attributes).tap do |gauge|
110
+ gauge.record(value)
111
+ end
112
+ end
113
+
114
+ # @api private
115
+ def extract_attributes(args)
116
+ args.select { |a| a.is_a?(Hash) }.first || {}
117
+ end
118
+
119
+ # @api private
120
+ def extract_callable(args)
121
+ args.select { |a| a.respond_to?(:call) }.first
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,120 @@
1
+ require 'honeybadger/instrumentation'
2
+
3
+ module Honeybadger
4
+ # +Honeybadger::InstrumentationHelper+ is a module that can be included into any class. This module
5
+ # provides a convenient DSL around the instrumentation methods to prvoide a cleaner interface.
6
+ # There are three usage variations as show in the example below:
7
+ #
8
+ # @example
9
+ #
10
+ # class TicketsController < ApplicationController
11
+ # include Honeybadger::InstrumentationHelper
12
+ #
13
+ # def create
14
+ # metric_source 'controller'
15
+ # metric_attributes { foo: 'bar' } # These attributes get tagged to all metrics called after.
16
+ #
17
+ # # pass a block
18
+ # time('create.ticket') { Ticket.create(params[:ticket]) }
19
+ #
20
+ # # pass a lambda argument
21
+ # time 'create.ticket', ->{ Ticket.create(params[:ticket]) }
22
+ #
23
+ # # pass the duration argument
24
+ # duration = timing_method { Ticket.create(params[:ticket]) }
25
+ # time 'create.ticket', duration: duration
26
+ # end
27
+ # end
28
+ #
29
+ #
30
+ module InstrumentationHelper
31
+
32
+ # returns two parameters, the first is the duration of the execution, and the second is
33
+ # the return value of the passed block
34
+ def monotonic_timer
35
+ metric_instrumentation.monotonic_timer { yield }
36
+ end
37
+
38
+ def metric_source(source)
39
+ @metric_source = source
40
+ end
41
+
42
+ def metric_agent(agent)
43
+ @metric_agent = agent
44
+ end
45
+
46
+ def metric_instrumentation
47
+ @metric_instrumentation ||= @metric_agent ? Honeybadger::Instrumentation.new(@metric_agent) : Honeybadger.instrumentation
48
+ end
49
+
50
+ def metric_attributes(attributes)
51
+ raise "metric_attributes expects a hash" unless attributes.is_a?(Hash)
52
+ @metric_attributes = attributes
53
+ end
54
+
55
+ def time(name, *args)
56
+ attributes = extract_attributes(args)
57
+ callable = extract_callable(args)
58
+ if callable
59
+ metric_instrumentation.time(name, attributes, ->{ callable.call })
60
+ elsif block_given?
61
+ metric_instrumentation.time(name, attributes, ->{ yield })
62
+ elsif attributes.keys.include?(:duration)
63
+ metric_instrumentation.time(name, attributes)
64
+ end
65
+ end
66
+
67
+ def histogram(name, *args)
68
+ attributes = extract_attributes(args)
69
+ callable = extract_callable(args)
70
+ if callable
71
+ metric_instrumentation.histogram(name, attributes, ->{ callable.call })
72
+ elsif block_given?
73
+ metric_instrumentation.histogram(name, attributes, ->{ yield })
74
+ elsif attributes.keys.include?(:duration)
75
+ metric_instrumentation.histogram(name, attributes)
76
+ end
77
+ end
78
+
79
+ def increment_counter(name, *args)
80
+ attributes = extract_attributes(args)
81
+ by = extract_callable(args)&.call || attributes.delete(:by) || 1
82
+ if block_given?
83
+ metric_instrumentation.increment_counter(name, attributes, ->{ yield })
84
+ else
85
+ metric_instrumentation.increment_counter(name, attributes.merge(by: by))
86
+ end
87
+ end
88
+
89
+ def decrement_counter(name, *args)
90
+ attributes = extract_attributes(args)
91
+ by = extract_callable(args)&.call || attributes.delete(:by) || 1
92
+ if block_given?
93
+ metric_instrumentation.decrement_counter(name, attributes, ->{ yield })
94
+ else
95
+ metric_instrumentation.decrement_counter(name, attributes.merge(by: by))
96
+ end
97
+ end
98
+
99
+ def gauge(name, *args)
100
+ attributes = extract_attributes(args)
101
+ value = extract_callable(args)&.call || attributes.delete(:value)
102
+ if block_given?
103
+ metric_instrumentation.gauge(name, attributes, ->{ yield })
104
+ else
105
+ metric_instrumentation.gauge(name, attributes.merge(value: value))
106
+ end
107
+ end
108
+
109
+ # @api private
110
+ def extract_attributes(args)
111
+ attributes = metric_instrumentation.extract_attributes(args)
112
+ attributes.merge(metric_source: @metric_source).merge(@metric_attributes || {}).compact
113
+ end
114
+
115
+ # @api private
116
+ def extract_callable(args)
117
+ metric_instrumentation.extract_callable(args)
118
+ end
119
+ end
120
+ end