Skip to content

Loading…

Changed the messages sent to the backends to include metadata and separated formatting from the backend #74

Merged
merged 14 commits into from

5 participants

@Vagabond

This enables the backends to do more formatting on their own and makes it easier to write more sophisticated backends, for example ones that log JSON terms.

@travisbot

This pull request passes (merged 277dafa into 77e7b68).

@travisbot

This pull request passes (merged e07c048 into 77e7b68).

@metadave metadave commented on an outdated diff
README.org
((16 lines not shown))
+ ]}
+ ]}
+ ]}.
+#+END_EXAMPLE
+
+ Included is lager_default_formatter. This 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.
+ 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.
+
+#+BEGIN_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"

typo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@metadave metadave commented on an outdated diff
src/lager.erl
((81 lines not shown))
%% @doc Manually log a message into lager without using the parse transform.
--spec log(log_level(), pid(), list()) -> ok | {error, lager_not_running}.
-log(Level, Pid, Message) ->
- Timestamp = lager_util:format_time(),
- Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
- safe_format_chop("~s", [Message], 4096)],
- safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+-spec log(log_level(), pid() | [tuple(),...], list()) -> ok | {error, lager_not_running}.
+log(Level, Pid, Message) when is_pid(Pid) ->
+ dispatch_log(Level, [{pid,Pid}], Message, [], 4096);

Can you pull the 4096 out as a constant?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@metadave metadave commented on an outdated diff
src/lager_default_formatter.erl
@@ -0,0 +1,157 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.

This code was so last year.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@metadave

@Vagabond nice work. Test cases seem reasonable, and all tests pass.
+1

@Vagabond Vagabond commented on an outdated diff
include/lager.hrl
@@ -14,6 +14,16 @@
%% specific language governing permissions and limitations
%% under the License.
+-record(lager_log_message,{

Maybe make this lager_message.

Hell, a record might not even be the best idea, unless we make it opaque.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Vagabond Vagabond commented on an outdated diff
include/lager.hrl
@@ -14,6 +14,16 @@
%% specific language governing permissions and limitations
%% under the License.
+-record(lager_log_message,{
+ destinations,
+ metadata,
+ severity_as_int,

This field is a little clumsy, maybe find a shorter one?

@DeadZen
DeadZen added a note

Maybe make this level_num

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Vagabond Vagabond Use an opaque type with a module for accessors rather than a record
I didn't want the lager_log_message record being used across application
boundaries, this will insulate other applications from any changes to
the message type's internal structure.
180e09f
@metadave

This approach certainly seems more readable and cleaner than the previous.

@Vagabond Vagabond merged commit 9981ca0 into master

1 check passed

Details default The Travis build passed
@seancribbs seancribbs deleted the adt-tensorwrench-integration-2 branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 28, 2012
  1. @nialscorva @Vagabond

    Changed the messages sent to the backends to include metadata and sep…

    nialscorva committed with Vagabond
    …arated formatting from the backend. Added documentation, fixed tests, and removed some unused code.
  2. @Vagabond

    Change the default formatter to be backwards compatible with old beha…

    Vagabond committed
    …viour
    
    Had to add a pseudo-ternary operator to the formatter to support this.
    Also allowed lager:log to log metadata.
  3. @Vagabond
  4. @Vagabond

    Whitespace cleanups

    Vagabond committed
  5. @Vagabond

    Licencing headers

    Vagabond committed
  6. @Vagabond

    Fix dialyzer warnings

    Vagabond committed
  7. @Vagabond

    Stricter dialyzer options

    Vagabond committed
  8. @Vagabond

    Adjust documentation

    Vagabond committed
  9. @Vagabond
Commits on Sep 25, 2012
  1. @Vagabond

    Fix typo

    Vagabond committed
  2. @Vagabond
  3. @Vagabond

    Time makes fools of us all

    Vagabond committed
Commits on Sep 26, 2012
  1. @Vagabond

    Use an opaque type with a module for accessors rather than a record

    Vagabond committed
    I didn't want the lager_log_message record being used across application
    boundaries, this will insulate other applications from any changes to
    the message type's internal structure.
  2. @Vagabond

    Tag lager_msg messages with a {log, ...} tuple

    Vagabond committed
    Helps with pattern matching.
View
1 .gitignore
@@ -4,3 +4,4 @@ ebin
doc
*.swp
erl_crash.dump
+.project
View
2 Makefile
@@ -39,7 +39,7 @@ dialyzer: compile
@echo Use "'make build_plt'" to build PLT prior to using this target.
@echo
@sleep 1
- dialyzer -Wno_return -Wunmatched_returns --plt $(COMBO_PLT) ebin | \
+ dialyzer -Wunmatched_returns --plt $(COMBO_PLT) ebin | \
fgrep -v -f ./dialyzer.ignore-warnings
cleanplt:
View
39 README.org
@@ -74,6 +74,45 @@
The available configuration options for each backend are listed in their
module's documentation.
+* Custom Formatting
+ All loggers have a default formatting that can be overriden. A formatter is any module that
+ exports format(#lager_log_message{},Config#any()). It is specified as part of the configuration
+ for the backend:
+
+#+BEGIN_EXAMPLE
+ {lager, [
+ {handlers, [
+ {lager_console_backend, [info, {lager_default_formatter, [time," [",severity,"] ", message, "\n"]}},
+ {lager_file_backend, [
+ [{"error.log", error, 10485760, "$D0", 5,{lager_default_formatter,[date, " ", time," [",severity,"] ",pid, " ", message, "\n"]}],
+ {"console.log", info, 10485760, "$D0", 5}
+ ]}
+ ]}
+ ]}.
+#+END_EXAMPLE
+
+ Included is lager_default_formatter. This 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.
+ 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.
+
+#+BEGIN_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 metadata print "My pid is <?.?.?>", otherwise print "Unknown Pid"
+#+END_EXAMPLE
+
+ 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 nested or refer to other properties.
+
+#+BEGIN_EXAMPLE
+ [{pid,"Unknown Pid"}] -> "<?.?.?>" if pid is in the metadata, "Unknown Pid" if not.
+ [{server,[$(,{pid,"Unknown Server"},$)]}}] -> user provided server metadata, otherwise "(<?.?.?>)", otherwise "(Unknown Server)"
+#+END_EXAMPLE
+
* Error logger integration
Lager is also supplied with a error_logger handler module that translates
traditional erlang error messages into a friendlier format and sends them into
View
15 include/lager.hrl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -14,6 +14,9 @@
%% specific language governing permissions and limitations
%% under the License.
+
+-define(DEFAULT_TRUNCATION, 4096).
+
-define(LEVELS,
[debug, info, notice, warning, error, critical, alert, emergency, none]).
@@ -55,9 +58,12 @@
lager_util:level_to_num(Level) =< element(1, lager_mochiglobal:get(loglevel, {?LOG_NONE, []}))).
-define(NOTIFY(Level, Pid, Format, Args),
- gen_event:notify(lager_event, {log, lager_util:level_to_num(Level),
- lager_util:format_time(), [io_lib:format("[~p] ", [Level]),
- io_lib:format("~p ", [Pid]), io_lib:format(Format, Args)]})).
+ gen_event:notify(lager_event, {log, lager_msg:new(io_lib:format(Format, Args),
+ lager_util:format_time(),
+ Level,
+ [{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}],
+ [])}
+ )).
%% FOR INTERNAL USE ONLY
%% internal non-blocking logging call
@@ -93,3 +99,4 @@
end
end)).
-endif.
+
View
20 src/error_logger_lager_h.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -88,7 +88,7 @@ handle_event(Event, State) ->
[ID, Name, format_reason(Reason)]);
_ ->
?CRASH_LOG(Event),
- ?LOG(error, Pid, lager:safe_format(Fmt, Args, 4096))
+ ?LOG(error, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION))
end;
{error_report, _GL, {Pid, std_error, D}} ->
?CRASH_LOG(Event),
@@ -102,17 +102,17 @@ handle_event(Event, State) ->
"Supervisor ~w had child ~s exit with reason ~s in context ~w",
[element(2, Name), Offender, format_reason(Reason), Ctx]);
_ ->
- ?LOG(error, Pid, ["SUPERVISOR REPORT ", print_silly_list(D)])
+ ?LOG(error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D))
end;
{error_report, _GL, {Pid, crash_report, [Self, Neighbours]}} ->
?CRASH_LOG(Event),
- ?LOG(error, Pid, ["CRASH REPORT ", format_crash_report(Self, Neighbours)]);
+ ?LOG(error, Pid, "CRASH REPORT " ++ format_crash_report(Self, Neighbours));
{warning_msg, _GL, {Pid, Fmt, Args}} ->
- ?LOG(warning, Pid, lager:safe_format(Fmt, Args, 4096));
+ ?LOG(warning, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION));
{warning_report, _GL, {Pid, std_warning, Report}} ->
?LOG(warning, Pid, print_silly_list(Report));
{info_msg, _GL, {Pid, Fmt, Args}} ->
- ?LOG(info, Pid, lager:safe_format(Fmt, Args, 4096));
+ ?LOG(info, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION));
{info_report, _GL, {Pid, std_info, D}} when is_list(D) ->
Details = lists:sort(D),
case Details of
@@ -136,7 +136,7 @@ handle_event(Event, State) ->
?LOG(debug, P, "Supervisor ~w started ~s at pid ~w",
[element(2, Name), MFA, Pid]);
_ ->
- ?LOG(info, P, ["PROGRESS REPORT ", print_silly_list(D)])
+ ?LOG(info, P, "PROGRESS REPORT " ++ print_silly_list(D))
end;
_ ->
?LOG(warning, self(), "Unexpected error_logger event ~w", [Event])
@@ -292,17 +292,17 @@ format_args([H|T], FmtAcc, ArgsAcc) ->
print_silly_list(L) when is_list(L) ->
case lager_stdlib:string_p(L) of
true ->
- lager_trunc_io:format("~s", [L], 4096);
+ lager_trunc_io:format("~s", [L], ?DEFAULT_TRUNCATION);
_ ->
print_silly_list(L, [], [])
end;
print_silly_list(L) ->
- {Str, _} = lager_trunc_io:print(L, 4096),
+ {Str, _} = lager_trunc_io:print(L, ?DEFAULT_TRUNCATION),
Str.
print_silly_list([], Fmt, Acc) ->
lager_trunc_io:format(string:join(lists:reverse(Fmt), ", "),
- lists:reverse(Acc), 4096);
+ lists:reverse(Acc), ?DEFAULT_TRUNCATION);
print_silly_list([{K,V}|T], Fmt, Acc) ->
print_silly_list(T, ["~p: ~p" | Fmt], [V, K | Acc]);
print_silly_list([H|T], Fmt, Acc) ->
View
112 src/lager.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -19,15 +19,16 @@
-module(lager).
-include("lager.hrl").
+-include_lib("eunit/include/eunit.hrl").
%% API
-export([start/0,
- log/9, log_dest/10, log/3, log/4,
+ log/3, log/4,
trace_file/2, trace_file/3, trace_console/1, trace_console/2,
clear_all_traces/0, stop_trace/1, status/0,
get_loglevel/1, set_loglevel/2, set_loglevel/3, get_loglevels/0,
minimum_loglevel/1, posix_error/1,
- safe_format/3, safe_format_chop/3,dispatch_log/9]).
+ safe_format/3, safe_format_chop/3,dispatch_log/5]).
-type log_level() :: debug | info | notice | warning | error | critical | alert | emergency.
-type log_level_number() :: 0..7.
@@ -52,69 +53,47 @@ start_ok(App, {error, Reason}) ->
erlang:error({app_start_failed, App, Reason}).
--spec dispatch_log(log_level(), atom(), atom(), pos_integer(), pid(), list(), string(), list(), integer()) ->
- ok | {error, lager_not_running}.
-
-dispatch_log(Severity, Module, Function, Line, Pid, Traces, Format, Args, TruncSize) ->
- {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}),
- Result=
- case LevelThreshold >= lager_util:level_to_num(Severity) of
- true -> lager:log(Severity,Module,Function,Line,Pid,
- lager_util:maybe_utc(lager_util:localtime_ms()),
- Format,Args,TruncSize);
- _ -> ok
- end,
- case TraceFilters of
- [] -> Result;
- Match when is_list(Match) ->
- lager:log_dest(Severity,Module,Function,Line,Pid,
- lager_util:maybe_utc(lager_util:localtime_ms()),
- lager_util:check_traces(Traces,
- lager_util:level_to_num(Severity),
- TraceFilters,
- []),
- Format,Args,TruncSize);
- _ -> ok
+-spec dispatch_log(log_level(), list(), string(), list() | none, pos_integer()) -> ok | {error, lager_not_running}.
+dispatch_log(Severity, Metadata, Format, Args, Size) when is_atom(Severity)->
+ case whereis(lager_event) of
+ undefined ->
+ %% lager isn't running
+ {error, lager_not_running};
+ Pid ->
+ {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}),
+ SeverityAsInt=lager_util:level_to_num(Severity),
+ Destinations = case TraceFilters of
+ [] -> [];
+ _ ->
+ lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[])
+ end,
+ case (LevelThreshold >= SeverityAsInt orelse Destinations =/= []) of
+ true ->
+ Timestamp = lager_util:format_time(),
+ Msg=case Args of
+ A when is_list(A) ->safe_format_chop(Format,Args,Size);
+ _ -> Format
+ end,
+ gen_event:sync_notify(Pid, {log, lager_msg:new(Msg, Timestamp,
+ Severity, Metadata, Destinations)});
+ _ ->
+ ok
+ end
end.
-%% @private
--spec log(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), string(), list(), integer()) ->
- ok | {error, lager_not_running}.
-log(Level, Module, Function, Line, Pid, Time, Format, Args, TruncSize) ->
- Timestamp = lager_util:format_time(Time),
- Msg = [["[", atom_to_list(Level), "] "],
- io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
- safe_format_chop(Format, Args, TruncSize)],
- safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
-
-%% @private
--spec log_dest(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), list(), string(), list(), integer()) ->
- ok | {error, lager_not_running}.
-log_dest(_Level, _Module, _Function, _Line, _Pid, _Time, [], _Format, _Args, _TruncSize) ->
- ok;
-log_dest(Level, Module, Function, Line, Pid, Time, Dest, Format, Args, TruncSize) ->
- Timestamp = lager_util:format_time(Time),
- Msg = [["[", atom_to_list(Level), "] "],
- io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
- safe_format_chop(Format, Args, TruncSize)],
- safe_notify({log, Dest, lager_util:level_to_num(Level), Timestamp, Msg}).
-
-
%% @doc Manually log a message into lager without using the parse transform.
--spec log(log_level(), pid(), list()) -> ok | {error, lager_not_running}.
-log(Level, Pid, Message) ->
- Timestamp = lager_util:format_time(),
- Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
- safe_format_chop("~s", [Message], 4096)],
- safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+-spec log(log_level(), pid() | [tuple(),...], list()) -> ok | {error, lager_not_running}.
+log(Level, Pid, Message) when is_pid(Pid) ->
+ dispatch_log(Level, [{pid,Pid}], Message, [], ?DEFAULT_TRUNCATION);
+log(Level, Metadata, Message) when is_list(Metadata) ->
+ dispatch_log(Level, Metadata, Message, [], ?DEFAULT_TRUNCATION).
%% @doc Manually log a message into lager without using the parse transform.
--spec log(log_level(), pid(), string(), list()) -> ok | {error, lager_not_running}.
-log(Level, Pid, Format, Args) ->
- Timestamp = lager_util:format_time(),
- Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
- safe_format_chop(Format, Args, 4096)],
- safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+-spec log(log_level(), pid() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}.
+log(Level, Pid, Format, Args) when is_pid(Pid) ->
+ dispatch_log(Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION);
+log(Level, Metadata, Format, Args) when is_list(Metadata) ->
+ dispatch_log(Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION).
trace_file(File, Filter) ->
trace_file(File, Filter, debug).
@@ -257,7 +236,7 @@ posix_error(Error) when is_atom(Error) ->
Message -> Message
end;
posix_error(Error) ->
- safe_format_chop("~p", [Error], 4096).
+ safe_format_chop("~p", [Error], ?DEFAULT_TRUNCATION).
%% @private
get_loglevels() ->
@@ -270,15 +249,6 @@ minimum_loglevel([]) ->
minimum_loglevel(Levels) ->
erlang:hd(lists:reverse(lists:sort(Levels))).
-safe_notify(Event) ->
- case whereis(lager_event) of
- undefined ->
- %% lager isn't running
- {error, lager_not_running};
- Pid ->
- gen_event:sync_notify(Pid, Event)
- end.
-
%% @doc Print the format string `Fmt' with `Args' safely with a size
%% limit of `Limit'. If the format string is invalid, or not enough
%% arguments are supplied 'FORMAT ERROR' is printed with the offending
View
44 src/lager_app.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -22,7 +22,9 @@
-behaviour(application).
-include("lager.hrl").
-
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
-export([start/0,
start/2,
stop/1]).
@@ -77,7 +79,43 @@ stop(Handlers) ->
expand_handlers([]) ->
[];
expand_handlers([{lager_file_backend, Configs}|T]) ->
- [{{lager_file_backend, element(1, Config)}, Config} || Config <- Configs] ++
+ [ to_config(Config) || Config <- Configs] ++
expand_handlers(T);
expand_handlers([H|T]) ->
[H | expand_handlers(T)].
+
+to_config({Name,Severity}) ->
+ {{lager_file_backend, Name}, {Name, Severity}};
+to_config({Name,_Severity,_Size,_Rotation,_Count}=Config) ->
+ {{lager_file_backend, Name}, Config};
+to_config({Name,Severity,Size,Rotation,Count,Format}) ->
+ {{lager_file_backend, Name}, {Name,Severity,Size,Rotation,Count},Format}.
+
+
+
+-ifdef(TEST).
+application_config_mangling_test_() ->
+ [{"Explode the file backend handlers",
+ ?_assertMatch(
+ [{lager_console_backend, info},
+ {{lager_file_backend,"error.log"},{"error.log",error,10485760, "$D0",5}},
+ {{lager_file_backend,"console.log"},{"console.log",info,10485760, "$D0",5}}
+ ],
+ expand_handlers([{lager_console_backend, info},
+ {lager_file_backend, [
+ {"error.log", error, 10485760, "$D0", 5},
+ {"console.log", info, 10485760, "$D0", 5}
+ ]}]
+ ))},
+ {"Explode with formatter info",
+ ?_assertMatch(
+ [{{lager_file_backend,"test.log"}, {"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}},
+ {{lager_file_backend,"test2.log"}, {"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ", message, "\n"]}}],
+ expand_handlers([{lager_file_backend, [
+ {"test.log", debug, 10485760, "$D0", 5,{lager_default_formatter,["[",severity,"] ", message, "\n"]}},
+ {"test2.log",debug, 10485760, "$D0", 5,{lager_default_formatter,["2>[",severity,"] ",message, "\n"]}}
+ ]
+ }])
+ )
+ }].
+-endif.
View
53 src/lager_console_backend.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -24,7 +24,7 @@
-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
code_change/3]).
--record(state, {level, verbose}).
+-record(state, {level, formatter,format_config}).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
@@ -33,18 +33,21 @@
-include("lager.hrl").
+-define(TERSE_FORMAT,[time, " [", severity,"] ", message, "\r\n"]).
+
%% @private
init(Level) when is_atom(Level) ->
+ init([Level,{lager_default_formatter,?TERSE_FORMAT}]);
+init([Level, true]) -> % for backwards compatibility
+ init([Level,{lager_default_formatter,[{eol, "\r\n"}]}]);
+init([Level,false]) -> % for backwards compatibility
+ init([Level,{lager_default_formatter,?TERSE_FORMAT}]);
+init([Level,{Formatter,FormatterConfig}]) when is_atom(Level), is_atom(Formatter)->
case lists:member(Level, ?LEVELS) of
true ->
- {ok, #state{level=lager_util:level_to_num(Level), verbose=false}};
- _ ->
- {error, bad_log_level}
- end;
-init([Level, Verbose]) ->
- case lists:member(Level, ?LEVELS) of
- true ->
- {ok, #state{level=lager_util:level_to_num(Level), verbose=Verbose}};
+ {ok, #state{level=lager_util:level_to_num(Level),
+ formatter=Formatter,
+ format_config=FormatterConfig}};
_ ->
{error, bad_log_level}
end.
@@ -64,29 +67,15 @@ handle_call(_Request, State) ->
{ok, ok, State}.
%% @private
-handle_event({log, Dest, Level, {Date, Time}, [LevelStr, Location, Message]},
- #state{level=L, verbose=Verbose} = State) when Level > L ->
- case lists:member(lager_console_backend, Dest) of
+handle_event({log, Message},
+ #state{level=L,formatter=Formatter,format_config=FormatConfig} = State) ->
+ case lager_util:is_loggable(Message, L, ?MODULE) of
true ->
- case Verbose of
- true ->
- io:put_chars(user, [Date, " ", Time, " ", LevelStr, Location, Message, "\r\n"]);
- _ ->
- io:put_chars(user, [Time, " ", LevelStr, Message, "\r\n"])
- end,
+ io:put_chars(user, Formatter:format(Message,FormatConfig)),
{ok, State};
false ->
{ok, State}
end;
-handle_event({log, Level, {Date, Time}, [LevelStr, Location, Message]},
- #state{level=LogLevel, verbose=Verbose} = State) when Level =< LogLevel ->
- case Verbose of
- true ->
- io:put_chars(user, [Date, " ", Time, " ", LevelStr, Location, Message, "\r\n"]);
- _ ->
- io:put_chars(user, [Time, " ", LevelStr, Message, "\r\n"])
- end,
- {ok, State};
handle_event(_Event, State) ->
{ok, State}.
@@ -146,6 +135,7 @@ console_log_test_() ->
unregister(user),
register(user, Pid),
erlang:group_leader(Pid, whereis(lager_event)),
+ lager_mochiglobal:put(loglevel, {?INFO, []}),
lager:log(info, self(), "Test message"),
receive
{io_request, From, ReplyAs, {put_chars, unicode, Msg}} ->
@@ -164,12 +154,13 @@ console_log_test_() ->
register(user, Pid),
erlang:group_leader(Pid, whereis(lager_event)),
gen_event:add_handler(lager_event, lager_console_backend, [info, true]),
- lager:log(info, self(), "Test message"),
- lager:log(info, self(), "Test message"),
+ lager_mochiglobal:put(loglevel, {?INFO, []}),
+ lager:info("Test message"),
+ lager:info("Test message"),
PidStr = pid_to_list(self()),
receive
{io_request, _, _, {put_chars, unicode, Msg}} ->
- ?assertMatch([_, _, "[info]", PidStr, "Test message\r\n"], re:split(Msg, " ", [{return, list}, {parts, 5}]))
+ ?assertMatch([_, _, "[info]", PidStr, _,"Test message\r\n"], re:split(Msg, "[ @]", [{return, list}, {parts, 6}]))
after
500 ->
?assert(false)
View
10 src/lager_crash_log.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -53,7 +53,7 @@
date,
count,
flap=false
- }).
+}).
%% @private
start_link(Filename, MaxBytes, Size, Date, Count) ->
@@ -273,7 +273,7 @@ filesystem_test_() ->
file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
{ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message))
end
},
@@ -292,7 +292,7 @@ filesystem_test_() ->
_ = gen_event:which_handlers(error_logger),
?assertEqual(3, lager_test_backend:count()),
lager_test_backend:pop(),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to reopen crash log crash_test.log with error: permission denied", lists:flatten(Message))
end
},
@@ -303,7 +303,7 @@ filesystem_test_() ->
file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
{ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message)),
file:write_file_info("crash_test.log", FInfo#file_info{mode = OldPerms}),
sync_error_logger:error_msg("Test message~n"),
View
169 src/lager_default_formatter.erl
@@ -0,0 +1,169 @@
+%% 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_lib("lager/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:timestamp(Msg),
+ D;
+output(time,Msg) ->
+ {_D, T} = lager_msg:timestamp(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).
+basic_test_() ->
+ [{"Default formatting test",
+ ?_assertEqual(iolist_to_binary([<<"Day Time [error] ">>, pid_to_list(self()), <<" Message\n">>]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ {"Day", "Time"},
+ error,
+ [{pid, self()}],
+ []),
+ [])))
+ },
+ {"Basic Formatting",
+ ?_assertEqual(<<"Simplist Format">>,
+ iolist_to_binary(format(lager_msg:new("Message",
+ {"Day", "Time"},
+ error,
+ [{pid, self()}],
+ []),
+ ["Simplist Format"])))
+ },
+ {"Default equivalent formatting test",
+ ?_assertEqual(iolist_to_binary([<<"Day Time [error] ">>, pid_to_list(self()), <<" Message\n">>]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ {"Day", "Time"},
+ error,
+ [{pid, self()}],
+ []),
+ [date, " ", time," [",severity,"] ",pid, " ", message, "\n"]
+ )))
+ },
+ {"Non existant metadata can default to string",
+ ?_assertEqual(iolist_to_binary([<<"Day Time [error] Fallback Message\n">>]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ {"Day", "Time"},
+ error,
+ [{pid, self()}],
+ []),
+ [date, " ", time," [",severity,"] ",{does_not_exist,"Fallback"}, " ", message, "\n"]
+ )))
+ },
+ {"Non existant metadata can default to other metadata",
+ ?_assertEqual(iolist_to_binary([<<"Day Time [error] Fallback Message\n">>]),
+ iolist_to_binary(format(lager_msg:new("Message",
+ {"Day", "Time"},
+ error,
+ [{pid, "Fallback"}],
+ []),
+ [date, " ", time," [",severity,"] ",{does_not_exist,pid}, " ", message, "\n"]
+ )))
+ }
+ ].
+
+-endif.
View
65 src/lager_file_backend.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -49,29 +49,37 @@
flap=false :: boolean(),
size = 0 :: integer(),
date,
- count = 10
+ count = 10,
+ formatter,
+ formatter_config
}).
%% @private
-spec init([{string(), lager:log_level()},...]) -> {ok, #state{}}.
-init(LogFile) ->
+init([LogFile,{Formatter}]) ->
+ init([LogFile,{Formatter,[]}]);
+init([LogFile,{Formatter,FormatterConfig}]) ->
case validate_logfile(LogFile) of
{Name, Level, Size, Date, Count} ->
schedule_rotation(Name, Date),
State = case lager_util:open_logfile(Name, true) of
{ok, {FD, Inode, _}} ->
#state{name=Name, level=lager_util:level_to_num(Level),
- fd=FD, inode=Inode, size=Size, date=Date, count=Count};
+ fd=FD, inode=Inode, size=Size, date=Date, count=Count, formatter=Formatter, formatter_config=FormatterConfig};
{error, Reason} ->
?INT_LOG(error, "Failed to open log file ~s with error ~s",
[Name, file:format_error(Reason)]),
#state{name=Name, level=lager_util:level_to_num(Level),
- flap=true, size=Size, date=Date, count=Count}
+ flap=true, size=Size, date=Date, count=Count, formatter=Formatter, formatter_config=FormatterConfig}
end,
+
{ok, State};
false ->
ignore
- end.
+ end;
+init(LogFile) ->
+ init([LogFile,{lager_default_formatter,[]}]).
+
%% @private
handle_call({set_loglevel, Level}, #state{name=Ident} = State) ->
@@ -83,17 +91,14 @@ handle_call(_Request, State) ->
{ok, ok, State}.
%% @private
-handle_event({log, Dest, Level, {Date, Time}, Message},
- #state{name=Name, level=L} = State) when Level > L ->
- case lists:member({lager_file_backend, Name}, Dest) of
+handle_event({log, Message},
+ #state{name=Name, level=L,formatter=Formatter,formatter_config=FormatConfig} = State) ->
+ case lager_util:is_loggable(Message,L,{lager_file_backend, Name}) of
true ->
- {ok, write(State, Level, [Date, " ", Time, " ", Message, "\n"])};
+ {ok,write(State, lager_msg:severity_as_int(Message), Formatter:format(Message,FormatConfig)) };
false ->
{ok, State}
end;
-handle_event({log, Level, {Date, Time}, Message}, #state{level=L} = State) when Level =< L->
- NewState = write(State, Level, [Date, " ", Time, " ", Message, "\n"]),
- {ok, NewState};
handle_event(_Event, State) ->
{ok, State}.
@@ -261,7 +266,7 @@ filesystem_test_() ->
file:write_file_info("test.log", FInfo#file_info{mode = 0}),
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time,Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message))
end
},
@@ -279,7 +284,7 @@ filesystem_test_() ->
?assertEqual(3, lager_test_backend:count()),
lager_test_backend:pop(),
lager_test_backend:pop(),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to reopen log file test.log with error permission denied", lists:flatten(Message))
end
},
@@ -290,7 +295,7 @@ filesystem_test_() ->
file:write_file_info("test.log", FInfo#file_info{mode = 0}),
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)),
file:write_file_info("test.log", FInfo#file_info{mode = OldPerms}),
lager:log(error, self(), "Test message"),
@@ -352,6 +357,7 @@ filesystem_test_() ->
?MODULE}], ?DEBUG,
{lager_file_backend, "test.log"}}]}),
lager:error("Test message"),
+ timer:sleep(1000),
{ok, Bin} = file:read_file("test.log"),
?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
end
@@ -389,6 +395,33 @@ filesystem_test_() ->
]
}.
+formatting_test_() ->
+ {foreach,
+ fun() ->
+ file:write_file("test.log", ""),
+ file:write_file("test2.log", ""),
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_test_backend, info}]),
+ application:set_env(lager, error_logger_redirect, false),
+ application:start(lager)
+ end,
+ fun(_) ->
+ file:delete("test.log"),
+ file:delete("test2.log"),
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [{"Should have two log files, the second prefixed with 2>",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend,[{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]),
+ gen_event:add_handler(lager_event, lager_file_backend,[{"test2.log", debug},{lager_default_formatter,["2> [",severity,"] ", message, "\n"]}]),
+ lager:log(error, self(), "Test message"),
+ ?assertMatch({ok, <<"[error] Test message\n">>},file:read_file("test.log")),
+ ?assertMatch({ok, <<"2> [error] Test message\n">>},file:read_file("test2.log"))
+ end
+ }
+ ]}.
-endif.
View
107 src/lager_format.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2011-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -88,7 +88,7 @@ field_value(Fmt, Args) ->
field_value([C|Fmt], Args, F) when is_integer(C), C >= $0, C =< $9 ->
field_value(Fmt, Args, 10*F + (C - $0));
-field_value(Fmt, Args, F) -> %Default case
+field_value(Fmt, Args, F) -> %Default case
{F,Fmt,Args}.
pad_char([$.,$*|Fmt], [Pad|Args]) -> {Pad,Fmt,Args};
@@ -96,7 +96,7 @@ pad_char([$.,Pad|Fmt], Args) -> {Pad,Fmt,Args};
pad_char(Fmt, Args) -> {$\s,Fmt,Args}.
%% collect_cc([FormatChar], [Argument]) ->
-%% {Control,[ControlArg],[FormatChar],[Arg]}.
+%% {Control,[ControlArg],[FormatChar],[Arg]}.
%% Here we collect the argments for each control character.
%% Be explicit to cause failure early.
@@ -151,8 +151,7 @@ build2([C|Cs], Count, MaxLen) ->
build2([], _, _) -> [].
%% control(FormatChar, [Argument], FieldWidth, Adjust, Precision, PadChar,
-%% Indentation) ->
-%% [Char]
+%% Indentation) -> [Char]
%% This is the main dispatch function for the various formatting commands.
%% Field widths and precisions have already been calculated.
@@ -275,15 +274,15 @@ term(T, F, Adj, P0, Pad) ->
L = lists:flatlength(T),
P = case P0 of none -> erlang:min(L, F); _ -> P0 end,
if
- L > P ->
- adjust(chars($*, P), chars(Pad, F-P), Adj);
- F >= P ->
- adjust(T, chars(Pad, F-L), Adj)
+ L > P ->
+ adjust(chars($*, P), chars(Pad, F-P), Adj);
+ F >= P ->
+ adjust(T, chars(Pad, F-L), Adj)
end.
%% fwrite_e(Float, Field, Adjust, Precision, PadChar)
-fwrite_e(Fl, none, Adj, none, Pad) -> %Default values
+fwrite_e(Fl, none, Adj, none, Pad) -> %Default values
fwrite_e(Fl, none, Adj, 6, Pad);
fwrite_e(Fl, none, _Adj, P, _Pad) when P >= 2 ->
float_e(Fl, float_data(Fl), P);
@@ -292,12 +291,12 @@ fwrite_e(Fl, F, Adj, none, Pad) ->
fwrite_e(Fl, F, Adj, P, Pad) when P >= 2 ->
term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad).
-float_e(Fl, Fd, P) when Fl < 0.0 -> %Negative numbers
+float_e(Fl, Fd, P) when Fl < 0.0 -> %Negative numbers
[$-|float_e(-Fl, Fd, P)];
float_e(_Fl, {Ds,E}, P) ->
case float_man(Ds, 1, P-1) of
- {[$0|Fs],true} -> [[$1|Fs]|float_exp(E)];
- {Fs,false} -> [Fs|float_exp(E-1)]
+ {[$0|Fs],true} -> [[$1|Fs]|float_exp(E)];
+ {Fs,false} -> [Fs|float_exp(E-1)]
end.
%% float_man([Digit], Icount, Dcount) -> {[Chars],CarryFlag}.
@@ -310,22 +309,22 @@ float_man(Ds, 0, Dc) ->
{[$.|Cs],C};
float_man([D|Ds], I, Dc) ->
case float_man(Ds, I-1, Dc) of
- {Cs,true} when D =:= $9 -> {[$0|Cs],true};
- {Cs,true} -> {[D+1|Cs],false};
- {Cs,false} -> {[D|Cs],false}
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
end;
-float_man([], I, Dc) -> %Pad with 0's
+float_man([], I, Dc) -> %Pad with 0's
{string:chars($0, I, [$.|string:chars($0, Dc)]),false}.
float_man([D|_], 0) when D >= $5 -> {[],true};
float_man([_|_], 0) -> {[],false};
float_man([D|Ds], Dc) ->
case float_man(Ds, Dc-1) of
- {Cs,true} when D =:= $9 -> {[$0|Cs],true};
- {Cs,true} -> {[D+1|Cs],false};
- {Cs,false} -> {[D|Cs],false}
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
end;
-float_man([], Dc) -> {string:chars($0, Dc),false}. %Pad with 0's
+float_man([], Dc) -> {string:chars($0, Dc),false}. %Pad with 0's
%% float_exp(Exponent) -> [Char].
%% Generate the exponent of a floating point number. Always include sign.
@@ -337,7 +336,7 @@ float_exp(E) ->
%% fwrite_f(FloatData, Field, Adjust, Precision, PadChar)
-fwrite_f(Fl, none, Adj, none, Pad) -> %Default values
+fwrite_f(Fl, none, Adj, none, Pad) -> %Default values
fwrite_f(Fl, none, Adj, 6, Pad);
fwrite_f(Fl, none, _Adj, P, _Pad) when P >= 1 ->
float_f(Fl, float_data(Fl), P);
@@ -349,11 +348,11 @@ fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 ->
float_f(Fl, Fd, P) when Fl < 0.0 ->
[$-|float_f(-Fl, Fd, P)];
float_f(Fl, {Ds,E}, P) when E =< 0 ->
- float_f(Fl, {string:chars($0, -E+1, Ds),1}, P); %Prepend enough 0's
+ float_f(Fl, {string:chars($0, -E+1, Ds),1}, P); %Prepend enough 0's
float_f(_Fl, {Ds,E}, P) ->
case float_man(Ds, E, P) of
- {Fs,true} -> "1" ++ Fs; %Handle carry
- {Fs,false} -> Fs
+ {Fs,true} -> "1" ++ Fs; %Handle carry
+ {Fs,false} -> Fs
end.
%% float_data([FloatChar]) -> {[Digit],Exponent}
@@ -378,20 +377,20 @@ fwrite_g(Fl, F, Adj, none, Pad) ->
fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 ->
A = abs(Fl),
E = if A < 1.0e-1 -> -2;
- A < 1.0e0 -> -1;
- A < 1.0e1 -> 0;
- A < 1.0e2 -> 1;
- A < 1.0e3 -> 2;
- A < 1.0e4 -> 3;
- true -> fwrite_f
- end,
+ A < 1.0e0 -> -1;
+ A < 1.0e1 -> 0;
+ A < 1.0e2 -> 1;
+ A < 1.0e3 -> 2;
+ A < 1.0e4 -> 3;
+ true -> fwrite_f
+ end,
if P =< 1, E =:= -1;
- P-1 > E, E >= -1 ->
- fwrite_f(Fl, F, Adj, P-1-E, Pad);
- P =< 1 ->
- fwrite_e(Fl, F, Adj, 2, Pad);
- true ->
- fwrite_e(Fl, F, Adj, P, Pad)
+ P-1 > E, E >= -1 ->
+ fwrite_f(Fl, F, Adj, P-1-E, Pad);
+ P =< 1 ->
+ fwrite_e(Fl, F, Adj, 2, Pad);
+ true ->
+ fwrite_e(Fl, F, Adj, P, Pad)
end.
@@ -405,15 +404,15 @@ string(S, none, _Adj, P, Pad) ->
string(S, F, Adj, P, Pad) when F >= P ->
N = lists:flatlength(S),
if F > P ->
- if N > P ->
- adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
- N < P ->
- adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj);
- true -> % N == P
- adjust(S, chars(Pad, F-P), Adj)
- end;
+ if N > P ->
+ adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
+ N < P ->
+ adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj);
+ true -> % N == P
+ adjust(S, chars(Pad, F-P), Adj)
+ end;
true -> % F == P
- string_field(S, F, Adj, N, Pad)
+ string_field(S, F, Adj, N, Pad)
end.
string_field(S, F, _Adj, N, _Pad) when N > F ->
@@ -429,11 +428,11 @@ string_field(S, _, _, _, _) -> % N == F
unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
when Base >= 2, Base =< 1+$Z-$A+10 ->
if Int < 0 ->
- S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
- term([$-|S], F, Adj, none, Pad);
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-|S], F, Adj, none, Pad);
true ->
- S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
- term(S, F, Adj, none, Pad)
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term(S, F, Adj, none, Pad)
end.
%% prefixed_integer(Int, Field, Adjust, Base, PadChar, Prefix, Lowercase)
@@ -442,11 +441,11 @@ unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
prefixed_integer(Int, F, Adj, Base, Pad, Prefix, Lowercase)
when Base >= 2, Base =< 1+$Z-$A+10 ->
if Int < 0 ->
- S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
- term([$-,Prefix|S], F, Adj, none, Pad);
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-,Prefix|S], F, Adj, none, Pad);
true ->
- S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
- term([Prefix|S], F, Adj, none, Pad)
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term([Prefix|S], F, Adj, none, Pad)
end.
%% char(Char, Field, Adjust, Precision, PadChar) -> [Char].
View
8 src/lager_handler_watcher.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -117,7 +117,7 @@ reinstall_on_initial_failure_test_() ->
application:start(lager),
try
?assertEqual(1, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message)),
?assertEqual(0, lager_test_backend:count()),
timer:sleep(6000),
@@ -148,9 +148,9 @@ reinstall_on_runtime_failure_test_() ->
?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))),
timer:sleep(6000),
?assertEqual(2, lager_test_backend:count()),
- {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
?assertEqual("Lager event handler lager_crash_backend exited with reason crash", lists:flatten(Message)),
- {_Level2, _Time2, [_, _, Message2]} = lager_test_backend:pop(),
+ {_Level2, _Time2, Message2, _Metadata} = lager_test_backend:pop(),
?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message2)),
?assertEqual(false, lists:member(lager_crash_backend, gen_event:which_handlers(lager_event)))
after
View
2 src/lager_handler_watcher_sup.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
View
51 src/lager_msg.erl
@@ -0,0 +1,51 @@
+-module(lager_msg).
+
+-export([new/5]).
+-export([message/1]).
+-export([timestamp/1]).
+-export([severity/1]).
+-export([severity_as_int/1]).
+-export([metadata/1]).
+-export([destinations/1]).
+
+-record(lager_msg,{
+ destinations :: list(),
+ metadata :: [tuple()],
+ severity :: lager:log_level(),
+ timestamp :: {string(), string()},
+ message :: list()
+ }).
+
+-opaque lager_msg() :: #lager_msg{}.
+-export_type([lager_msg/0]).
+
+-spec new(list(), {string(), string()}, atom(), [tuple()], list()) -> lager_msg().
+new(Msg, Timestamp, Severity, Metadata, Destinations) ->
+ #lager_msg{message=Msg, timestamp=Timestamp, severity=Severity,
+ metadata=Metadata, destinations=Destinations}.
+
+-spec message(lager_msg()) -> list().
+message(Msg) ->
+ Msg#lager_msg.message.
+
+-spec timestamp(lager_msg()) -> {string(), string()}.
+timestamp(Msg) ->
+ Msg#lager_msg.timestamp.
+
+-spec severity(lager_msg()) -> lager:log_level().
+severity(Msg) ->
+ Msg#lager_msg.severity.
+
+-spec severity_as_int(lager_msg()) -> lager:log_level_number().
+severity_as_int(Msg) ->
+ lager_util:level_to_num(Msg#lager_msg.severity).
+
+-spec metadata(lager_msg()) -> [tuple()].
+metadata(Msg) ->
+ Msg#lager_msg.metadata.
+
+-spec destinations(lager_msg()) -> list().
+destinations(Msg) ->
+ Msg#lager_msg.destinations.
+
+
View
2 src/lager_sup.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
View
35 src/lager_transform.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -29,7 +29,7 @@
%% @private
parse_transform(AST, Options) ->
- TruncSize = proplists:get_value(lager_truncation_size, Options, 4096),
+ TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION),
put(truncation_size, TruncSize),
walk_ast([], AST).
@@ -59,8 +59,8 @@ walk_body(Acc, []) ->
walk_body(Acc, [H|T]) ->
walk_body([transform_statement(H)|Acc], T).
-transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
- {atom, Line3, Severity}}, Arguments0} = Stmt) ->
+transform_statement({call, Line, {remote, _Line1, {atom, _Line2, lager},
+ {atom, _Line3, Severity}}, Arguments0} = Stmt) ->
case lists:member(Severity, ?LEVELS) of
true ->
DefaultAttrs = {cons, Line, {tuple, Line, [
@@ -77,7 +77,7 @@ transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
{nil, Line}}}}},
{Traces, Message, Arguments} = case Arguments0 of
[Format] ->
- {DefaultAttrs, Format, {nil, Line}};
+ {DefaultAttrs, Format, {atom, Line, none}};
[Arg1, Arg2] ->
%% some ambiguity here, figure out if these arguments are
%% [Format, Args] or [Attr, Format].
@@ -86,29 +86,22 @@ transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
case Arg1 of
{cons, _, {tuple, _, _}, _} ->
{concat_lists(Arg1, DefaultAttrs),
- Arg2, {nil,Line}};
+ Arg2, {atom, Line, none}};
_ ->
{DefaultAttrs, Arg1, Arg2}
end;
[Attrs, Format, Args] ->
{concat_lists(Attrs, DefaultAttrs), Format, Args}
end,
- {block, Line,
+ {call, Line, {remote, Line, {atom,Line,lager},{atom,Line,dispatch_log}},
[
- {call, Line, {remote, Line, {atom,Line1,lager},{atom,Line2,dispatch_log}},
- [
- {atom, Line3, Severity},
- {atom, Line3, get(module)},
- {atom, Line3, get(function)},
- {integer, Line3, Line},
- {call, Line3, {atom, Line3 ,self}, []},
- Traces,
- Message,
- Arguments,
- {integer, Line3, get(truncation_size)}
- ]
- }
- ]}; % block contents
+ {atom,Line,Severity},
+ Traces,
+ Message,
+ Arguments,
+ {integer, Line, get(truncation_size)}
+ ]
+ };
false ->
Stmt
end;
View
44 src/lager_trunc_io.erl
@@ -42,8 +42,8 @@
-endif.
-type option() :: {'depth', integer()}
- | {'lists_as_strings', boolean()}
- | {'force_strings', boolean()}.
+ | {'lists_as_strings', boolean()}
+ | {'force_strings', boolean()}.
-type options() :: [option()].
-record(print_options, {
@@ -92,9 +92,9 @@ fprint(T, Max, Options) ->
-spec safe(term(), pos_integer()) -> {string(), pos_integer()} | {string()}.
safe(What, Len) ->
case catch print(What, Len) of
- {L, Used} when is_list(L) -> {L, Used};
- _ -> {"unable to print" ++ io_lib:write(What, 99)}
- end.
+ {L, Used} when is_list(L) -> {L, Used};
+ _ -> {"unable to print" ++ io_lib:write(What, 99)}
+ end.
%% @doc Returns {List, Length}
-spec print(term(), pos_integer()) -> {iolist(), pos_integer()}.
@@ -371,29 +371,29 @@ test() ->
-spec test(atom(), atom()) -> ok.
test(Mod, Func) ->
Simple_items = [atom, 1234, 1234.0, {tuple}, [], [list], "string", self(),
- <<1,2,3>>, make_ref(), fun() -> ok end],
+ <<1,2,3>>, make_ref(), fun() -> ok end],
F = fun(A) ->
- Mod:Func(A, 100),
- Mod:Func(A, 2),
- Mod:Func(A, 20)
- end,
+ Mod:Func(A, 100),
+ Mod:Func(A, 2),
+ Mod:Func(A, 20)
+ end,
G = fun(A) ->
- case catch F(A) of
- {'EXIT', _} -> exit({failed, A});
- _ -> ok
- end
- end,
-
+ case catch F(A) of
+ {'EXIT', _} -> exit({failed, A});
+ _ -> ok
+ end
+ end,
+
lists:foreach(G, Simple_items),
-
+
Tuples = [ {1,2,3,a,b,c}, {"abc", def, 1234},
- {{{{a},b,c,{d},e}},f}],
-
+ {{{{a},b,c,{d},e}},f}],
+
Lists = [ [1,2,3,4,5,6,7], lists:seq(1,1000),
- [{a}, {a,b}, {a, [b,c]}, "def"], [a|b], [$a|$b] ],
-
-
+ [{a}, {a,b}, {a, [b,c]}, "def"], [a|b], [$a|$b] ],
+
+
lists:foreach(G, Tuples),
lists:foreach(G, Lists).
View
20 src/lager_util.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
@@ -21,12 +21,14 @@
-export([levels/0, level_to_num/1, num_to_level/1, open_logfile/2,
ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
localtime_ms/0, maybe_utc/1, parse_rotation_date_spec/1,
- calculate_next_rotation/1, validate_trace/1, check_traces/4]).
+ calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
+-include("lager.hrl").
+
levels() ->
[debug, info, notice, warning, error, critical, alert, emergency].
@@ -327,6 +329,11 @@ check_trace_iter(Attrs, [{Key, Match}|T]) ->
false
end.
+-spec is_loggable(lager_msg:lager_msg(),integer(),term()) -> boolean().
+is_loggable(Msg ,SeverityThreshold,MyName) ->
+ lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse
+ lists:member(MyName, lager_msg:destinations(Msg)).
+
-ifdef(TEST).
parse_test() ->
@@ -454,4 +461,13 @@ check_trace_test() ->
{[{module, '*'}], 0, bar}], [])),
ok.
+is_loggable_test_() ->
+ [
+ {"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("",{"",""}, alert, [], []),2,me))},
+ {"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("",{"",""}, critical, [], []),1,me))},
+ {"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("",{"",""}, alert, [], [you]),2,me))},
+ {"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("",{"",""}, critical, [], [you]),1,me))},
+ {"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("",{"",""}, critical, [], [me]),1,me))}
+ ].
+
-endif.
View
110 test/crash.erl
@@ -15,92 +15,92 @@
}).
start() ->
- gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+ gen_server:start({local, ?MODULE}, ?MODULE, [], []).
init(_) ->
- {ok, {}}.
+ {ok, {}}.
handle_call(undef, _, State) ->
- {reply, ?MODULE:booger(), State};
+ {reply, ?MODULE:booger(), State};
handle_call(badfun, _, State) ->
- M = booger,
- {reply, M(), State};
+ M = booger,
+ {reply, M(), State};
handle_call(bad_return, _, _) ->
- bleh;
+ bleh;
handle_call(bad_return_string, _, _) ->
- {tuple, {tuple, "string"}};
+ {tuple, {tuple, "string"}};
handle_call(case_clause, _, State) ->
- case State of
- goober ->
- {reply, ok, State}
- end;
+ case State of
+ goober ->
+ {reply, ok, State}
+ end;
handle_call(case_clause_string, _, State) ->
- Foo = atom_to_list(?MODULE),
- case Foo of
- State ->
- {reply, ok, State}
- end;
+ Foo = atom_to_list(?MODULE),
+ case Foo of
+ State ->
+ {reply, ok, State}
+ end;
handle_call(if_clause, _, State) ->
- if State == 1 ->
- {reply, ok, State}
- end;
+ if State == 1 ->
+ {reply, ok, State}
+ end;
handle_call(try_clause, _, State) ->
- Res = try tuple_to_list(State) of
- [_A, _B] -> ok
- catch
- _:_ -> ok
- end,
- {reply, Res, State};
+ Res = try tuple_to_list(State) of
+ [_A, _B] -> ok
+ catch
+ _:_ -> ok
+ end,
+ {reply, Res, State};
handle_call(badmatch, _, State) ->
- {A, B, C} = State,
- {reply, [A, B, C], State};
+ {A, B, C} = State,
+ {reply, [A, B, C], State};
handle_call(badrecord, _, State) ->
- Host = State#state.host,
- {reply, Host, State};
+ Host = State#state.host,
+ {reply, Host, State};
handle_call(function_clause, _, State) ->
- {reply, function(State), State};
+ {reply, function(State), State};
handle_call(badarith, _, State) ->
- Res = 1 / length(tuple_to_list(State)),
- {reply, Res, State};
+ Res = 1 / length(tuple_to_list(State)),
+ {reply, Res, State};
handle_call(badarg1, _, State) ->
- Res = list_to_binary(["foo", bar]),
- {reply, Res, State};
+ Res = list_to_binary(["foo", bar]),
+ {reply, Res, State};
handle_call(badarg2, _, State) ->
- Res = erlang:iolist_to_binary(["foo", bar]),
- {reply, Res, State};
+ Res = erlang:iolist_to_binary(["foo", bar]),
+ {reply, Res, State};
handle_call(system_limit, _, State) ->
- Res = list_to_atom(lists:flatten(lists:duplicate(256, "a"))),
- {reply, Res, State};
+ Res = list_to_atom(lists:flatten(lists:duplicate(256, "a"))),
+ {reply, Res, State};
handle_call(process_limit, _, State) ->
- %% run with +P 300 to make this crash
- [erlang:spawn(fun() -> timer:sleep(5000) end) || _ <- lists:seq(0, 500)],
- {reply, ok, State};
+ %% run with +P 300 to make this crash
+ [erlang:spawn(fun() -> timer:sleep(5000) end) || _ <- lists:seq(0, 500)],
+ {reply, ok, State};
handle_call(port_limit, _, State) ->
- [erlang:open_port({spawn, "ls"}, []) || _ <- lists:seq(0, 1024)],
- {reply, ok, State};
+ [erlang:open_port({spawn, "ls"}, []) || _ <- lists:seq(0, 1024)],
+ {reply, ok, State};
handle_call(noproc, _, State) ->
- Res = gen_event:call(foo, bar, baz),
- {reply, Res, State};
+ Res = gen_event:call(foo, bar, baz),
+ {reply, Res, State};
handle_call(badarity, _, State) ->
- F = fun(A, B, C) -> A + B + C end,
- Res = F(State),
- {reply, Res, State};
+ F = fun(A, B, C) -> A + B + C end,
+ Res = F(State),
+ {reply, Res, State};
handle_call(throw, _, _State) ->
- throw(a_ball);
+ throw(a_ball);
handle_call(_Call, _From, State) ->
- {reply, ok, State}.
+ {reply, ok, State}.
handle_cast(_Cast, State) ->
- {noreply, State}.
+ {noreply, State}.
handle_info(_Info, State) ->
- {noreply, State}.
+ {noreply, State}.
terminate(_, _) ->
- ok.
+ ok.
code_change(_, State, _) ->
- {ok, State}.
+ {ok, State}.
function(X) when is_list(X) ->
- ok.
+ ok.
View
2 test/lager_crash_backend.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%% 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
View
432 test/lager_test_backend.erl