blob: 2e354e001b24ba6e209f3e5b56bac4766a805a30 [file] [log] [blame]
%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
-module(lager_default_formatter).
%%
%% Include files
%%
-include("lager.hrl").
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
%%
%% Exported Functions
%%
-export([format/2]).
%%
%% API Functions
%%
%% @doc Provides a generic, default formatting for log messages using a semi-iolist as configuration. Any iolist allowed
%% elements in the configuration are printed verbatim. Atoms in the configuration are treated as metadata properties
%% and extracted from the log message. Optionally, a tuple of {atom(),semi-iolist()} can be used. The atom will look
%% up the property, but if not found it will use the semi-iolist() instead. These fallbacks can be similarly nested
%% or refer to other properties, if desired. You can also use a {atom, semi-iolist(), semi-iolist()} formatter, which
%% acts like a ternary operator's true/false branches.
%%
%% The metadata properties date,time, message, and severity will always exist.
%% The properties pid, file, line, module, and function will always exist if the parser transform is used.
%%
%% Example:
%%
%% `["Foo"]' -> "Foo", regardless of message content.
%%
%% `[message]' -> The content of the logged message, alone.
%%
%% `[{pid,"Unknown Pid"}]' -> "?.?.?" if pid is in the metadata, "Unknown Pid" if not.
%%
%% `[{pid, ["My pid is ", pid], "Unknown Pid"}]' -> if pid is in the metada print "My pid is ?.?.?", otherwise print "Unknown Pid"
%% @end
-spec format(lager_msg:lager_msg(),list()) -> any().
format(Msg,[]) ->
format(Msg, [{eol, "\n"}]);
format(Msg,[{eol, EOL}]) ->
format(Msg,
[date, " ", time, " [", severity, "] ",
{pid, ""},
{module, [
{pid, ["@"], ""},
module,
{function, [":", function], ""},
{line, [":",line], ""}], ""},
" ", message, EOL]);
format(Message,Config) ->
[ output(V,Message) || V <- Config ].
-spec output(term(),lager_msg:lager_msg()) -> iolist().
output(message,Msg) -> lager_msg:message(Msg);
output(date,Msg) ->
{D, _T} = lager_msg:datetime(Msg),
D;
output(time,Msg) ->
{_D, T} = lager_msg:datetime(Msg),
T;
output(severity,Msg) ->
atom_to_list(lager_msg:severity(Msg));
output(Prop,Msg) when is_atom(Prop) ->
Metadata = lager_msg:metadata(Msg),
make_printable(get_metadata(Prop,Metadata,<<"Undefined">>));
output({Prop,Default},Msg) when is_atom(Prop) ->
Metadata = lager_msg:metadata(Msg),
make_printable(get_metadata(Prop,Metadata,output(Default,Msg)));
output({Prop, Present, Absent}, Msg) when is_atom(Prop) ->
%% sort of like a poor man's ternary operator
Metadata = lager_msg:metadata(Msg),
case get_metadata(Prop, Metadata) of
undefined ->
[ output(V, Msg) || V <- Absent];
_ ->
[ output(V, Msg) || V <- Present]
end;
output(Other,_) -> make_printable(Other).
-spec make_printable(any()) -> iolist().
make_printable(A) when is_atom(A) -> atom_to_list(A);
make_printable(P) when is_pid(P) -> pid_to_list(P);
make_printable(L) when is_list(L) orelse is_binary(L) -> L;
make_printable(Other) -> io_lib:format("~p",[Other]).
get_metadata(Key, Metadata) ->
get_metadata(Key, Metadata, undefined).
get_metadata(Key, Metadata, Default) ->
case lists:keyfind(Key, 1, Metadata) of
false ->
Default;
{Key, Value} ->
Value
end.
-ifdef(TEST).
date_time_now() ->
Now = os:timestamp(),
{Date, Time} = lager_util:format_time(lager_util:maybe_utc(lager_util:localtime_ms(Now))),
{Date, Time, Now}.
basic_test_() ->
{Date, Time, Now} = date_time_now(),
[{"Default formatting test",
?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]),
iolist_to_binary(format(lager_msg:new("Message",
Now,
error,
[{pid, self()}],
[]),
[])))
},
{"Basic Formatting",
?_assertEqual(<<"Simplist Format">>,
iolist_to_binary(format(lager_msg:new("Message",
Now,
error,
[{pid, self()}],
[]),
["Simplist Format"])))
},
{"Default equivalent formatting test",
?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]),
iolist_to_binary(format(lager_msg:new("Message",
Now,
error,
[{pid, self()}],
[]),
[date, " ", time," [",severity,"] ",pid, " ", message, "\n"]
)))
},
{"Non existant metadata can default to string",
?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]),
iolist_to_binary(format(lager_msg:new("Message",
Now,
error,
[{pid, self()}],
[]),
[date, " ", time," [",severity,"] ",{does_not_exist,"Fallback"}, " ", message, "\n"]
)))
},
{"Non existant metadata can default to other metadata",
?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]),
iolist_to_binary(format(lager_msg:new("Message",
Now,
error,
[{pid, "Fallback"}],
[]),
[date, " ", time," [",severity,"] ",{does_not_exist,pid}, " ", message, "\n"]
)))
}
].
-endif.