From 9ebb99bfb652352cda4abaa61eb496d397254db1 Mon Sep 17 00:00:00 2001 From: Mauricio Cassola Date: Thu, 6 Jan 2022 10:32:42 -0300 Subject: [PATCH 1/2] Read configuration from application environment --- lib/boom_notifier.ex | 35 +++++++++++++------------------ lib/boom_notifier/config.ex | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 lib/boom_notifier/config.ex diff --git a/lib/boom_notifier.ex b/lib/boom_notifier.ex index 68794e4..88fcb26 100644 --- a/lib/boom_notifier.ex +++ b/lib/boom_notifier.ex @@ -4,6 +4,7 @@ defmodule BoomNotifier do # Responsible for sending a notification to each notifier every time an # exception is raised. + alias BoomNotifier.Config alias BoomNotifier.ErrorStorage alias BoomNotifier.NotifierSenderServer require Logger @@ -25,10 +26,10 @@ defmodule BoomNotifier do end end - def walkthrough_notifiers(settings, callback) do - case Keyword.get(settings, :notifiers) do - nil -> - run_callback(settings, callback) + def walkthrough_notifiers(callback) do + case Config.notifiers() do + [] -> + run_callback(Config.single_notifier_config(), callback) notifiers_settings when is_list(notifiers_settings) -> Enum.each(notifiers_settings, &run_callback(&1, callback)) @@ -49,7 +50,7 @@ defmodule BoomNotifier do end end - defmacro __using__(config) do + defmacro __using__(_config) do quote location: :keep do import BoomNotifier @@ -65,27 +66,20 @@ defmodule BoomNotifier do end # Notifiers validation - walkthrough_notifiers( - unquote(config), - fn notifier, options -> validate_notifiers(notifier, options) end - ) + walkthrough_notifiers(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) + unless Enum.member?(Config.ignore_exceptions(), mod) do + do_notify_error(conn, error) end end def notify_error(conn, error) do - do_notify_error(conn, unquote(config), error) + do_notify_error(conn, error) end - defp do_notify_error(conn, settings, error) do - {custom_data, _settings} = Keyword.pop(settings, :custom_data, :nothing) - {error_kind, error_info} = ErrorInfo.build(error, conn, custom_data) + defp do_notify_error(conn, error) do + {error_kind, error_info} = ErrorInfo.build(error, conn, Config.custom_data()) ErrorStorage.add_errors(error_kind, error_info) @@ -93,12 +87,11 @@ defmodule BoomNotifier do occurrences = ErrorStorage.get_errors(error_kind) # Triggers the notification in each notifier - walkthrough_notifiers(settings, fn notifier, options -> + walkthrough_notifiers(fn notifier, options -> NotifierSenderServer.send(notifier, occurrences, options) end) - {notification_trigger, _settings} = - Keyword.pop(settings, :notification_trigger, :always) + notification_trigger = Config.notification_trigger() ErrorStorage.clear_errors(notification_trigger, error_kind) end diff --git a/lib/boom_notifier/config.ex b/lib/boom_notifier/config.ex new file mode 100644 index 0000000..5fa6d3e --- /dev/null +++ b/lib/boom_notifier/config.ex @@ -0,0 +1,42 @@ +defmodule BoomNotifier.Config do + @moduledoc """ + This module provides the functionality for fetching configuration settings and their defaults. + """ + + @custom_data_default :nothing + @ignore_exceptions_default [] + @notifiers_default [] + @notification_trigger_default :always + @notifier_default nil + @options_default [] + + def custom_data do + get_config(:custom_data, @custom_data_default) + end + + def ignore_exceptions do + get_config(:ignore_exceptions, @ignore_exceptions_default) + end + + def notifiers do + get_config(:notifiers, @notifiers_default) + end + + def notification_trigger do + get_config(:notification_trigger, @notification_trigger_default) + end + + def single_notifier_config do + [ + notifier: get_config(:notifier, @notifier_default), + options: get_config(:options, @options_default) + ] + end + + defp get_config(key, default) do + case Application.fetch_env(:boom_notifier, key) do + {:ok, value} -> value + _ -> default + end + end +end From 5f30cf8f7ac84d5c7b6fc24324d5b34007965577 Mon Sep 17 00:00:00 2001 From: Mauricio Cassola Date: Thu, 6 Jan 2022 11:05:06 -0300 Subject: [PATCH 2/2] Allow manual notify of exception --- README.md | 25 +++++++++++++++++++++++++ lib/boom_notifier.ex | 17 ++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a65fb2..af0f3ca 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,31 @@ defmodule YourApp.Router do ... ``` +## Manually notify of exception + +If your controller action manually handles an error, the notifier will never be run. + +To manually notify of an error you can use `BoomNotifier` in your controller and call the `manual_notify_error/2` callback: + +```elixir +defmodule MyController do + use BoomNotifier + + def some_action(conn, _params) do + try do + # ... + rescue + error -> + # ... + manual_notify_error(conn, error) + # ... + end + end + + , + ... +``` + ## Custom notifiers To create a custom notifier, you need to implement the `BoomNotifier.Notifier` behaviour: diff --git a/lib/boom_notifier.ex b/lib/boom_notifier.ex index 88fcb26..1857c98 100644 --- a/lib/boom_notifier.ex +++ b/lib/boom_notifier.ex @@ -55,8 +55,9 @@ defmodule BoomNotifier do import BoomNotifier error_handler_in_use = {:handle_errors, 2} in Module.definitions_in(__MODULE__) + is_router = __MODULE__ |> to_string() |> String.split(".") |> List.last() == "Router" - unless error_handler_in_use do + if is_router and not error_handler_in_use do use Plug.ErrorHandler @impl Plug.ErrorHandler @@ -78,6 +79,20 @@ defmodule BoomNotifier do do_notify_error(conn, error) end + def manual_notify_error(conn, error) do + {_error_kind, error_info} = + ErrorInfo.build(%{kind: :error, reason: error, stack: []}, conn, Config.custom_data()) + + # Triggers the notification in each notifier + walkthrough_notifiers(fn notifier, options -> + NotifierSenderServer.send( + notifier, + [error_info], + options + ) + end) + end + defp do_notify_error(conn, error) do {error_kind, error_info} = ErrorInfo.build(error, conn, Config.custom_data())