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 +4 -4
- data/CHANGELOG.md +19 -0
- data/lib/honeybadger/agent.rb +70 -3
- data/lib/honeybadger/config/defaults.rb +56 -1
- data/lib/honeybadger/config.rb +32 -0
- data/lib/honeybadger/context_manager.rb +10 -1
- data/lib/honeybadger/counter.rb +18 -0
- data/lib/honeybadger/events_worker.rb +1 -1
- data/lib/honeybadger/gauge.rb +30 -0
- data/lib/honeybadger/histogram.rb +32 -0
- data/lib/honeybadger/instrumentation.rb +124 -0
- data/lib/honeybadger/instrumentation_helper.rb +120 -0
- data/lib/honeybadger/metric.rb +47 -0
- data/lib/honeybadger/metrics_worker.rb +175 -0
- data/lib/honeybadger/notification_subscriber.rb +99 -0
- data/lib/honeybadger/plugin.rb +77 -1
- data/lib/honeybadger/plugins/active_job.rb +16 -4
- data/lib/honeybadger/plugins/autotuner.rb +30 -0
- data/lib/honeybadger/plugins/karafka.rb +17 -2
- data/lib/honeybadger/plugins/net_http.rb +52 -0
- data/lib/honeybadger/plugins/rails.rb +15 -0
- data/lib/honeybadger/plugins/sidekiq.rb +128 -1
- data/lib/honeybadger/plugins/solid_queue.rb +27 -0
- data/lib/honeybadger/plugins/system.rb +16 -0
- data/lib/honeybadger/registry.rb +32 -0
- data/lib/honeybadger/registry_execution.rb +28 -0
- data/lib/honeybadger/singleton.rb +8 -0
- data/lib/honeybadger/timer.rb +6 -0
- data/lib/honeybadger/version.rb +1 -1
- data/lib/puma/plugin/honeybadger.rb +43 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 238281f2485599809a1cbde2313f3b3dbcce4e8ef2fb251b47190d6e2f5c5ce4
|
4
|
+
data.tar.gz: 82c41d922f89c1e9f0d3392086d6e3f2c2437842dba791715df324eacb3bf150
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
|
data/lib/honeybadger/agent.rb
CHANGED
@@ -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 =
|
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
|
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:
|
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
|
|
data/lib/honeybadger/config.rb
CHANGED
@@ -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.
|
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
|