honeybadger 5.10.2 → 5.11.1

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: 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