venkatd
Best practices on exception handling and tracking (let it crash vs. try catch tradeoffs)
I have some code that allows me to run a series of functions. I currently surround the execution itself by a try-catch because I don’t want one execution crashing to cause all future executions to crash.
defmodule ES.Queue do
use GenServer
def start_link(base_opts, opts \\ []) do
defaults = [log_success: &noop/1, log_failure: &noop/1, timeout: 10_000]
{queue_opts, genserver_opts} =
defaults
|> Keyword.merge(base_opts)
|> Keyword.merge(opts)
|> Keyword.split([:queue_name, :timeout, :log_success, :log_failure])
GenServer.start_link(__MODULE__, Map.new(queue_opts), genserver_opts)
end
def run(pid, func), do: GenServer.call(pid, {:run, func}, 60_000)
def init(%{timeout: timeout}=state) do
{:ok, state, timeout}
end
def handle_call({:run, func}, _from, %{queue_name: queue_name, timeout: timeout, log_success: log_success, log_failure: log_failure}=state) do
resp =
try do
t1 = :erlang.monotonic_time()
val = execute(func)
t2 = :erlang.monotonic_time()
ms_taken = :erlang.convert_time_unit(t2 - t1, :native, :millisecond)
log_success.({queue_name, func, ms_taken})
val
catch
type, error ->
log_failure.({type, error, System.stacktrace()})
end
{:reply, resp, state, timeout}
end
def handle_call(:inspect, _from, %{timeout: timeout}=state) do
{:reply, Kernel.inspect(state), state, timeout}
end
def execute(func) when is_function(func), do: func.()
def execute({mod, func}), do: apply(mod, func, [])
def execute({mod, func, args}), do: apply(mod, func, args)
def noop(_), do: nil
def handle_info(:timeout, state) do
{:stop, :normal, state}
end
end
However there are a few problems with this approach:
- When an exception occurs during a
mix test, the errors get swallowed. It becomes much harder to track down the error vs. when I remove the try catch. - If this code is in production, exceptions won’t get logged out or sent to an error reporting service like Rollbar
Basically, I still want exceptions to be loud. I still want the red error with the stacktrace to be printed out to the logs when I am running tests. I still want errors to get reported to an error reporting service while the app is running in production. What are some good options for this?
Thanks!
Most Liked
cmkarlsson
How about doing the execution in another process? It is not uncommon to do this for things that can fail. Your GenServer spawns and waits for the result from the execution. It also monitors the newly spawned process for failures. The execute are still synchronized, the monitoring GenServer will not fail and continues with the other cases even if one fails.
I’m not sure what sort of error logging you are looking for when something crashes but perhaps if the execution is done according to OTP standards (either a GenServer or a process started with proc_lib) I think you may get SASL logging which Logger may pick up.
sasajuric
You can log the error directly like this:
try do
# ...
catch
type, error ->
Logger.error(Exception.format(type, error, __STACKTRACE__))
end
That said, I agree with @cmkarlsson’s proposal. If you want to ensure that “execution” doesn’t take the GenServer down, doing it in a separate process would give you the strongest guarantees of that.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









