lib/exception_notifier/datadog_notifier.rb
# frozen_string_literal: true
require 'action_dispatch'
module ExceptionNotifier
class DatadogNotifier < BaseNotifier
attr_reader :client,
:default_options
def initialize(options)
super
@client = options.fetch(:client)
@default_options = options
end
def call(exception, options = {})
client.emit_event(
datadog_event(exception, options)
)
end
def datadog_event(exception, options = {})
DatadogExceptionEvent.new(
exception,
options.reverse_merge(default_options)
).event
end
class DatadogExceptionEvent
include ExceptionNotifier::BacktraceCleaner
MAX_TITLE_LENGTH = 120
MAX_VALUE_LENGTH = 300
MAX_BACKTRACE_SIZE = 3
ALERT_TYPE = 'error'
attr_reader :exception,
:options
def initialize(exception, options)
@exception = exception
@options = options
end
def request
@request ||= ActionDispatch::Request.new(options[:env]) if options[:env]
end
def controller
@controller ||= options[:env] && options[:env]['action_controller.instance']
end
def backtrace
@backtrace ||= exception.backtrace ? clean_backtrace(exception) : []
end
def tags
options[:tags] || []
end
def title_prefix
options[:title_prefix] || ''
end
def event
title = formatted_title
body = formatted_body
Dogapi::Event.new(
body,
msg_title: title,
alert_type: ALERT_TYPE,
tags: tags,
aggregation_key: [title]
)
end
def formatted_title
title =
"#{title_prefix}#{controller_subtitle} (#{exception.class}) #{exception.message.inspect}"
truncate(title, MAX_TITLE_LENGTH)
end
def formatted_body
text = []
text << '%%%'
text << formatted_request if request
text << formatted_session if request
text << formatted_backtrace
text << '%%%'
text.join("\n")
end
def formatted_key_value(key, value)
"**#{key}:** #{value}"
end
def formatted_request
text = []
text << '### **Request**'
text << formatted_key_value('URL', request.url)
text << formatted_key_value('HTTP Method', request.request_method)
text << formatted_key_value('IP Address', request.remote_ip)
text << formatted_key_value('Parameters', request.filtered_parameters.inspect)
text << formatted_key_value('Timestamp', Time.current)
text << formatted_key_value('Server', Socket.gethostname)
text << formatted_key_value('Rails root', Rails.root) if defined?(Rails) && Rails.respond_to?(:root)
text << formatted_key_value('Process', $PROCESS_ID)
text << '___'
text.join("\n")
end
def formatted_session
text = []
text << '### **Session**'
text << formatted_key_value('Data', request.session.to_hash)
text << '___'
text.join("\n")
end
def formatted_backtrace
size = [backtrace.size, MAX_BACKTRACE_SIZE].min
text = []
text << '### **Backtrace**'
text << '````'
size.times { |i| text << backtrace[i] }
text << '````'
text << '___'
text.join("\n")
end
def truncate(string, max)
string.length > max ? "#{string[0...max]}..." : string
end
def inspect_object(object)
case object
when Hash, Array
truncate(object.inspect, MAX_VALUE_LENGTH)
else
object.to_s
end
end
private
def controller_subtitle
"#{controller.controller_name} #{controller.action_name}" if controller
end
end
end
end