Skip to content

Commit

Permalink
feat: add aggregated metrics for rails and more
Browse files Browse the repository at this point in the history
This adds aggregated metrics for the following plugins:

* rails
* net_http
* sidekiq

Similar to the Karafka plugin, there is now more configuration:

* `rails.insights.events`
* `rails.insights.metrics`
* `sidekiq.insights.events`
* `sidekiq.insights.metrics`
* `net_http.insights.events`
* `net_http.insights.metrics`
  • Loading branch information
roelbondoc committed Nov 6, 2024
1 parent 0efe109 commit bc87b48
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 28 deletions.
40 changes: 40 additions & 0 deletions lib/honeybadger/config/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,36 @@ class Boolean; end
default: 60,
type: Integer
},
:'sidekiq.insights.enabled' => {
description: 'Enable automatic data collection for Sidekiq.',
default: true,
type: Boolean
},
:'sidekiq.insights.events' => {
description: 'Enable automatic event capturing for Sidekiq.',
default: true,
type: Boolean
},
:'sidekiq.insights.metrics' => {
description: 'Enable automatic metric data collection for Sidekiq.',
default: true,
type: Boolean
},
:'rails.insights.enabled' => {
description: 'Enable automatic data collection for Ruby on Rails.',
default: true,
type: Boolean
},
:'rails.insights.events' => {
description: 'Enable automatic event capturing for Ruby on Rails.',
default: true,
type: Boolean
},
:'rails.insights.metrics' => {
description: 'Enable automatic metric data collection for Ruby on Rails.',
default: true,
type: Boolean
},
:'karafka.insights.enabled' => {
description: 'Enable automatic data collection for Karafka.',
default: true,
Expand All @@ -383,6 +413,16 @@ class Boolean; end
default: true,
type: Boolean
},
:'net_http.insights.events' => {
description: 'Enable automatic event capturing for Net::HTTP requests.',
default: true,
type: Boolean
},
:'net_http.insights.metrics' => {
description: 'Enable automatic metric data collection for Net::HTTP requests.',
default: true,
type: Boolean
},
:'net_http.insights.full_url' => {
description: 'Record the full request url during instrumentation.',
default: false,
Expand Down
2 changes: 1 addition & 1 deletion lib/honeybadger/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def gauge(name, *args)
elsif block_given?
value = yield
else
value = attributes.delete(:value)
value = attributes.delete(:duration) || attributes.delete(:value)
end

Honeybadger::Gauge.register(registry, name, attributes).tap do |gauge|
Expand Down
61 changes: 44 additions & 17 deletions lib/honeybadger/notification_subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@

module Honeybadger
class NotificationSubscriber
include Honeybadger::InstrumentationHelper

Metric = Struct.new(:type, :event, :value_key, :context)

RAILS_METRICS = [
Metric.new(:gauge, 'sql.active_record', :duration, %i[query]),

Metric.new(:gauge, 'process_action.action_controller', :duration, %i[method controller action format status]),
Metric.new(:gauge, 'process_action.action_controller', :db_runtime, %i[method controller action format status]),
Metric.new(:gauge, 'process_action.action_controller', :view_runtime, %i[method controller action format status]),

Metric.new(:gauge, 'cache_read.active_support', :duration, %i[store key]),
Metric.new(:gauge, 'cache_fetch_hit.active_support', :duration, %i[store key]),
Metric.new(:gauge, 'cache_write.active_support', :duration, %i[store key]),
Metric.new(:gauge, 'cache_exist?.active_support', :duration, %i[store key]),

Metric.new(:gauge, 'render_partial.action_view', :duration, %i[view]),
Metric.new(:gauge, 'render_template.action_view', :duration, %i[view]),
Metric.new(:gauge, 'render_collection.action_view', :duration, %i[view]),

Metric.new(:gauge, 'perform.active_job', :duration, %i[job_class queue_name])
]

def start(name, id, payload)
@start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
end
Expand All @@ -21,7 +44,27 @@ def finish(name, id, payload)
end

def record(name, payload)
Honeybadger.event(name, payload)
if Honeybadger.config.load_plugin_insights_events?(:rails)
Honeybadger.event(name, payload)
end

if Honeybadger.config.load_plugin_insights_metrics?(:rails) && (metrics = find_metrics(name, payload))
metric_source 'rails'
metrics.each do |metric|
public_send(
metric.type,
[metric.value_key, metric.event].join('.'),
value: payload[metric.value_key],
**payload.slice(*metric.context)
)
end
end
end

def find_metrics(name, payload)
RAILS_METRICS.select do |metric|
metric.event.to_s == name.to_s && payload.keys.include?(metric.value_key) && (payload.keys & metric.context).any?
end
end

def process?(event, payload)
Expand Down Expand Up @@ -109,22 +152,6 @@ def format_payload(payload)
end
end

class ActiveJobMetricsSubscriber < NotificationSubscriber
include Honeybadger::InstrumentationHelper

def format_payload(payload)
{
job_class: payload[:job].class.to_s,
queue_name: payload[:job].queue_name
}
end

def record(name, payload)
metric_source 'active_job'
histogram name, { bins: [30, 60, 120, 300, 1800, 3600, 21_600] }.merge(payload)
end
end

class ActionMailerSubscriber < NotificationSubscriber
end

Expand Down
1 change: 0 additions & 1 deletion lib/honeybadger/plugins/active_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def context(job) # rubocop:disable Metrics/MethodLength

if config.load_plugin_insights?(:active_job)
::ActiveSupport::Notifications.subscribe(/(enqueue_at|enqueue|enqueue_retry|enqueue_all|perform|retry_stopped|discard)\.active_job/, Honeybadger::ActiveJobSubscriber.new)
::ActiveSupport::Notifications.subscribe('perform.active_job', Honeybadger::ActiveJobMetricsSubscriber.new)
end
end
end
Expand Down
20 changes: 17 additions & 3 deletions lib/honeybadger/plugins/net_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ module Honeybadger
module Plugins
module Net
module HTTP
@@hb_config = ::Honeybadger.config

def self.set_hb_config(config)
@@hb_config = config
end

def request(request_data, body = nil, &block)
return super unless started?
return super if hb?
Expand All @@ -18,19 +24,26 @@ def request(request_data, body = nil, &block)
status: response_data.code.to_i
}.merge(parsed_uri_data(request_data))

