Current section
Files
Jump to
Current section
Files
src/lager_graylog_gelf_formatter.erl
-module(lager_graylog_gelf_formatter).
-export([format/2]).
-export_type([option/0]).
-type option() :: {metadata, all | [atom()], {module(), Function :: atom()}}
| {include_timestamp, boolean()}
| {override_host, string() | binary()}
| {on_encode_failure, crash | string() | binary()}.
-type severity_int() :: 0..7.
-type metadata_key() :: atom().
-type metadata_val() :: binary()
| atom()
| integer()
| float()
| reference()
| port()
| pid()
| list()
| tuple()
| map()
| bitstring().
-type metadata_kv() :: {metadata_key(), metadata_val()}.
-define(GELF_VERSION, <<"1.1">>).
%% API
-spec format(lager_msg:lager_msg(), [option()]) -> iodata().
format(Message, Opts) ->
Host = get_host(Opts),
ShortMessage = lager_msg:message(Message),
Level = severity_to_int(lager_msg:severity(Message)),
Metadata = extract_metadata(Message, Opts),
Props0 = [{<<"version">>, ?GELF_VERSION},
{<<"host">>, Host},
{<<"short_message">>, iolist_to_binary(ShortMessage)},
{<<"level">>, Level} | prepare_metadata(Metadata)],
TsProps = timestamp_prop(lager_msg:timestamp(Message), Opts),
Props1 = lists:flatten([TsProps | Props0]),
case on_encode_failure(Opts) of
crash ->
encode(Props1);
OnFailMessage ->
safely_encode(Props1, OnFailMessage)
end.
%% Helpers
-spec get_host([option()]) -> binary().
get_host(Opts) ->
Host =
case proplists:lookup(override_host, Opts) of
{override_host, OverridenHost} ->
OverridenHost;
_ ->
{ok, InetHost} = inet:gethostname(),
InetHost
end,
iolist_to_binary(io_lib:format("~s", [Host])).
-spec timestamp_prop(erlang:timestamp(), [option()]) -> [{binary(), float()}].
timestamp_prop(Timestamp, Opts) ->
case proplists:get_value(include_timestamp, Opts, true) of
true -> [{<<"timestamp">>, erlang_ts_to_gelf_ts(Timestamp)}];
false -> []
end.
-spec extract_metadata(lager_msg:lager_msg(), [option()]) -> [metadata_kv()].
extract_metadata(Message, Opts) ->
AllMetadata = lager_msg:metadata(Message),
case proplists:get_value(metadata, Opts, all) of
all ->
AllMetadata;
{Mod, Fun} when is_atom(Mod) andalso is_atom(Fun) ->
Mod:Fun(Message);
Keys when is_list(Keys) ->
lists:foldl(fun(K, Acc) ->
case proplists:lookup(K, AllMetadata) of
{K, _} = Tuple -> [Tuple | Acc];
_ -> Acc
end
end, [], Keys)
end.
-spec prepare_metadata([metadata_kv()]) -> [{binary(), binary() | atom() | number()}].
prepare_metadata(Metadata) ->
[{prepare_metadata_key(K), prepare_metadata_val(V)} || {K, V} <- Metadata].
-spec prepare_metadata_key(metadata_key()) -> binary().
prepare_metadata_key(Key) ->
iolist_to_binary(io_lib:format("_~s", [Key])).
-spec prepare_metadata_val(metadata_val()) -> binary() | atom() | number().
prepare_metadata_val(Term) when is_atom(Term) -> Term;
prepare_metadata_val(Term) when is_number(Term) -> Term;
prepare_metadata_val(Term) ->
iolist_to_binary(io_lib:format("~p", [Term])).
-spec severity_to_int(lager:log_level()) -> severity_int().
severity_to_int(none) -> 0;
severity_to_int(emergency) -> 0;
severity_to_int(alert) -> 1;
severity_to_int(critical) -> 2;
severity_to_int(error) -> 3;
severity_to_int(warning) -> 4;
severity_to_int(notice) -> 5;
severity_to_int(info) -> 6;
severity_to_int(debug) -> 7.
-spec erlang_ts_to_gelf_ts(erlang:timestamp()) -> float().
erlang_ts_to_gelf_ts({MegaSecs, Secs, MicroSecs}) ->
(MegaSecs * 1000000) + Secs + (MicroSecs / 1000000).
-spec on_encode_failure([option()]) -> crash | binary().
on_encode_failure(Opts) ->
case proplists:get_value(on_encode_failure, Opts, crash) of
crash ->
crash;
OnFailMessage when is_binary(OnFailMessage) ->
OnFailMessage;
OnFailMessage when is_list(OnFailMessage) ->
list_to_binary(OnFailMessage)
end.
-spec encode([tuple()]) -> binary() | no_return().
encode(Props) ->
jiffy:encode({Props}).
-spec safely_encode([tuple()], OnFailMessage :: binary()) -> binary().
safely_encode(Props, OnFailMessage) ->
case catch encode(Props) of
{error, _} ->
Props1 = lists:keyreplace(<<"short_message">>, 1, Props, {<<"short_message">>,
OnFailMessage}),
encode(Props1);
Encoded ->
Encoded
end.