|
|
@ -43,6 +43,15 @@ |
|
|
|
|
|
|
|
-export([config_to_id/1]). |
|
|
|
|
|
|
|
-define(DEFAULT_LOG_LEVEL, info). |
|
|
|
-define(DEFAULT_ROTATION_SIZE, 10485760). %% 10mb |
|
|
|
-define(DEFAULT_ROTATION_DATE, "$D0"). %% midnight |
|
|
|
-define(DEFAULT_ROTATION_COUNT, 5). |
|
|
|
-define(DEFAULT_SYNC_LEVEL, error). |
|
|
|
-define(DEFAULT_SYNC_INTERVAL, 1000). |
|
|
|
-define(DEFAULT_SYNC_SIZE, 1024*64). %% 64kb |
|
|
|
-define(DEFAULT_CHECK_INTERVAL, 1000). |
|
|
|
|
|
|
|
-record(state, { |
|
|
|
name :: string(), |
|
|
|
level :: {'mask', integer()}, |
|
|
@ -53,35 +62,53 @@ |
|
|
|
date, |
|
|
|
count = 10, |
|
|
|
formatter, |
|
|
|
formatter_config |
|
|
|
formatter_config, |
|
|
|
sync_on, |
|
|
|
check_interval = ?DEFAULT_CHECK_INTERVAL, |
|
|
|
sync_interval = ?DEFAULT_SYNC_INTERVAL, |
|
|
|
sync_size = ?DEFAULT_SYNC_SIZE, |
|
|
|
last_check = os:timestamp() |
|
|
|
}). |
|
|
|
|
|
|
|
%% @private |
|
|
|
-spec init([{string(), lager:log_level()},...]) -> {ok, #state{}}. |
|
|
|
init({FileName, LogLevel}) when is_list(FileName), is_atom(LogLevel) -> |
|
|
|
%% backwards compatability hack |
|
|
|
init([{file, FileName}, {level, LogLevel}]); |
|
|
|
init({FileName, LogLevel, Size, Date, Count}) when is_list(FileName), is_atom(LogLevel) -> |
|
|
|
%% backwards compatability hack |
|
|
|
init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}]); |
|
|
|
init([{FileName, LogLevel, Size, Date, Count}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) -> |
|
|
|
%% backwards compatability hack |
|
|
|
init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}, {formatter, Formatter}, {formatter_config, FormatterConfig}]); |
|
|
|
init([LogFile,{Formatter}]) -> |
|
|
|
%% backwards compatability hack |
|
|
|
init([LogFile,{Formatter,[]}]); |
|
|
|
init([LogFile,{Formatter,FormatterConfig}]) -> |
|
|
|
case validate_logfile(LogFile) of |
|
|
|
{Name, Level, Size, Date, Count} -> |
|
|
|
init([{FileName, LogLevel}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) -> |
|
|
|
%% backwards compatability hack |
|
|
|
init([{file, FileName}, {level, LogLevel}, {formatter, Formatter}, {formatter_config, FormatterConfig}]); |
|
|
|
init(LogFileConfig) when is_list(LogFileConfig) -> |
|
|
|
case validate_logfile_proplist(LogFileConfig) of |
|
|
|
false -> |
|
|
|
%% falied to validate config |
|
|
|
ignore; |
|
|
|
Config -> |
|
|
|
%% probabably a better way to do this, but whatever |
|
|
|
[Name, Level, Date, Size, Count, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] = |
|
|
|
[proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]], |
|
|
|
schedule_rotation(Name, Date), |
|
|
|
State = case lager_util:open_logfile(Name, true) of |
|
|
|
State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, formatter=Formatter, |
|
|
|
formatter_config=FormatterConfig, sync_on=SyncOn, sync_interval=SyncInterval, sync_size=SyncSize, |
|
|
|
check_interval=CheckInterval}, |
|
|
|
State = case lager_util:open_logfile(Name, {SyncSize, SyncInterval}) of |
|
|
|
{ok, {FD, Inode, _}} -> |
|
|
|
#state{name=Name, level=Level, |
|
|
|
fd=FD, inode=Inode, size=Size, date=Date, count=Count, formatter=Formatter, formatter_config=FormatterConfig}; |
|
|
|
State0#state{fd=FD, inode=Inode}; |
|
|
|
{error, Reason} -> |
|
|
|
?INT_LOG(error, "Failed to open log file ~s with error ~s", |
|
|
|
[Name, file:format_error(Reason)]), |
|
|
|
#state{name=Name, level=Level, |
|
|
|
flap=true, size=Size, date=Date, count=Count, formatter=Formatter, formatter_config=FormatterConfig} |
|
|
|
?INT_LOG(error, "Failed to open log file ~s with error ~s", [Name, file:format_error(Reason)]), |
|
|
|
State0#state{flap=true} |
|
|
|
end, |
|
|
|
|
|
|
|
{ok, State}; |
|
|
|
false -> |
|
|
|
ignore |
|
|
|
end; |
|
|
|
init(LogFile) -> |
|
|
|
init([LogFile,{lager_default_formatter,[]}]). |
|
|
|
|
|
|
|
{ok, State} |
|
|
|
end. |
|
|
|
|
|
|
|
%% @private |
|
|
|
handle_call({set_loglevel, Level}, #state{name=Ident} = State) -> |
|
|
@ -102,7 +129,7 @@ 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, lager_msg:severity_as_int(Message), Formatter:format(Message,FormatConfig)) }; |
|
|
|
{ok,write(State, lager_msg:timestamp(Message), lager_msg:severity_as_int(Message), Formatter:format(Message,FormatConfig)) }; |
|
|
|
false -> |
|
|
|
{ok, State} |
|
|
|
end; |
|
|
@ -129,93 +156,71 @@ code_change(_OldVsn, State, _Extra) -> |
|
|
|
{ok, State}. |
|
|
|
|
|
|
|
%% convert the config into a gen_event handler ID |
|
|
|
config_to_id({Name,_Severity}) -> |
|
|
|
config_to_id({Name,_Severity}) when is_list(Name) -> |
|
|
|
{?MODULE, Name}; |
|
|
|
config_to_id({Name,_Severity,_Size,_Rotation,_Count}) -> |
|
|
|
{?MODULE, Name}; |
|
|
|
config_to_id([{Name,_Severity,_Size,_Rotation,_Count}, _Format]) -> |
|
|
|
{?MODULE, Name}. |
|
|
|
{?MODULE, Name}; |
|
|
|
config_to_id([{Name,_Severity}, _Format]) when is_list(Name) -> |
|
|
|
{?MODULE, Name}; |
|
|
|
config_to_id(Config) -> |
|
|
|
case proplists:get_value(file, Config) of |
|
|
|
undefined -> |
|
|
|
erlang:error(no_file); |
|
|
|
File -> |
|
|
|
{?MODULE, File} |
|
|
|
end. |
|
|
|
|
|
|
|
|
|
|
|
write(#state{name=Name, fd=FD, inode=Inode, flap=Flap, size=RotSize, |
|
|
|
count=Count} = State, Level, Msg) -> |
|
|
|
case lager_util:ensure_logfile(Name, FD, Inode, true) of |
|
|
|
{ok, {_, _, Size}} when RotSize /= 0, Size > RotSize -> |
|
|
|
lager_util:rotate_logfile(Name, Count), |
|
|
|
write(State, Level, Msg); |
|
|
|
{ok, {NewFD, NewInode, _}} -> |
|
|
|
%% delayed_write doesn't report errors |
|
|
|
_ = file:write(NewFD, Msg), |
|
|
|
case Level of |
|
|
|
_ when Level =< ?ERROR -> |
|
|
|
%% force a sync on any message at error severity or above |
|
|
|
Flap2 = case file:datasync(NewFD) of |
|
|
|
{error, Reason2} when Flap == false -> |
|
|
|
?INT_LOG(error, "Failed to write log message to file ~s: ~s", |
|
|
|
[Name, file:format_error(Reason2)]), |
|
|
|
true; |
|
|
|
ok -> |
|
|
|
false; |
|
|
|
count=Count} = State, Timestamp, Level, Msg) -> |
|
|
|
LastCheck = timer:now_diff(os:timestamp(), Timestamp) div 1000, |
|
|
|
case LastCheck >= State#state.check_interval orelse FD == undefined of |
|
|
|
true -> |
|
|
|
%% need to check for rotation |
|
|
|
case lager_util:ensure_logfile(Name, FD, Inode, {State#state.sync_size, State#state.sync_interval}) of |
|
|
|
{ok, {_, _, Size}} when RotSize /= 0, Size > RotSize -> |
|
|
|
lager_util:rotate_logfile(Name, Count), |
|
|
|
%% go around the loop again, we'll do another rotation check and hit the next clause here |
|
|
|
write(State, Timestamp, Level, Msg); |
|
|
|
{ok, {NewFD, NewInode, _}} -> |
|
|
|
%% update our last check and try again |
|
|
|
do_write(State#state{last_check=Timestamp, fd=NewFD, inode=NewInode}, Level, Msg); |
|
|
|
{error, Reason} -> |
|
|
|
case Flap of |
|
|
|
true -> |
|
|
|
State; |
|
|
|
_ -> |
|
|
|
Flap |
|
|
|
end, |
|
|
|
State#state{fd=NewFD, inode=NewInode, flap=Flap2}; |
|
|
|
_ -> |
|
|
|
State#state{fd=NewFD, inode=NewInode} |
|
|
|
?INT_LOG(error, "Failed to reopen log file ~s with error ~s", [Name, file:format_error(Reason)]), |
|
|
|
State#state{flap=true} |
|
|
|
end |
|
|
|
end; |
|
|
|
{error, Reason} -> |
|
|
|
case Flap of |
|
|
|
true -> |
|
|
|
State; |
|
|
|
_ -> |
|
|
|
?INT_LOG(error, "Failed to reopen log file ~s with error ~s", |
|
|
|
[Name, file:format_error(Reason)]), |
|
|
|
State#state{flap=true} |
|
|
|
end |
|
|
|
false -> |
|
|
|
do_write(State, Level, Msg) |
|
|
|
end. |
|
|
|
|
|
|
|
validate_logfile({Name, Level}) -> |
|
|
|
case validate_loglevel(Level) of |
|
|
|
false -> |
|
|
|
?INT_LOG(error, "Invalid log level of ~p for ~s.", |
|
|
|
[Level, Name]), |
|
|
|
false; |
|
|
|
Levels -> |
|
|
|
{Name, Levels, 0, undefined, 0} |
|
|
|
end; |
|
|
|
validate_logfile({Name, Level, Size, Date, Count}) -> |
|
|
|
ValidLevel = validate_loglevel(Level), |
|
|
|
ValidSize = (is_integer(Size) andalso Size >= 0), |
|
|
|
ValidCount = (is_integer(Count) andalso Count >= 0), |
|
|
|
case {ValidLevel, ValidSize, ValidCount} of |
|
|
|
{false, _, _} -> |
|
|
|
?INT_LOG(error, "Invalid log level of ~p for ~s.", |
|
|
|
[Level, Name]), |
|
|
|
false; |
|
|
|
{_, false, _} -> |
|
|
|
?INT_LOG(error, "Invalid rotation size of ~p for ~s.", |
|
|
|
[Size, Name]), |
|
|
|
false; |
|
|
|
{_, _, false} -> |
|
|
|
?INT_LOG(error, "Invalid rotation count of ~p for ~s.", |
|
|
|
[Count, Name]), |
|
|
|
false; |
|
|
|
{Levels, true, true} -> |
|
|
|
case lager_util:parse_rotation_date_spec(Date) of |
|
|
|
{ok, Spec} -> |
|
|
|
{Name, Levels, Size, Spec, Count}; |
|
|
|
{error, _} when Date == "" -> |
|
|
|
%% blank ones are fine. |
|
|
|
{Name, Levels, Size, undefined, Count}; |
|
|
|
{error, _} -> |
|
|
|
?INT_LOG(error, "Invalid rotation date of ~p for ~s.", |
|
|
|
[Date, Name]), |
|
|
|
false |
|
|
|
end |
|
|
|
end; |
|
|
|
validate_logfile(H) -> |
|
|
|
?INT_LOG(error, "Invalid log file config ~p.", [H]), |
|
|
|
false. |
|
|
|
do_write(#state{fd=FD, name=Name, flap=Flap} = State, Level, Msg) -> |
|
|
|
%% delayed_write doesn't report errors |
|
|
|
_ = file:write(FD, Msg), |
|
|
|
{mask, SyncLevel} = State#state.sync_on, |
|
|
|
case (Level band SyncLevel) /= 0 of |
|
|
|
true -> |
|
|
|
%% force a sync on any message that matches the 'sync_on' bitmask |
|
|
|
Flap2 = case file:datasync(FD) of |
|
|
|
{error, Reason2} when Flap == false -> |
|
|
|
?INT_LOG(error, "Failed to write log message to file ~s: ~s", |
|
|
|
[Name, file:format_error(Reason2)]), |
|
|
|
true; |
|
|
|
ok -> |
|
|
|
false; |
|
|
|
_ -> |
|
|
|
Flap |
|
|
|
end, |
|
|
|
State#state{flap=Flap2}; |
|
|
|
_ -> |
|
|
|
State |
|
|
|
end. |
|
|
|
|
|
|
|
validate_loglevel(Level) -> |
|
|
|
try lager_util:config_to_mask(Level) of |
|
|
@ -226,6 +231,113 @@ validate_loglevel(Level) -> |
|
|
|
false |
|
|
|
end. |
|
|
|
|
|
|
|
validate_logfile_proplist(List) -> |
|
|
|
try validate_logfile_proplist(List, []) of |
|
|
|
Res -> |
|
|
|
case proplists:get_value(file, Res) of |
|
|
|
undefined -> |
|
|
|
?INT_LOG(error, "Missing required file option", []), |
|
|
|
false; |
|
|
|
_File -> |
|
|
|
%% merge with the default options |
|
|
|
{ok, DefaultRotationDate} = lager_util:parse_rotation_date_spec(?DEFAULT_ROTATION_DATE), |
|
|
|
lists:keymerge(1, lists:sort(Res), lists:sort([ |
|
|
|
{level, validate_loglevel(?DEFAULT_LOG_LEVEL)}, {date, DefaultRotationDate}, |
|
|
|
{size, ?DEFAULT_ROTATION_SIZE}, {count, ?DEFAULT_ROTATION_COUNT}, |
|
|
|
{sync_on, validate_loglevel(?DEFAULT_SYNC_LEVEL)}, {sync_interval, ?DEFAULT_SYNC_INTERVAL}, |
|
|
|
{sync_size, ?DEFAULT_SYNC_SIZE}, {check_interval, ?DEFAULT_CHECK_INTERVAL}, |
|
|
|
{formatter, lager_default_formatter}, {formatter_config, []} |
|
|
|
])) |
|
|
|
end |
|
|
|
catch |
|
|
|
{bad_config, Msg, Value} -> |
|
|
|
?INT_LOG(error, "~s ~p for file ~p", |
|
|
|
[Msg, Value, proplists:get_value(file, List)]), |
|
|
|
false |
|
|
|
end. |
|
|
|
|
|
|
|
validate_logfile_proplist([], Acc) -> |
|
|
|
Acc; |
|
|
|
validate_logfile_proplist([{file, File}|Tail], Acc) -> |
|
|
|
%% is there any reasonable validation we can do here? |
|
|
|
validate_logfile_proplist(Tail, [{file, File}|Acc]); |
|
|
|
validate_logfile_proplist([{level, Level}|Tail], Acc) -> |
|
|
|
case validate_loglevel(Level) of |
|
|
|
false -> |
|
|
|
throw({bad_config, "Invalid loglevel", Level}); |
|
|
|
Res -> |
|
|
|
validate_logfile_proplist(Tail, [{level, Res}|Acc]) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{size, Size}|Tail], Acc) -> |
|
|
|
case Size of |
|
|
|
S when is_integer(S), S >= 0 -> |
|
|
|
validate_logfile_proplist(Tail, [{size, Size}|Acc]); |
|
|
|
_ -> |
|
|
|
throw({bad_config, "Invalid rotation size", Size}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{count, Count}|Tail], Acc) -> |
|
|
|
case Count of |
|
|
|
C when is_integer(C), C >= 0 -> |
|
|
|
validate_logfile_proplist(Tail, [{count, Count}|Acc]); |
|
|
|
_ -> |
|
|
|
throw({bad_config, "Invalid rotation count", Count}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{date, Date}|Tail], Acc) -> |
|
|
|
case lager_util:parse_rotation_date_spec(Date) of |
|
|
|
{ok, Spec} -> |
|
|
|
validate_logfile_proplist(Tail, [{date, Spec}|Acc]); |
|
|
|
{error, _} when Date == "" -> |
|
|
|
%% legacy config allowed blanks |
|
|
|
validate_logfile_proplist(Tail, Acc); |
|
|
|
{error, _} -> |
|
|
|
throw({bad_config, "Invalid rotation date", Date}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{sync_interval, SyncInt}|Tail], Acc) -> |
|
|
|
case SyncInt of |
|
|
|
Val when is_integer(Val), Val >= 0 -> |
|
|
|
validate_logfile_proplist(Tail, [{sync_interval, Val}|Acc]); |
|
|
|
_ -> |
|
|
|
throw({bad_config, "Invalid sync interval", SyncInt}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{sync_size, SyncSize}|Tail], Acc) -> |
|
|
|
case SyncSize of |
|
|
|
Val when is_integer(Val), Val >= 0 -> |
|
|
|
validate_logfile_proplist(Tail, [{sync_size, Val}|Acc]); |
|
|
|
_ -> |
|
|
|
throw({bad_config, "Invalid sync size", SyncSize}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{check_interval, CheckInt}|Tail], Acc) -> |
|
|
|
case CheckInt of |
|
|
|
Val when is_integer(Val), Val >= 0 -> |
|
|
|
validate_logfile_proplist(Tail, [{check_interval, Val}|Acc]); |
|
|
|
always -> |
|
|
|
validate_logfile_proplist(Tail, [{check_interval, 0}|Acc]); |
|
|
|
_ -> |
|
|
|
throw({bad_config, "Invalid check interval", CheckInt}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{sync_on, Level}|Tail], Acc) -> |
|
|
|
case validate_loglevel(Level) of |
|
|
|
false -> |
|
|
|
throw({bad_config, "Invalid sync on level", Level}); |
|
|
|
Res -> |
|
|
|
validate_logfile_proplist(Tail, [{sync_on, Res}|Acc]) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{formatter, Fmt}|Tail], Acc) -> |
|
|
|
case is_atom(Fmt) of |
|
|
|
true -> |
|
|
|
validate_logfile_proplist(Tail, [{formatter, Fmt}|Acc]); |
|
|
|
false -> |
|
|
|
throw({bad_config, "Invalid formatter module", Fmt}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{formatter_config, FmtCfg}|Tail], Acc) -> |
|
|
|
case is_list(FmtCfg) of |
|
|
|
true -> |
|
|
|
validate_logfile_proplist(Tail, [{formatter_config, FmtCfg}|Acc]); |
|
|
|
false -> |
|
|
|
throw({bad_config, "Invalid formatter config", FmtCfg}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([Other|_Tail], _Acc) -> |
|
|
|
throw({bad_config, "Invalid option", Other}). |
|
|
|
|
|
|
|
schedule_rotation(_, undefined) -> |
|
|
|
ok; |
|
|
@ -244,16 +356,22 @@ get_loglevel_test() -> |
|
|
|
?assertEqual(Level2, lager_util:config_to_mask(warning)). |
|
|
|
|
|
|
|
rotation_test() -> |
|
|
|
{ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", true), |
|
|
|
SyncLevel = validate_loglevel(?DEFAULT_SYNC_LEVEL), |
|
|
|
SyncSize = ?DEFAULT_SYNC_SIZE, |
|
|
|
SyncInterval = ?DEFAULT_SYNC_INTERVAL, |
|
|
|
CheckInterval = 0, %% hard to test delayed mode |
|
|
|
{ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", {SyncSize, SyncInterval}), |
|
|
|
State0 = #state{name="test.log", level=?DEBUG, fd=FD, inode=Inode, sync_on=SyncLevel, |
|
|
|
sync_size=SyncSize, sync_interval=SyncInterval, check_interval=CheckInterval}, |
|
|
|
?assertMatch(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, |
|
|
|
write(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, 0, "hello world")), |
|
|
|
write(State0, os:timestamp(), ?DEBUG, "hello world")), |
|
|
|
file:delete("test.log"), |
|
|
|
Result = write(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, 0, "hello world"), |
|
|
|
Result = write(State0, os:timestamp(), ?DEBUG, "hello world"), |
|
|
|
%% assert file has changed |
|
|
|
?assert(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode} =/= Result), |
|
|
|
?assertMatch(#state{name="test.log", level=?DEBUG}, Result), |
|
|
|
file:rename("test.log", "test.log.1"), |
|
|
|
Result2 = write(Result, 0, "hello world"), |
|
|
|
Result2 = write(Result, os:timestamp(), ?DEBUG, "hello world"), |
|
|
|
%% assert file has changed |
|
|
|
?assert(Result =/= Result2), |
|
|
|
?assertMatch(#state{name="test.log", level=?DEBUG}, Result2), |
|
|
@ -271,13 +389,15 @@ filesystem_test_() -> |
|
|
|
end, |
|
|
|
fun(_) -> |
|
|
|
file:delete("test.log"), |
|
|
|
file:delete("test.log.0"), |
|
|
|
file:delete("test.log.1"), |
|
|
|
application:stop(lager), |
|
|
|
error_logger:tty(true) |
|
|
|
end, |
|
|
|
[ |
|
|
|
{"under normal circumstances, file should be opened", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]), |
|
|
|
lager:log(error, self(), "Test message"), |
|
|
|
{ok, Bin} = file:read_file("test.log"), |
|
|
|
Pid = pid_to_list(self()), |
|
|
@ -288,7 +408,7 @@ filesystem_test_() -> |
|
|
|
fun() -> |
|
|
|
{ok, FInfo} = file:read_file_info("test.log"), |
|
|
|
file:write_file_info("test.log", FInfo#file_info{mode = 0}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info, 10*1024*1024, "$D0", 5}), |
|
|
|
?assertEqual(1, lager_test_backend:count()), |
|
|
|
{_Level, _Time,Message,_Metadata} = lager_test_backend:pop(), |
|
|
|
?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)) |
|
|
@ -296,7 +416,7 @@ filesystem_test_() -> |
|
|
|
}, |
|
|
|
{"file that becomes unavailable at runtime should trigger an error message", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}]), |
|
|
|
?assertEqual(0, lager_test_backend:count()), |
|
|
|
lager:log(error, self(), "Test message"), |
|
|
|
?assertEqual(1, lager_test_backend:count()), |
|
|
@ -317,7 +437,7 @@ filesystem_test_() -> |
|
|
|
{ok, FInfo} = file:read_file_info("test.log"), |
|
|
|
OldPerms = FInfo#file_info.mode, |
|
|
|
file:write_file_info("test.log", FInfo#file_info{mode = 0}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"},{check_interval, 0}]), |
|
|
|
?assertEqual(1, lager_test_backend:count()), |
|
|
|
{_Level, _Time, Message,_Metadata} = lager_test_backend:pop(), |
|
|
|
?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)), |
|
|
@ -330,7 +450,7 @@ filesystem_test_() -> |
|
|
|
}, |
|
|
|
{"external logfile rotation/deletion should be handled", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}]), |
|
|
|
?assertEqual(0, lager_test_backend:count()), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
?assertEqual(1, lager_test_backend:count()), |
|
|
@ -346,6 +466,68 @@ filesystem_test_() -> |
|
|
|
?assertMatch([_, _, "[error]", Pid, "Test message3\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}])) |
|
|
|
end |
|
|
|
}, |
|
|
|
{"internal size rotation should work", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}, {size, 10}]), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
?assert(filelib:is_regular("test.log.0")) |
|
|
|
end |
|
|
|
}, |
|
|
|
{"internal time rotation should work", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 1000}]), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
whereis(lager_event) ! {rotate, "test.log"}, |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
?assert(filelib:is_regular("test.log.0")) |
|
|
|
end |
|
|
|
}, |
|
|
|
{"sync_on option should work", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "=info"}, {check_interval, 5000}, {sync_interval, 5000}]), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
?assertEqual({ok, <<>>}, file:read_file("test.log")), |
|
|
|
lager:log(info, self(), "Test message1"), |
|
|
|
{ok, Bin} = file:read_file("test.log"), |
|
|
|
?assert(<<>> /= Bin) |
|
|
|
end |
|
|
|
}, |
|
|
|
{"sync_on none option should work (also tests sync_interval)", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5000}, {sync_interval, 1000}]), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
lager:log(error, self(), "Test message1"), |
|
|
|
?assertEqual({ok, <<>>}, file:read_file("test.log")), |
|
|
|
lager:log(info, self(), "Test message1"), |
|
|
|
?assertEqual({ok, <<>>}, file:read_file("test.log")), |
|
|
|
timer:sleep(1000), |
|
|
|
{ok, Bin} = file:read_file("test.log"), |
|
|
|
?assert(<<>> /= Bin) |
|
|
|
end |
|
|
|
}, |
|
|
|
{"sync_size option should work", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5001}, {sync_size, 640}, {sync_interval, 5001}]), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
?assertEqual({ok, <<>>}, file:read_file("test.log")), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
?assertEqual({ok, <<>>}, file:read_file("test.log")), |
|
|
|
%% now we've written enough bytes |
|
|
|
lager:log(error, self(), "Test messageis64bytes"), |
|
|
|
{ok, Bin} = file:read_file("test.log"), |
|
|
|
?assert(<<>> /= Bin) |
|
|
|
end |
|
|
|
}, |
|
|
|
{"runtime level changes", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, {"test.log", info}), |
|
|
@ -365,7 +547,7 @@ filesystem_test_() -> |
|
|
|
}, |
|
|
|
{"invalid runtime level changes", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info, 10*1024*1024, "$D0", 5}, {lager_default_formatter}]), |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, {"test3.log", info}), |
|
|
|
?assertEqual({error, bad_module}, lager:set_loglevel(lager_file_backend, "test.log", warning)) |
|
|
|
end |
|
|
@ -389,7 +571,7 @@ filesystem_test_() -> |
|
|
|
{"tracing should not duplicate messages", |
|
|
|
fun() -> |
|
|
|
gen_event:add_handler(lager_event, lager_file_backend, |
|
|
|
{"test.log", critical}), |
|
|
|
[{file, "test.log"}, {level, critical}, {check_interval, always}]), |
|
|
|
lager:critical("Test message"), |
|
|
|
{ok, Bin1} = file:read_file("test.log"), |
|
|
|
?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin1, " ", [{return, list}, {parts, 5}])), |
|
|
@ -449,5 +631,62 @@ formatting_test_() -> |
|
|
|
} |
|
|
|
]}. |
|
|
|
|
|
|
|
config_validation_test_() -> |
|
|
|
[ |
|
|
|
{"missing file", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{level, info},{size, 10}])) |
|
|
|
}, |
|
|
|
{"bad level", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {level, blah},{size, 10}])) |
|
|
|
}, |
|
|
|
{"bad size", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {size, infinity}])) |
|
|
|
}, |
|
|
|
{"bad count", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {count, infinity}])) |
|
|
|
}, |
|
|
|
{"bad date", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {date, "midnight"}])) |
|
|
|
}, |
|
|
|
{"blank date is ok", |
|
|
|
?_assertMatch([_|_], |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {date, ""}])) |
|
|
|
}, |
|
|
|
{"bad sync_interval", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {sync_interval, infinity}])) |
|
|
|
}, |
|
|
|
{"bad sync_size", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {sync_size, infinity}])) |
|
|
|
}, |
|
|
|
{"bad check_interval", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {check_interval, infinity}])) |
|
|
|
}, |
|
|
|
{"bad sync_on level", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {sync_on, infinity}])) |
|
|
|
}, |
|
|
|
{"bad formatter module", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {formatter, "io:format"}])) |
|
|
|
}, |
|
|
|
{"bad formatter config", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {formatter_config, blah}])) |
|
|
|
}, |
|
|
|
{"unknown option", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {rhubarb, spicy}])) |
|
|
|
} |
|
|
|
]. |
|
|
|
|
|
|
|
|
|
|
|
-endif. |
|
|
|
|