Current section

Files

Jump to
bugsnag_erlang src bugsnag.erl
Raw

src/bugsnag.erl

-module(bugsnag).
-behavior(gen_server).
-export([start/0, start_link/2, notify/5, notify/7, test_error/0]).
% Gen server hooks
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(state, {api_key, release_stage}).
-define(NOTIFY_ENDPOINT, "https://notify.bugsnag.com").
-define(NOTIFIER_NAME, <<"Bugsnag Erlang">>).
-define(NOTIFIER_VERSION, <<"2.0.1">>).
-define(NOTIFIER_URL, <<"https://github.com/dnsimple/bugsnag-erlang">>).
% Public API
start() ->
application:ensure_all_started(bugsnag).
start_link(ApiKey, ReleaseStage) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [ApiKey, ReleaseStage], []).
notify(Type, Reason, Message, Module, Line) ->
notify(Type, Reason, Message, Module, Line, generate_trace(), undefined).
notify(Type, Reason, Message, Module, Line, Trace, Request) ->
gen_server:cast(?MODULE, {exception, Type, Reason, Message, Module, Line, Trace, Request}).
test_error() ->
gen_server:cast(?MODULE, {test_error}).
% Gen server hooks
init([ApiKey, ReleaseStage]) ->
{ok, #state{api_key = ApiKey, release_stage = ReleaseStage}}.
handle_call(_, _, State) ->
{reply, ok, State}.
handle_cast({exception, Type, Reason, Message, Module, Line, Trace, Request}, State) ->
send_exception(Type, Reason, Message, Module, Line, Trace, Request, State),
{noreply, State};
handle_cast({test_error}, State) ->
erlang:error(test_error),
{noreply, State}.
handle_info(_Message, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
% Internal API
encoder([{_, _} | _] = Value, Encode) -> json:encode_key_value_list(Value, Encode);
encoder(Other, Encode) -> json:encode_value(Other, Encode).
custom_encode(Value) -> json:encode(Value, fun(Value, Encode) -> encoder(Value, Encode) end).
% See https://docs.bugsnag.com/api/error-reporting/#api-reference
send_exception(_Type, Reason, Message, _Module, _Line, Trace, _Request, State) ->
Payload = [
{apiKey, to_bin(State#state.api_key)},
{payloadVersion, <<"5">>},
{notifier, [
{name, ?NOTIFIER_NAME},
{version, ?NOTIFIER_VERSION},
{url, ?NOTIFIER_URL}
]},
{events, [
[
{device, [
{hostname, to_bin(net_adm:localhost())}
]},
{app, [
{releaseStage, to_bin(State#state.release_stage)}
]},
{exceptions, [
[
{errorClass, to_bin(Reason)},
{message, to_bin(Message)},
{stacktrace, process_trace(Trace)}
]
]}
]
]}
],
deliver_payload(iolist_to_binary(custom_encode(Payload))).
process_trace(Trace) ->
lager:info("Processing trace ~p", [Trace]),
process_trace(Trace, []).
process_trace([], ProcessedTrace) ->
ProcessedTrace;
process_trace([Current | Rest], ProcessedTrace) ->
StackTraceLine =
case Current of
{_, F, _, [{file, File}, {line, Line}]} ->
[
{file, to_bin(File)},
{lineNumber, Line},
{method, to_bin(F)}
];
{_, F, _} ->
[
{method, to_bin(F)}
];
_ ->
lager:warning("Discarding stack trace line: ~p", [Current]),
[]
end,
process_trace(Rest, ProcessedTrace ++ [StackTraceLine]).
deliver_payload(Payload) ->
lager:info("Sending exception: ~p", [Payload]),
case
httpc:request(
post, {?NOTIFY_ENDPOINT, [], "application/json", Payload}, [{timeout, 5000}], []
)
of
{ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} ->
lager:info("Error sent. Response: ~p", [Body]);
{_, {{_Version, Status, ReasonPhrase}, _Headers, _Body}} ->
lager:warning("Failed to send error to bugsnag (~p : ~p)", [Status, ReasonPhrase])
end,
ok.
to_bin(Atom) when erlang:is_atom(Atom) ->
erlang:atom_to_binary(Atom, utf8);
to_bin(Bin) when erlang:is_binary(Bin) ->
Bin;
to_bin(Int) when erlang:is_integer(Int) ->
erlang:integer_to_binary(Int);
to_bin(List) when erlang:is_list(List) ->
erlang:iolist_to_binary(List).
generate_trace() ->
lager:info("Generating trace"),
try
throw(bugsnag_gen_trace)
catch
_:_:StackTrace ->
StackTrace
end.