We're preparing our upgrade to Sentry Elixir 6.0 at Timber, and we ran into an issue on the usage of :error_logger.add_report_handler/1 with umbrella applications.
Because :error_logger is implemented as a :gen_event manager and :error_logger.add_report_handler/1 is a lightweight wrapper around :gen_event.add_handler/3, report handlers are not unique by module. It is therefore valid to add a report handler multiple times.
This means that if an end-user of Sentry Elixir adds :error_logger.add_report_handler(Sentry.Logger) to the start/2 function in two applications in an umbrella application, there would be two Sentry.Logger instances. With Timber, we ended up calling it in 9 application, and the result was this:
iex(1)> :gen_event.which_handlers(:error_logger)
[Sentry.Logger, Sentry.Logger, Sentry.Logger, Sentry.Logger, Sentry.Logger,
Sentry.Logger, Sentry.Logger, Sentry.Logger, Sentry.Logger,
Logger.ErrorHandler, :error_logger]
Following that, a single error would be reported to Sentry 9 times, since the event would be sent to every instance of Sentry.Logger.
There are two solutions to fix this (we use the second):
-
The easy-but-brittle solution is to only add the report handler in the start/2 of the primary entrypoint application. This ensures that Sentry.Logger will only be added once. However, if at some point the entry-point changes or the end-user switches to a multiple release style, they will need to switch to option 2. (And hopefully have documented this fact in case anyone joins the team later).
-
The second solution is a bit more robust, but it makes implementation assumptions about :error_logger (notably that it will always be a :gen_event manager registered with the name :error_logger). It works by testing whether Sentry.Logger is already in the list of handlers. With this logic, it can be added to any and every application's start/2 function but only ever be added as a handler once:
if !(Sentry.Logger in :gen_event.which_handlers(:error_logger)) do
:ok = :error_logger.add_report_handler(Sentry.Logger)
end
We're preparing our upgrade to Sentry Elixir 6.0 at Timber, and we ran into an issue on the usage of
:error_logger.add_report_handler/1with umbrella applications.Because
:error_loggeris implemented as a:gen_eventmanager and:error_logger.add_report_handler/1is a lightweight wrapper around:gen_event.add_handler/3, report handlers are not unique by module. It is therefore valid to add a report handler multiple times.This means that if an end-user of Sentry Elixir adds
:error_logger.add_report_handler(Sentry.Logger)to thestart/2function in two applications in an umbrella application, there would be twoSentry.Loggerinstances. With Timber, we ended up calling it in 9 application, and the result was this:Following that, a single error would be reported to Sentry 9 times, since the event would be sent to every instance of
Sentry.Logger.There are two solutions to fix this (we use the second):
The easy-but-brittle solution is to only add the report handler in the
start/2of the primary entrypoint application. This ensures thatSentry.Loggerwill only be added once. However, if at some point the entry-point changes or the end-user switches to a multiple release style, they will need to switch to option 2. (And hopefully have documented this fact in case anyone joins the team later).The second solution is a bit more robust, but it makes implementation assumptions about
:error_logger(notably that it will always be a:gen_eventmanager registered with the name:error_logger). It works by testing whetherSentry.Loggeris already in the list of handlers. With this logic, it can be added to any and every application'sstart/2function but only ever be added as a handler once: