1 |
|
-module(safely). |
2 |
|
%% This module preserves the return types of a `catch' statement, |
3 |
|
%% but doesn't silently convert `throw's to normal values. |
4 |
|
%% The issue is that throws have different semantics than errors: |
5 |
|
%% https://erlang.org/doc/reference_manual/expressions.html#catch-and-throw, |
6 |
|
%% so a throw is actually a control flow escape, while an error is an actual crash. |
7 |
|
%% If it is an error, we want the stacktrace, if not, we don't. |
8 |
|
%% For more information, see usage in test/safely_SUITE.erl |
9 |
|
|
10 |
|
-include("safely.hrl"). |
11 |
|
-include_lib("kernel/include/logger.hrl"). |
12 |
|
|
13 |
|
-ifdef(TEST). |
14 |
|
-export([apply/2, apply/3]). |
15 |
|
-endif. |
16 |
|
-export([apply_and_log/3, apply_and_log/4]). |
17 |
|
-ignore_xref([apply_and_log/4]). |
18 |
|
|
19 |
|
-type error_class() :: error | exit | throw. |
20 |
|
-type error_info() :: #{class => error_class(), reason => term(), stacktrace => [term()]}. |
21 |
|
-type exception() :: {exception, error_info()}. |
22 |
|
-export_type([exception/0]). |
23 |
|
|
24 |
|
-define(MATCH_EXCEPTIONS_DO_LOG(F, Context), |
25 |
|
try F catch |
26 |
|
error:R:S -> |
27 |
|
Info = #{class => error, reason => R, stacktrace => S}, |
28 |
|
?LOG_ERROR(maps:merge(Context, Info)), |
29 |
|
{exception, Info}; |
30 |
|
throw:R -> |
31 |
|
Info = #{class => throw, reason => R}, |
32 |
|
?LOG_ERROR(maps:merge(Context, Info)), |
33 |
|
{exception, Info}; |
34 |
|
exit:R:S -> |
35 |
|
Info = #{class => exit, reason => R, stacktrace => S}, |
36 |
|
?LOG_ERROR(maps:merge(Context, Info)), |
37 |
|
{exception, Info} |
38 |
|
end). |
39 |
|
|
40 |
|
-ifdef(TEST). |
41 |
|
-spec apply(fun((...) -> A), [term()]) -> A | exception(). |
42 |
|
apply(Function, Args) when is_function(Function), is_list(Args) -> |
43 |
|
?SAFELY(erlang:apply(Function, Args)). |
44 |
|
|
45 |
|
-spec apply(atom(), atom(), [term()]) -> term() | exception(). |
46 |
|
apply(Module, Function, Args) when is_atom(Function), is_list(Args) -> |
47 |
|
?SAFELY(erlang:apply(Module, Function, Args)). |
48 |
|
-endif. |
49 |
|
|
50 |
|
-spec apply_and_log(fun((...) -> A), [term()], map()) -> A | exception(). |
51 |
|
apply_and_log(Function, Args, Context) |
52 |
|
when is_function(Function), is_list(Args), is_map(Context) -> |
53 |
44 |
?MATCH_EXCEPTIONS_DO_LOG(erlang:apply(Function, Args), Context). |
54 |
|
|
55 |
|
-spec apply_and_log(atom(), atom(), [term()], map()) -> term() | exception(). |
56 |
|
apply_and_log(Module, Function, Args, Context) |
57 |
|
when is_atom(Function), is_list(Args), is_map(Context) -> |
58 |
:-( |
?MATCH_EXCEPTIONS_DO_LOG(erlang:apply(Module, Function, Args), Context). |