Universal error handler for elixir applications

Hi not sure what the best practice for handling errors are in elixir. Is there an example of how I can make a universal error handler to take care of any type of error and return an error message for my absinthe graphql elixir application?

Absinthe is normal Plug, so check out Plug.Error and error handling in Phoenix.Endpoint.

The community standard goes along the lines of having your functions returns {:error, _reason}. Then depending on that you can pass this around and decide what to do. There is also Plug ofc as mentioned before.

There is no such thing as a “Universal error handler”. An Elixir application is a collection of processes and those processes don’t share memory or processing threads except through explicit message passing. That includes error information. The best you could do is put a top level exception handler on a process but then the question becomes what do you do with that error?

The correct answer is that you let the process crash and allow a supervisor to decide how to deal with the failure. In a Goto conference presentation, Joe Armstrong (creator of Erlang) described it as (paraphrasing) “What do I do if I have a heart attack? Do I run off to find a defribulator? No, I ask someone else to deal with the error”

In short, the idea of a “universal error handler” runs pretty much counter to the design philosophy of Erlang (and by extension Elixir) so you are probably barking up the wrong tree. If you are looking for a good catch-phrase to learn more about handling errors in Elixir an Erlang, search for “Let it Crash” along with Erlang and Elixir.

2 Likes

I believe the OP is asking about errors and not exceptions.

  • Errors are something we can predict in our apps, like a user entering a wrong password.
  • Exceptions are the heart attacks you mentioned. We didn’t consider for a meteor to fall, so when it does we allow our processes to crash and have a supervisor take care of them.
1 Like

If we accept that definition - things that we can predict and account for, then the concept of a “universal error handler” makes even less sense. Errors of that kind, by their very nature, are specific to their cause.

It depends on which programming paradigm you use. Scott Wlaschin has a series of Railway Oriented Programming conferences (and books) that have the program follow a given flow where all errors end up at the end.

He then has an universal error interpreter (or handler, depending on what you feel like calling it) that decides what messages to show based on each error. I personally find that a really nice way to program.

So to me, this post means something different than what it means for you. I do think however your criticisms are very welcome, but I apply them in the realm of exceptions and not errors.

But you would like to know if your application had “a heart attack” as the analogy you used previously, right?

Say a user tries out your app; the app has some logic error; user leaves without telling devs about the error (he has other things to do).

What is your strategy to be alerted of such an exception? You’d like to receive an email for example whenever that happens, right?

Crashes are usually logged.

Logs are usually monitored.

Monitors can send you messages or aggregate on a dashboard.

@NobbZ would a Logger with custom backend cover my needs maybe?
https://hexdocs.pm/logger/Logger.html#module-custom-backends
Or is there a reason you didn’t mention it maybe?

Yes, it would. However if you want to handle only errors (for example like Sentry or BugSnag handler) then it will be better to attach yourself to logger instead as it will give you structured data which will be easier to transform to whatever you need. However beware that this is not overload protected, so you should handle that on your own.

1 Like

For the moment I try to use Elixir Backend because I don’t understand about attaching to erlang logger and overload protection.

So I see I can catch some warnings and errors, but there is for example one warning from Phoenix that is not caught:

warning: Rendering the child template from layouts is deprecated. Instead of:

<%= render(@view_module, @view_template, assigns) %>

You should do:

<%= @inner_content %>

Would you know why my Backend can’t catch this one?

Because it is compile time warning which is not handled by the logger at all. If you want to prevent something like that, then you should use --warnings-as-errors during compilation (or set that flag as a default in your mix.exs).

2 Likes

If it’s a compile warning, then that’s all fine, I won’t miss it, thank you:)

But now I stumbled across another error that my custom Logger backend didn’t catch (it filters warning and errors):

  • level is :debug

  • message is

** (Ecto.InvalidChangesetError) could not perform update because changeset is invalid.\n\nErrors\n\n %{\n […]

So somewhere in the code, indeed I called Ecto.Changeset.apply_action!/2 on an invalid changeset.

Why was this error message marked as :debug?


UPDATE. Posted an issue here and it is fixed already: