Packages

This package allows your Phoenix application to send notifications whenever an exceptions is raised. By default it includes an email and a webhook notifier, but you can implement your custom ones.

Current section

Files

Jump to
boom_notifier lib boom_notifier.ex
Raw

lib/boom_notifier.ex

defmodule BoomNotifier do
@moduledoc false
# Responsible for sending a notification to each notifier every time an
# exception is raised.
alias BoomNotifier.ErrorInfo
alias BoomNotifier.ErrorStorage
alias BoomNotifier.NotifierSenderServer
require Logger
def run_callback(settings, callback) do
missing_keys = Enum.reject([:notifier, :options], &Keyword.has_key?(settings, &1))
case missing_keys do
[] ->
callback.(settings[:notifier], settings[:options])
[missing_key] ->
Logger.error("(BoomNotifier) #{inspect(missing_key)} parameter is missing")
_ ->
Logger.error(
"(BoomNotifier) The following parameters are missing: #{inspect(missing_keys)}"
)
end
end
def walkthrough_notifiers(settings, callback) do
case Keyword.get(settings, :notifiers) do
nil ->
run_callback(settings, callback)
notifiers_settings when is_list(notifiers_settings) ->
Enum.each(notifiers_settings, &run_callback(&1, callback))
end
end
def validate_notifiers(notifier, options) do
if Code.ensure_loaded?(notifier) && function_exported?(notifier, :validate_config, 1) do
case notifier.validate_config(options) do
{:error, message} ->
Logger.error(
"Notifier validation: #{message} in #{notifier |> to_string() |> String.split(".") |> List.last()}"
)
_ ->
nil
end
end
end
defmacro __using__(config) do
quote location: :keep do
import BoomNotifier
error_handler_in_use = {:handle_errors, 2} in Module.definitions_in(__MODULE__)
unless error_handler_in_use do
use Plug.ErrorHandler
@impl Plug.ErrorHandler
def handle_errors(conn, error) do
notify_error(conn, error)
end
end
# Notifiers validation
walkthrough_notifiers(
unquote(config),
fn notifier, options -> validate_notifiers(notifier, options) end
)
def notify_error(conn, %{kind: :error, reason: %mod{}} = error) do
settings = unquote(config)
{ignored_exceptions, _settings} = Keyword.pop(settings, :ignore_exceptions, [])
unless Enum.member?(ignored_exceptions, mod) do
do_notify_error(conn, settings, error)
end
end
def notify_error(conn, error) do
do_notify_error(conn, unquote(config), error)
end
defp do_notify_error(conn, settings, error) do
{custom_data, _settings} = Keyword.pop(settings, :custom_data, :nothing)
error_info = ErrorInfo.build(error, conn, custom_data)
ErrorStorage.store_error(error_info)
if ErrorStorage.send_notification?(error_info) do
notification_data =
Map.put(error_info, :occurrences, ErrorStorage.get_error_stats(error_info))
# Triggers the notification in each notifier
walkthrough_notifiers(settings, fn notifier, options ->
NotifierSenderServer.send(notifier, notification_data, options)
end)
{notification_trigger, _settings} =
Keyword.pop(settings, :notification_trigger, :always)
ErrorStorage.reset_accumulated_errors(notification_trigger, error_info)
end
end
end
end
end