Current section
Files
Jump to
Current section
Files
src/lager_logtail_backend.erl
%% ----------------------------------------------------------------------------
%%
%% lager_logtail: Logtail backend for Lager
%%
%% Copyright (c) 2022 BocDev
%%
%% Permission is hereby granted, free of charge, to any person obtaining a
%% copy of this software and associated documentation files (the "Software"),
%% to deal in the Software without restriction, including without limitation
%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
%% and/or sell copies of the Software, and to permit persons to whom the
%% Software is furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
%% DEALINGS IN THE SOFTWARE.
%%
%% ----------------------------------------------------------------------------
-module(lager_logtail_backend).
-behaviour(gen_event).
-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()
}).
-include_lib("lager/include/lager.hrl").
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}.
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}.
%% @private
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}.
%%% Private
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).
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).