Honeybadger.event('request.net_http', context)
if @@hb_config.load_plugin_insights_events?(:net_http)
Honeybadger.event('request.net_http', context)
end

if @@hb_config.load_plugin_insights_metrics?(:net_http)
context.delete(:url)
Honeybadger.gauge('duration.request', context.merge(metric_source: 'net_http'))
end
end[1] # return the response data only
end

def hb?
address.to_s[/#{Honeybadger.config[:'connection.host'].to_s}/]
address.to_s[/#{@@hb_config[:'connection.host'].to_s}/]
end

def parsed_uri_data(request_data)
uri = request_data.uri || build_uri(request_data)
{}.tap do |uri_data|
uri_data[:host] = uri.host
uri_data[:url] = uri.to_s if Honeybadger.config[:'net_http.insights.full_url']
uri_data[:url] = uri.to_s if @@hb_config[:'net_http.insights.full_url']
end
end

Expand All @@ -43,6 +56,7 @@ def build_uri(request_data)
requirement { config.load_plugin_insights?(:net_http) }

execution do
Honeybadger::Plugins::Net::HTTP.set_hb_config(config)
::Net::HTTP.send(:prepend, Honeybadger::Plugins::Net::HTTP)
end
end
Expand Down
14 changes: 10 additions & 4 deletions lib/honeybadger/plugins/sidekiq.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,14 @@ def call(worker, msg, queue, &block)
raise
ensure
context.merge!(duration: duration, status: status)
Honeybadger.event('perform.sidekiq', context)
if Honeybadger.config.load_plugin_insights_events?(:sidekiq)
Honeybadger.event('perform.sidekiq', context)
end

metric_source 'sidekiq'
histogram 'perform', { bins: [30, 60, 120, 300, 1800, 3600, 21_600] }.merge(context.slice(:worker, :queue, :duration))
if Honeybadger.config.load_plugin_insights_metrics?(:sidekiq)
metric_source 'sidekiq'
histogram 'perform', { bins: [30, 60, 120, 300, 1800, 3600, 21_600] }.merge(context.slice(:worker, :queue, :duration))
end
end
end
end
Expand All @@ -55,7 +59,9 @@ def call(worker, msg, queue, _redis)
queue: queue
}

Honeybadger.event('enqueue.sidekiq', context)
if Honeybadger.config.load_plugin_insights_events?(:sidekiq)
Honeybadger.event('enqueue.sidekiq', context)
end

yield
end
Expand Down
6 changes: 4 additions & 2 deletions spec/unit/honeybadger/plugins/net_http_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

before do
Honeybadger::Plugin.instances[:net_http].reset!
Honeybadger::Plugin.instances[:net_http].load!(config)
end

it "includes integration module into Net::HTTP" do
Honeybadger::Plugin.instances[:net_http].load!(config)
expect(Net::HTTP.ancestors).to include(Honeybadger::Plugins::Net::HTTP)
end

Expand All @@ -19,15 +19,17 @@
context "report domain only" do
it "contains a domain" do
expect(Honeybadger).to receive(:event).with('request.net_http', hash_including({method: "GET", status: 200, host: "example.com"}))
expect(Honeybadger).to receive(:gauge).with('duration.request', hash_including({method: "GET", status: 200, host: "example.com"}))
Net::HTTP.get(URI.parse('http://example.com'))
end
end

context "report domain and full url" do
before { ::Honeybadger.config[:'net_http.insights.full_url'] = true }
before { config[:'net_http.insights.full_url'] = true }

it "contains a domain and url" do
expect(Honeybadger).to receive(:event).with('request.net_http', hash_including({method: "GET", status: 200, url: "http://example.com", host: "example.com"}))
expect(Honeybadger).to receive(:gauge).with('duration.request', hash_including({method: "GET", status: 200, host: "example.com"}))
Net::HTTP.get(URI.parse('http://example.com'))
end
end
Expand Down

0 comments on commit bc87b48

Please sign in to comment.