%%%----------------------------------------------------------------------------- %%% @doc %%% %%% @author boc_dev %%% @copyright MIT %%% @version 0.0.1 %%% @end %%%----------------------------------------------------------------------------- -module(lager_logtail_backend). -author(boc_dev). -behaviour(gen_event). %%%============================================================================= %%% Exports and Definitions %%%============================================================================= -export([ init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3 ]). -type lager_msg_metadata() :: [tuple()]. -type binary_proplist() :: [{binary(), binary()}]. %%% this is only exported for the spawn call -export([deferred_log/3]). -record(state, { level :: integer(), retry_interval :: integer(), retry_times :: integer(), token :: string(), url :: string() }). -type state() :: state. -include_lib("lager/include/lager.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%============================================================================= %%% Behaviour Impl %%%============================================================================= -spec init(list()) -> {ok, state()}. init([Level, RetryTimes, RetryInterval, Token]) -> State = #state{ level = lager_util:level_to_num(Level), retry_interval = RetryInterval, retry_times = RetryTimes, token = Token, url = "https://in.logtail.com" }, {ok, State}. -spec handle_call(get_loglevel | set_loglevel, state()) -> {ok, state()}. handle_call(get_loglevel, #state{ level = Level } = State) -> {ok, Level, State}; handle_call({set_loglevel, Level}, State) -> {ok, ok, State#state{ level = lager_util:level_to_num(Level) }}; handle_call(_Request, State) -> {ok, ok, State}. -spec handle_event({log, any()}, state()) -> {ok, state()}. handle_event({log, Message}, #state{level=Level} = State) -> case lager_util:is_loggable(Message, Level, ?MODULE) of true -> Payload = jsx:encode(cons_metadata_to_binary_proplist(lager_msg:metadata(Message), [ {<<"level">>, any_to_binary(lager_msg:severity(Message))}, {<<"message">>, any_to_binary(lager_msg:message(Message))} ])), Request = {State#state.url, [{"Authorization", "Bearer " ++ State#state.token}], "application/json", Payload}, RetryTimes = State#state.retry_times, RetryInterval = State#state.retry_interval, %% Spawn a background process to handle sending the payload. %% It will recurse until the payload has ben successfully sent. spawn(?MODULE, deferred_log, [Request, RetryTimes, RetryInterval]), {ok, State}; false -> {ok, State} end; handle_event(_Event, State) -> {ok, State}. handle_info(_Info, State) -> {ok, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== -spec deferred_log(any(), integer(), integer()) -> ok. deferred_log(_Request, 0, _) -> io:format("LogTail Request Failed and can't try again"), ok; deferred_log(Request, Retries, Interval) -> case httpc:request(post, Request, [], [{body_format, binary}]) of {ok, {{_, 202, _}, _Header, _Body}} -> ok; Failure -> io:format("LogTail Request Failed with ~p retries left: ~p", [Retries, Failure]), timer:sleep(Interval * 1000), deferred_log(Request, Retries - 1, Interval) end. -spec cons_metadata_to_binary_proplist(Metadata::lager_msg_metadata(), Proplist::binary_proplist()) -> Proplist::binary_proplist(). cons_metadata_to_binary_proplist(Metadata, Proplist) -> lists:foldl(fun({Key, Value}, Acc) -> [{any_to_binary(Key), any_to_binary(Value)} | Acc] end, Proplist, Metadata). -spec any_to_binary(any()) -> binary(). any_to_binary(V) when is_atom(V) -> any_to_binary(atom_to_list(V)); any_to_binary(V) when is_pid(V) -> any_to_binary(pid_to_list(V)); any_to_binary(V) when is_list(V) -> list_to_binary(V); any_to_binary(V) when is_integer(V) -> integer_to_binary(V); any_to_binary(V) when is_binary(V) -> V; any_to_binary(V) -> term_to_binary(V). %%%=================================================================== %%% Tests %%%=================================================================== -ifdef(TEST). any_to_binary_test() -> ?assertEqual(<<"hello">>, any_to_binary(hello)), ?assertEqual(<<1,2,3>>, any_to_binary([1,2,3])), ?assertEqual(<<"1">>, any_to_binary(1)), ?assertEqual(<<"1">>, any_to_binary(<<"1">>)), ?assertEqual(<<131,104,2,100,0,5,104,101,108,108,111,97,1>>, any_to_binary({hello, 1})). -endif.