%% @copyright 2014-2016 Takeru Ohta %% %% @doc A default layout for built-in sinks %% %% This module layouts a log message by the following format: %% ``` %% {yyyy}-{MM}-{dd} {HH}:{mm}:{ss}.{SSS} [{SEVERITY}] {NODE} {PID} {MODULE}:{FUNCTION}:{LINE} [{HEADER(KEY=VALUE)}*] {MESSAGE}\n %% ''' %% %% == NOTE == %% This module is provided for debuging/testing purposes only. %% The message format is not customizable. %% And no overload protection exists (e.g. if log message is too large, the caller process may hang). %% %% == EXAMPLE == %%
%% > error_logger:tty(false). % Suppresses annoying warning outputs for brevity
%%
%% > Layout = logi_builtin_layout_default:new().
%% > {ok, _} = logi_channel:install_sink(logi_builtin_sink_io_device:new(foo, [{layout, Layout}]), info).
%% > logi:info("hello world").
%% 2015-10-21 15:06:42.842 [info] nonode@nohost <0.91.0> erl_eval:do_apply:673 [] hello world
%% 
%% @end -module(logi_builtin_layout_default). -behaviour(logi_layout). %%---------------------------------------------------------------------------------------------------------------------- %% Exported API %%---------------------------------------------------------------------------------------------------------------------- -export([new/0]). %%---------------------------------------------------------------------------------------------------------------------- %% 'logi_layout' Callback API %%---------------------------------------------------------------------------------------------------------------------- -export([format/4]). %%---------------------------------------------------------------------------------------------------------------------- %% Exported Functions %%---------------------------------------------------------------------------------------------------------------------- %% @doc Creates a layout -spec new() -> logi_layout:layout(io_lib:chars()). new() -> logi_layout:new(?MODULE). %%---------------------------------------------------------------------------------------------------------------------- %% 'logi_layout' Callback Functions %%---------------------------------------------------------------------------------------------------------------------- %% @private -spec format(logi_context:context(), io:format(), logi_layout:data(), undefined) -> io_lib:chars(). format(Context, Format, Data, _) -> Location = logi_context:get_location(Context), io_lib:format( "~s [~s] ~p ~p ~s:~s:~p [~s] " ++ re:replace(Format, "~p", "~1000000p", [global, {return, list}]) ++ "\n", [ format_timestamp(logi_context:get_timestamp(Context)), logi_context:get_severity(Context), node(logi_location:get_process(Location)), logi_location:get_process(Location), logi_location:get_module(Location), logi_location:get_function(Location), logi_location:get_line(Location), format_headers(logi_context:get_headers(Context)) | Data ]). %%---------------------------------------------------------------------------------------------------------------------- %% Internal Functions %%---------------------------------------------------------------------------------------------------------------------- -spec format_timestamp(erlang:timestamp()) -> iodata(). format_timestamp(Timestamp) -> {_, _, Micros} = Timestamp, Millis = Micros div 1000, {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(Timestamp), io_lib:format("~4..0B-~2..0B-~2..0B ~2..0B:~2..0B:~2..0B.~3..0B", [Year, Month, Day, Hour, Minute, Second, Millis]). -spec format_headers(logi:headers()) -> iodata(). format_headers(Headers) -> string:join([[atom_to_list(K),"=",to_string(V)] || {K, V} <- maps:to_list(Headers)], ","). -spec to_string(term()) -> iodata(). to_string(V) when is_binary(V) -> binary_to_list(V); to_string(V) when is_atom(V) -> atom_to_list(V); to_string(V) when is_integer(V) -> integer_to_list(V); to_string(V) when is_float(V) -> float_to_list(V); to_string(V) when is_function(V) -> erlang:fun_to_list(V); to_string(V) when is_pid(V) -> erlang:pid_to_list(V); to_string(V) when is_port(V) -> erlang:port_to_list(V); to_string(V) when is_reference(V) -> erlang:ref_to_list(V); to_string(V) when is_list(V) -> IsChar = fun (C) -> is_integer(C) andalso 0 =< C andalso C =< 16#10ffff end, case lists:all(IsChar, V) of true -> V; false -> io_lib:format("~1000000p", [V]) end; to_string(V) -> io_lib:format("~1000000p", [V]).