|
|
@ -23,25 +23,15 @@ |
|
|
|
|
|
|
|
-export([ |
|
|
|
init/1 |
|
|
|
, handle_call/2 |
|
|
|
, handle_event/2 |
|
|
|
, handle_info/2 |
|
|
|
, handleCall/2 |
|
|
|
, handleEvent/2 |
|
|
|
, handleInfo/2 |
|
|
|
, terminate/2 |
|
|
|
, code_change/3 |
|
|
|
]). |
|
|
|
|
|
|
|
-define(DEFAULT_LOG_LEVEL, info). |
|
|
|
-define(DEFAULT_ROTATION_SIZE, 10485760). %% 10mb |
|
|
|
-define(DEFAULT_ROTATION_DATE, "$D0"). %% midnight |
|
|
|
-define(DEFAULT_ROTATION_COUNT, 5). |
|
|
|
-define(DEFAULT_ROTATION_MOD, lager_rotator_default). |
|
|
|
-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(), |
|
|
|
fileName :: string(), |
|
|
|
level :: {'mask', integer()}, |
|
|
|
fd :: file:io_device() | undefined, |
|
|
|
inode :: integer() | undefined, |
|
|
@ -53,13 +43,13 @@ |
|
|
|
rotator = lager_util :: atom(), |
|
|
|
shaper :: rumShaper(), |
|
|
|
formatter :: atom(), |
|
|
|
formatter_config :: any(), |
|
|
|
formatterConfig :: any(), |
|
|
|
sync_on :: {'mask', integer()}, |
|
|
|
check_interval = ?DEFAULT_CHECK_INTERVAL :: non_neg_integer(), |
|
|
|
sync_interval = ?DEFAULT_SYNC_INTERVAL :: non_neg_integer(), |
|
|
|
sync_size = ?DEFAULT_SYNC_SIZE :: non_neg_integer(), |
|
|
|
last_check = os:timestamp() :: erlang:timestamp(), |
|
|
|
os_type :: atom() |
|
|
|
checkInterval = ?RumDefCheckInterval :: non_neg_integer(), |
|
|
|
syncInterval = ?RumDefSyncInterval :: non_neg_integer(), |
|
|
|
sync_size = ?RumDefSyncSize :: non_neg_integer(), |
|
|
|
lastCheck = os:timestamp() :: erlang:timestamp(), |
|
|
|
osType :: atom() |
|
|
|
}). |
|
|
|
|
|
|
|
-type option() :: |
|
|
@ -80,110 +70,108 @@ |
|
|
|
{formatter_config, term()}. |
|
|
|
|
|
|
|
-spec init([option(), ...]) -> {ok, #state{}} | {error, {fatal, bad_config}}. |
|
|
|
init({FileName, LogLevel}) when is_list(FileName), is_atom(LogLevel) -> |
|
|
|
%% backwards compatibility hack |
|
|
|
init([{file, FileName}, {level, LogLevel}]); |
|
|
|
init({FileName, LogLevel, Size, Date, Count}) when is_list(FileName), is_atom(LogLevel) -> |
|
|
|
%% backwards compatibility 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 compatibility hack |
|
|
|
init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}, {formatter, Formatter}, {formatter_config, FormatterConfig}]); |
|
|
|
init([LogFile, {Formatter}]) -> |
|
|
|
%% backwards compatibility hack |
|
|
|
init([LogFile, {Formatter, []}]); |
|
|
|
init([{FileName, LogLevel}, {Formatter, FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) -> |
|
|
|
%% backwards compatibility 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 |
|
|
|
{error, {fatal, bad_config}}; |
|
|
|
Config -> |
|
|
|
%% probabably a better way to do this, but whatever |
|
|
|
[RelName, Level, Date, Size, Count, Rotator, HighWaterMark, Flush, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] = |
|
|
|
[proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, rotator, high_water_mark, flush_queue, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]], |
|
|
|
FlushThr = proplists:get_value(flush_threshold, Config, 0), |
|
|
|
Name = rumUtil:parsePath(RelName), |
|
|
|
schedule_rotation(Name, Date), |
|
|
|
Shaper = rumUtil:maybe_flush(Flush, #rumShaper{hwm = HighWaterMark, flushThreshold = FlushThr, id = Name}), |
|
|
|
State0 = #state{name = Name, level = Level, size = Size, date = Date, count = Count, rotator = Rotator, |
|
|
|
shaper = Shaper, formatter = Formatter, formatter_config = FormatterConfig, |
|
|
|
sync_on = SyncOn, sync_interval = SyncInterval, sync_size = SyncSize, check_interval = CheckInterval}, |
|
|
|
State = case Rotator:createLogfile(Name, {SyncSize, SyncInterval}) of |
|
|
|
{ok, {FD, Inode, Ctime, _Size}} -> |
|
|
|
State0#state{fd = FD, inode = Inode, ctime = Ctime}; |
|
|
|
{error, Reason} -> |
|
|
|
?INT_LOG(error, "Failed to open log file ~ts with error ~s", [Name, file:format_error(Reason)]), |
|
|
|
State0#state{flap = true} |
|
|
|
end, |
|
|
|
{ok, State} |
|
|
|
init(Opts) -> |
|
|
|
true = checkOpts(Opts, false), |
|
|
|
RelName = rumUtil:get_opt(file, Opts, undefined), |
|
|
|
CfgLevel = rumUtil:get_opt(level, Opts, ?RumDefLogLevel), |
|
|
|
CfgDate = rumUtil:get_opt(date, Opts, ?RumDefRotateDate), |
|
|
|
Size = rumUtil:get_opt(size, Opts, ?RumDefRotateSize), |
|
|
|
Count = rumUtil:get_opt(count, Opts, ?RumDefRotateCnt), |
|
|
|
Rotator = rumUtil:get_opt(rotator, Opts, ?RumDefRotateMod), |
|
|
|
HighWaterMark = rumUtil:get_opt(high_water_mark, Opts, ?RumDefCheckHWM), |
|
|
|
Flush = rumUtil:get_opt(flush_queue, Opts, ?RumDefFlushQueue), |
|
|
|
FlushThr = rumUtil:get_opt(flush_threshold, Opts, ?RumDefFlushThreshold), |
|
|
|
SyncInterval = rumUtil:get_opt(sync_interval, Opts, ?RumDefSyncInterval), |
|
|
|
CfgCheckInterval = rumUtil:get_opt(check_interval, Opts, ?RumDefCheckInterval), |
|
|
|
SyncSize = rumUtil:get_opt(sync_size, Opts, ?RumDefSyncSize), |
|
|
|
CfgSyncOn = rumUtil:get_opt(sync_on, Opts, ?RumDefSyncLevel), |
|
|
|
Formatter = rumUtil:get_opt(formatter, Opts, ?RumDefFormatter), |
|
|
|
FormatterConfig = rumUtil:get_opt(formatter_config, Opts, ?RumDefFormatterCfg), |
|
|
|
|
|
|
|
%% 需要二次转换的配置在这里处理 |
|
|
|
Level = validate_loglevel(CfgLevel), |
|
|
|
SyncOn = validate_loglevel(CfgSyncOn), |
|
|
|
CheckInterval = ?IIF(CfgCheckInterval == always, 0, CfgCheckInterval), |
|
|
|
{ok, Date} = rumUtil:parseRotateSpec(CfgDate), |
|
|
|
|
|
|
|
FileName = rumUtil:parsePath(RelName), |
|
|
|
scheduleRotation(Date, FileName), |
|
|
|
Shaper = rumUtil:maybe_flush(Flush, #rumShaper{hwm = HighWaterMark, flushThreshold = FlushThr, id = FileName}), |
|
|
|
TemState = #state{ |
|
|
|
fileName = FileName, level = Level, size = Size, date = Date |
|
|
|
, count = Count, rotator = Rotator, shaper = Shaper |
|
|
|
, formatter = Formatter, formatterConfig = FormatterConfig |
|
|
|
, sync_on = SyncOn, syncInterval = SyncInterval |
|
|
|
, sync_size = SyncSize, checkInterval = CheckInterval |
|
|
|
}, |
|
|
|
case Rotator:createLogfile(FileName, {SyncSize, SyncInterval}) of |
|
|
|
{ok, Fd, Inode, Ctime, _Size} -> |
|
|
|
{ok, TemState#state{fd = Fd, inode = Inode, ctime = Ctime}}; |
|
|
|
{error, Reason} -> |
|
|
|
?INT_LOG(error, "Failed to open log file ~ts with error ~s", [FileName, file:format_error(Reason)]), |
|
|
|
{ok, TemState#state{flap = true}} |
|
|
|
end. |
|
|
|
|
|
|
|
%% @private |
|
|
|
handle_call({mSetLogLevel, Level}, #state{name = Ident} = State) -> |
|
|
|
handleCall(mGetLogLevel, #state{level = Level} = State) -> |
|
|
|
{reply, Level, State}; |
|
|
|
handleCall({mSetLogLevel, Level}, #state{fileName = Ident} = State) -> |
|
|
|
case validate_loglevel(Level) of |
|
|
|
false -> |
|
|
|
{ok, {error, bad_loglevel}, State}; |
|
|
|
{reply, {error, bad_loglevel}, State}; |
|
|
|
Levels -> |
|
|
|
?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]), |
|
|
|
{ok, ok, State#state{level = Levels}} |
|
|
|
{reply, ok, State#state{level = Levels}} |
|
|
|
end; |
|
|
|
handle_call(mGetLogLevel, #state{level = Level} = State) -> |
|
|
|
{ok, Level, State}; |
|
|
|
handle_call({set_loghwm, Hwm}, #state{shaper = Shaper, name = Name} = State) -> |
|
|
|
case validate_logfile_proplist([{file, Name}, {high_water_mark, Hwm}]) of |
|
|
|
|
|
|
|
handleCall({mSetLogHwm, Hwm}, #state{shaper = Shaper, fileName = FileName} = State) -> |
|
|
|
case checkOpts([{high_water_mark, Hwm}], true) of |
|
|
|
false -> |
|
|
|
{ok, {error, bad_log_hwm}, State}; |
|
|
|
{reply, {error, bad_log_hwm}, State}; |
|
|
|
_ -> |
|
|
|
NewShaper = Shaper#rumShaper{hwm = Hwm}, |
|
|
|
?INT_LOG(notice, "Changed loghwm of ~ts to ~p", [Name, Hwm]), |
|
|
|
{ok, {last_loghwm, Shaper#rumShaper.hwm}, State#state{shaper = NewShaper}} |
|
|
|
?INT_LOG(notice, "Changed loghwm of ~ts to ~p", [FileName, Hwm]), |
|
|
|
{reply, {last_loghwm, Shaper#rumShaper.hwm}, State#state{shaper = NewShaper}} |
|
|
|
end; |
|
|
|
handle_call(mRotate, State = #state{name = File}) -> |
|
|
|
{ok, NewState} = handle_info({mRotate, File}, State), |
|
|
|
{ok, ok, NewState}; |
|
|
|
handle_call(_Request, State) -> |
|
|
|
{ok, ok, State}. |
|
|
|
|
|
|
|
%% @private |
|
|
|
handle_event({mWriteLog, Message}, |
|
|
|
#state{name = Name, level = L, shaper = Shaper, formatter = Formatter, formatter_config = FormatConfig} = State) -> |
|
|
|
case rumUtil:is_loggable(Message, L, {lager_file_backend, Name}) of |
|
|
|
handleCall(mRotate, State = #state{fileName = File}) -> |
|
|
|
{ok, NewState} = handleInfo({mRotate, File}, State), |
|
|
|
{reply, ok, NewState}; |
|
|
|
handleCall(_Msg, State) -> |
|
|
|
?ERR("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]), |
|
|
|
{reply, ok, State}. |
|
|
|
|
|
|
|
handleEvent({mWriteLog, Message}, #state{fileName = FileName, level = L, shaper = Shaper, formatter = Formatter, formatterConfig = FormatConfig} = State) -> |
|
|
|
case rumUtil:is_loggable(Message, L, {rumBkdFile, FileName}) of |
|
|
|
true -> |
|
|
|
case rumUtil:check_hwm(Shaper) of |
|
|
|
{true, Drop, #rumShaper{hwm = Hwm} = NewShaper} -> |
|
|
|
NewState = case Drop > 0 of |
|
|
|
true -> |
|
|
|
Report = io_lib:format( |
|
|
|
"lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", |
|
|
|
[Drop, Hwm]), |
|
|
|
ReportMsg = rumMsg:new(Report, warning, [], []), |
|
|
|
write(State, rumMsg:timestamp(ReportMsg), |
|
|
|
rumMsg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig)); |
|
|
|
false -> |
|
|
|
State |
|
|
|
end, |
|
|
|
NewState = |
|
|
|
case Drop > 0 of |
|
|
|
true -> |
|
|
|
Report = io_lib:format("lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", [Drop, Hwm]), |
|
|
|
ReportMsg = rumMsg:new(Report, warning, [], []), |
|
|
|
write(State, rumMsg:timestamp(ReportMsg), rumMsg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig)); |
|
|
|
_ -> |
|
|
|
State |
|
|
|
end, |
|
|
|
{ok, write(NewState#state{shaper = NewShaper}, |
|
|
|
rumMsg:timestamp(Message), rumMsg:severity_as_int(Message), |
|
|
|
Formatter:format(Message, FormatConfig))}; |
|
|
|
{false, _, #rumShaper{dropped = D} = NewShaper} -> |
|
|
|
{ok, State#state{shaper = NewShaper#rumShaper{dropped = D + 1}}} |
|
|
|
end; |
|
|
|
false -> |
|
|
|
{okpan>, State} |
|
|
|
_ -> |
|
|
|
kpS |
|
|
|
end; |
|
|
|
handle_event(_Event, State) -> |
|
|
|
{ok, State}. |
|
|
|
handleEvent(_Msg, _State) -> |
|
|
|
?ERR("~p event receive unexpect msg ~p ~n ", [?MODULE, _Msg]), |
|
|
|
kpS. |
|
|
|
|
|
|
|
%% @private |
|
|
|
handle_info({mRotate, File}, #state{name = File, count = Count, date = Date, rotator = Rotator} = State0) -> |
|
|
|
handleInfo({mRotate, File}, #state{fileName = File, count = Count, date = Date, rotator = Rotator} = State0) -> |
|
|
|
State1 = close_file(State0), |
|
|
|
_ = Rotator:rotateLogfile(File, Count), |
|
|
|
schedule_rotation(File, Date), |
|
|
|
scheduleRotation(File, Date), |
|
|
|
{ok, State1}; |
|
|
|
handle_info({shaper_expired, Name}, #state{shaper = Shaper, name = Name, formatter = Formatter, formatter_config = FormatConfig} = State) -> |
|
|
|
handleInfo({shaper_expired, Name}, #state{shaper = Shaper, fileName = Name, formatter = Formatter, formatterConfig = FormatConfig} = State) -> |
|
|
|
_ = case Shaper#rumShaper.dropped of |
|
|
|
0 -> |
|
|
|
ok; |
|
|
@ -196,16 +184,14 @@ handle_info({shaper_expired, Name}, #state{shaper = Shaper, name = Name, formatt |
|
|
|
rumMsg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig)) |
|
|
|
end, |
|
|
|
{ok, State#state{shaper = Shaper#rumShaper{dropped = 0, mps = 0, lastTime = os:timestamp()}}}; |
|
|
|
handle_info(_Info, State) -> |
|
|
|
handleInfo(_Info, State) -> |
|
|
|
{ok, State}. |
|
|
|
|
|
|
|
%% @private |
|
|
|
terminate(_Reason, State) -> |
|
|
|
%% leaving this function call unmatched makes dialyzer cranky |
|
|
|
_ = close_file(State), |
|
|
|
ok. |
|
|
|
|
|
|
|
%% @private |
|
|
|
code_change(_OldVsn, State, _Extra) -> |
|
|
|
{ok, State}. |
|
|
|
|
|
|
@ -226,14 +212,14 @@ config_to_id(Config) -> |
|
|
|
{?MODULE, File} |
|
|
|
end. |
|
|
|
|
|
|
|
write(#state{name = Name, fd = FD, |
|
|
|
write(#state{fileName = Name, fd = FD, |
|
|
|
inode = Inode, ctime = Ctime, |
|
|
|
flap = Flap, size = RotSize, |
|
|
|
count = Count, rotator = Rotator} = State0, Timestamp, Level, Msg) -> |
|
|
|
case write_should_check(State0, Timestamp) of |
|
|
|
true -> |
|
|
|
%% need to check for rotation |
|
|
|
Buffer = {State0#state.sync_size, State0#state.sync_interval}, |
|
|
|
Buffer = {State0#state.sync_size, State0#state.syncInterval}, |
|
|
|
case Rotator:ensureLogfile(Name, FD, Inode, Ctime, Buffer) of |
|
|
|
{ok, {_FD, _Inode, _Ctime, Size}} when RotSize > 0, Size > RotSize -> |
|
|
|
State1 = close_file(State0), |
|
|
@ -252,7 +238,7 @@ write(#state{name = Name, fd = FD, |
|
|
|
end; |
|
|
|
{ok, {NewFD, NewInode, NewCtime, _Size}} -> |
|
|
|
%% update our last check and try again |
|
|
|
State1 = State0#state{last_check = Timestamp, fd = NewFD, inode = NewInode, ctime = NewCtime}, |
|
|
|
State1 = State0#state{lastCheck = Timestamp, fd = NewFD, inode = NewInode, ctime = NewCtime}, |
|
|
|
do_write(State1, Level, Msg); |
|
|
|
{error, Reason} -> |
|
|
|
case Flap of |
|
|
@ -269,8 +255,8 @@ write(#state{name = Name, fd = FD, |
|
|
|
|
|
|
|
write_should_check(#state{fd = undefined}, _Timestamp) -> |
|
|
|
true; |
|
|
|
write_should_check(#state{last_check = LastCheck0, check_interval = CheckInterval, |
|
|
|
name = Name, inode = Inode0, ctime = Ctime0}, Timestamp) -> |
|
|
|
write_should_check(#state{lastCheck = LastCheck0, checkInterval = CheckInterval, |
|
|
|
fileName = Name, inode = Inode0, ctime = Ctime0}, Timestamp) -> |
|
|
|
LastCheck1 = timer:now_diff(Timestamp, LastCheck0) div 1000, |
|
|
|
case LastCheck1 >= CheckInterval of |
|
|
|
true -> |
|
|
@ -282,7 +268,7 @@ write_should_check(#state{last_check = LastCheck0, check_interval = CheckInterva |
|
|
|
Result |
|
|
|
end. |
|
|
|
|
|
|
|
do_write(#state{fd = FD, name = Name, flap = Flap} = State, Level, Msg) -> |
|
|
|
do_write(#state{fd = FD, fileName = Name, flap = Flap} = State, Level, Msg) -> |
|
|
|
%% delayed_write doesn't report errors |
|
|
|
_ = file:write(FD, unicode:characters_to_binary(Msg)), |
|
|
|
{mask, SyncLevel} = State#state.sync_on, |
|
|
@ -313,146 +299,48 @@ 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} = rumUtil:parseRotateSpec(?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}, |
|
|
|
{rotator, ?DEFAULT_ROTATION_MOD}, |
|
|
|
{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 ~tp", |
|
|
|
[Msg, Value, proplists:get_value(file, List)]), |
|
|
|
false |
|
|
|
end. |
|
|
|
|
|
|
|
validate_logfile_proplist([], Acc) -> |
|
|
|
Acc; |
|
|
|
validate_logfile_proplist([{file, File} | Tail], Acc) -> |
|
|
|
checkOpts([], IsFile) -> |
|
|
|
?IIF(IsFile, true, {error, no_file_name}); |
|
|
|
checkOpts([{file, _File} | Tail], _IsFile) -> |
|
|
|
%% 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([{rotator, Rotator} | Tail], Acc) -> |
|
|
|
case is_atom(Rotator) of |
|
|
|
true -> |
|
|
|
validate_logfile_proplist(Tail, [{rotator, Rotator} | Acc]); |
|
|
|
false -> |
|
|
|
throw({bad_config, "Invalid rotation module", Rotator}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{high_water_mark, HighWaterMark} | Tail], Acc) -> |
|
|
|
case HighWaterMark of |
|
|
|
Hwm when is_integer(Hwm), Hwm >= 0 -> |
|
|
|
validate_logfile_proplist(Tail, [{high_water_mark, Hwm} | Acc]); |
|
|
|
_ -> |
|
|
|
throw({bad_config, "Invalid high water mark", HighWaterMark}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{date, Date} | Tail], Acc) -> |
|
|
|
case rumUtil:parseRotateSpec(Date) of |
|
|
|
{ok, Spec} -> |
|
|
|
validate_logfile_proplist(Tail, [{date, Spec} | Acc]); |
|
|
|
{error, _} when Date == "" -> |
|
|
|
%% legacy config allowed blanks |
|
|
|
validate_logfile_proplist(Tail, [{date, undefined} | 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([{flush_queue, FlushCfg} | Tail], Acc) -> |
|
|
|
case is_boolean(FlushCfg) of |
|
|
|
true -> |
|
|
|
validate_logfile_proplist(Tail, [{flush_queue, FlushCfg} | Acc]); |
|
|
|
false -> |
|
|
|
throw({bad_config, "Invalid queue flush flag", FlushCfg}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([{flush_threshold, Thr} | Tail], Acc) -> |
|
|
|
case Thr of |
|
|
|
_ when is_integer(Thr), Thr >= 0 -> |
|
|
|
validate_logfile_proplist(Tail, [{flush_threshold, Thr} | Acc]); |
|
|
|
_ -> |
|
|
|
throw({bad_config, "Invalid queue flush threshold", Thr}) |
|
|
|
end; |
|
|
|
validate_logfile_proplist([Other | _Tail], _Acc) -> |
|
|
|
throw({bad_config, "Invalid option", Other}). |
|
|
|
|
|
|
|
schedule_rotation(_, undefined) -> |
|
|
|
checkOpts(Tail, true); |
|
|
|
checkOpts([{level, Level} | Tail], IsFile) -> |
|
|
|
?IIF(validate_loglevel(Level), checkOpts(Tail, IsFile), ({error, {invalid_log_level, Level}})); |
|
|
|
checkOpts([{size, Size} | Tail], IsFile) when is_integer(Size), Size >= 0 -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{count, Count} | Tail], IsFile) when is_integer(Count), Count >= 0 -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{rotator, Rotator} | Tail], IsFile) when is_atom(Rotator) -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{high_water_mark, HighWaterMark} | Tail], IsFile) when is_integer(HighWaterMark), HighWaterMark >= 0 -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{date, _Date} | Tail], IsFile) -> |
|
|
|
%% IMY-todo 使用前需要转换一下 rumUtil:parseRotateSpec(_Date) |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{sync_interval, SyncInt} | Tail], IsFile) when is_integer(SyncInt), SyncInt >= 0 -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{sync_size, SyncSize} | Tail], IsFile) when is_integer(SyncSize), SyncSize >= 0 -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{check_interval, CheckInt} | Tail], IsFile) when is_integer(CheckInt), CheckInt >= 0; CheckInt == always -> |
|
|
|
%% IMY-todo 使用前转换一下 always ->{check_interval, 0}; |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{sync_on, _Level} | Tail], IsFile) -> |
|
|
|
%% IMY-todo 使用前转换一下 validate_loglevel(Level) |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{formatter, Fmt} | Tail], IsFile) when is_atom(Fmt) -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{formatter_config, FmtCfg} | Tail], IsFile) when is_list(FmtCfg) -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{flush_queue, FlushCfg} | Tail], IsFile) when is_boolean(FlushCfg) -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([{flush_threshold, Thr} | Tail], IsFile) when is_integer(Thr), Thr >= 0 -> |
|
|
|
checkOpts(Tail, IsFile); |
|
|
|
checkOpts([Other | _Tail], _IsFile) -> |
|
|
|
{error, {invalid_opt, Other}}. |
|
|
|
|
|
|
|
scheduleRotation(undefined, _FileName) -> |
|
|
|
ok; |
|
|
|
schedule_rotation(Name, Date) -> |
|
|
|
scheduleRotation(Date, Name) -> |
|
|
|
erlang:send_after(rumUtil:calcNextRotateMs(Date), self(), {mRotate, Name}), |
|
|
|
ok. |
|
|
|
|
|
|
@ -469,37 +357,37 @@ close_file(#state{fd = FD} = State) -> |
|
|
|
-ifdef(TEST). |
|
|
|
|
|
|
|
get_loglevel_test() -> |
|
|
|
{ok, Level, _} = handle_call(mGetLogLevel, |
|
|
|
#state{name = "bar", level = rumUtil:config_to_mask(info), fd = 0, inode = 0, ctime = undefined}), |
|
|
|
{ok, Level, _} = handleCall(mGetLogLevel, |
|
|
|
#state{fileName = "bar", level = rumUtil:config_to_mask(info), fd = 0, inode = 0, ctime = undefined}), |
|
|
|
?assertEqual(Level, rumUtil:config_to_mask(info)), |
|
|
|
{ok, Level2, _} = handle_call(mGetLogLevel, |
|
|
|
#state{name = "foo", level = rumUtil:config_to_mask(warning), fd = 0, inode = 0, ctime = undefined}), |
|
|
|
{ok, Level2, _} = handleCall(mGetLogLevel, |
|
|
|
#state{fileName = "foo", level = rumUtil:config_to_mask(warning), fd = 0, inode = 0, ctime = undefined}), |
|
|
|
?assertEqual(Level2, rumUtil:config_to_mask(warning)). |
|
|
|
|
|
|
|
rotation_test_() -> |
|
|
|
{foreach, |
|
|
|
fun() -> |
|
|
|
SyncLevel = validate_loglevel(?DEFAULT_SYNC_LEVEL), |
|
|
|
SyncSize = ?DEFAULT_SYNC_SIZE, |
|
|
|
SyncInterval = ?DEFAULT_SYNC_INTERVAL, |
|
|
|
Rotator = ?DEFAULT_ROTATION_MOD, |
|
|
|
SyncLevel = validate_loglevel(?RumDefSyncLevel), |
|
|
|
SyncSize = ?RumDefSyncSize, |
|
|
|
SyncInterval = ?RumDefSyncInterval, |
|
|
|
Rotator = ?RumDefRotateMod, |
|
|
|
CheckInterval = 0, %% hard to test delayed mode |
|
|
|
{ok, TestDir} = rumUtil:create_test_dir(), |
|
|
|
TestLog = filename:join(TestDir, "test.log"), |
|
|
|
{OsType, _} = os:type(), |
|
|
|
#state{name = TestLog, |
|
|
|
#state{fileName = TestLog, |
|
|
|
level = ?DEBUG, |
|
|
|
sync_on = SyncLevel, |
|
|
|
sync_size = SyncSize, |
|
|
|
sync_interval = SyncInterval, |
|
|
|
check_interval = CheckInterval, |
|
|
|
syncInterval = SyncInterval, |
|
|
|
checkInterval = CheckInterval, |
|
|
|
rotator = Rotator, |
|
|
|
os_type = OsType} |
|
|
|
osType = OsType} |
|
|
|
end, |
|
|
|
fun(#state{}) -> |
|
|
|
ok = rumUtil:delete_test_dir() |
|
|
|
end, [ |
|
|
|
fun(DefaultState = #state{name = TestLog, os_type = OsType, sync_size = SyncSize, sync_interval = SyncInterval, rotator = Rotator}) -> |
|
|
|
fun(DefaultState = #state{fileName = TestLog, osType = OsType, sync_size = SyncSize, syncInterval = SyncInterval, rotator = Rotator}) -> |
|
|
|
{"External rotation should work", |
|
|
|
fun() -> |
|
|
|
case OsType of |
|
|
@ -511,23 +399,23 @@ rotation_test_() -> |
|
|
|
{ok, FD, Inode, Ctime, _Size} = Rotator:openLogfile(TestLog, {SyncSize, SyncInterval}), |
|
|
|
State0 = DefaultState#state{fd = FD, inode = Inode, ctime = Ctime}, |
|
|
|
State1 = write(State0, os:timestamp(), ?DEBUG, "hello world"), |
|
|
|
?assertMatch(#state{name = TestLog, level = ?DEBUG, fd = FD, inode = Inode, ctime = Ctime}, State1), |
|
|
|
?assertMatch(#state{fileName = TestLog, level = ?DEBUG, fd = FD, inode = Inode, ctime = Ctime}, State1), |
|
|
|
?assertEqual(ok, file:delete(TestLog)), |
|
|
|
State2 = write(State0, os:timestamp(), ?DEBUG, "hello world"), |
|
|
|
%% assert file has changed |
|
|
|
ExpState1 = #state{name = TestLog, level = ?DEBUG, fd = FD, inode = Inode, ctime = Ctime}, |
|
|
|
ExpState1 = #state{fileName = TestLog, level = ?DEBUG, fd = FD, inode = Inode, ctime = Ctime}, |
|
|
|
?assertNotEqual(ExpState1, State2), |
|
|
|
?assertMatch(#state{name = TestLog, level = ?DEBUG}, State2), |
|
|
|
?assertMatch(#state{fileName = TestLog, level = ?DEBUG}, State2), |
|
|
|
?assertEqual(ok, file:rename(TestLog, TestLog ++ ".1")), |
|
|
|
State3 = write(State2, os:timestamp(), ?DEBUG, "hello world"), |
|
|
|
%% assert file has changed |
|
|
|
?assertNotEqual(State3, State2), |
|
|
|
?assertMatch(#state{name = TestLog, level = ?DEBUG}, State3), |
|
|
|
?assertMatch(#state{fileName = TestLog, level = ?DEBUG}, State3), |
|
|
|
ok |
|
|
|
end |
|
|
|
end} |
|
|
|
end, |
|
|
|
fun(DefaultState = #state{name = TestLog, sync_size = SyncSize, sync_interval = SyncInterval, rotator = Rotator}) -> |
|
|
|
fun(DefaultState = #state{fileName = TestLog, sync_size = SyncSize, syncInterval = SyncInterval, rotator = Rotator}) -> |
|
|
|
{"Internal rotation and delayed write", |
|
|
|
fun() -> |
|
|
|
TestLog0 = TestLog ++ ".0", |
|
|
@ -538,7 +426,7 @@ rotation_test_() -> |
|
|
|
{ok, FD, Inode, Ctime, _Size} = Rotator:openLogfile(TestLog, {SyncSize, SyncInterval}), |
|
|
|
State0 = DefaultState#state{ |
|
|
|
fd = FD, inode = Inode, ctime = Ctime, size = RotationSize, |
|
|
|
check_interval = CheckInterval, last_check = PreviousCheck}, |
|
|
|
checkInterval = CheckInterval, lastCheck = PreviousCheck}, |
|
|
|
|
|
|
|
%% new message within check interval with sync_on level |
|
|
|
Msg1Timestamp = add_secs(PreviousCheck, 1), |
|
|
@ -566,7 +454,7 @@ rotation_test_() -> |
|
|
|
{ok, FInfo} = file:read_file_info(TestLog, [raw]), |
|
|
|
?assert(RotationSize < FInfo#file_info.size), |
|
|
|
%% ...no rotation yet |
|
|
|
?assertEqual(PreviousCheck, State2#state.last_check), |
|
|
|
?assertEqual(PreviousCheck, State2#state.lastCheck), |
|
|
|
?assertNot(filelib:is_regular(TestLog0)), |
|
|
|
|
|
|
|
%% new message after check interval |
|
|
@ -1122,59 +1010,59 @@ config_validation_test_() -> |
|
|
|
[ |
|
|
|
{"missing file", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{level, info}, {size, 10}])) |
|
|
|
checkOpts([{level, info}, {size, 10}])) |
|
|
|
}, |
|
|
|
{"bad level", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {level, blah}, {size, 10}])) |
|
|
|
checkOpts([{file, "test.log"}, {level, blah}, {size, 10}])) |
|
|
|
}, |
|
|
|
{"bad size", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {size, infinity}])) |
|
|
|
checkOpts([{file, "test.log"}, {size, infinity}])) |
|
|
|
}, |
|
|
|
{"bad count", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {count, infinity}])) |
|
|
|
checkOpts([{file, "test.log"}, {count, infinity}])) |
|
|
|
}, |
|
|
|
{"bad high water mark", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {high_water_mark, infinity}])) |
|
|
|
checkOpts([{file, "test.log"}, {high_water_mark, infinity}])) |
|
|
|
}, |
|
|
|
{"bad date", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {date, "midnight"}])) |
|
|
|
checkOpts([{file, "test.log"}, {date, "midnight"}])) |
|
|
|
}, |
|
|
|
{"blank date is ok", |
|
|
|
?_assertMatch([_ | _], |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {date, ""}])) |
|
|
|
checkOpts([{file, "test.log"}, {date, ""}])) |
|
|
|
}, |
|
|
|
{"bad sync_interval", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {sync_interval, infinity}])) |
|
|
|
checkOpts([{file, "test.log"}, {sync_interval, infinity}])) |
|
|
|
}, |
|
|
|
{"bad sync_size", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {sync_size, infinity}])) |
|
|
|
checkOpts([{file, "test.log"}, {sync_size, infinity}])) |
|
|
|
}, |
|
|
|
{"bad check_interval", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {check_interval, infinity}])) |
|
|
|
checkOpts([{file, "test.log"}, {check_interval, infinity}])) |
|
|
|
}, |
|
|
|
{"bad sync_on level", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {sync_on, infinity}])) |
|
|
|
checkOpts([{file, "test.log"}, {sync_on, infinity}])) |
|
|
|
}, |
|
|
|
{"bad formatter module", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {formatter, "io:format"}])) |
|
|
|
checkOpts([{file, "test.log"}, {formatter, "io:format"}])) |
|
|
|
}, |
|
|
|
{"bad formatter config", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {formatter_config, blah}])) |
|
|
|
checkOpts([{file, "test.log"}, {formatter_config, blah}])) |
|
|
|
}, |
|
|
|
{"unknown option", |
|
|
|
?_assertEqual(false, |
|
|
|
validate_logfile_proplist([{file, "test.log"}, {rhubarb, spicy}])) |
|
|
|
checkOpts([{file, "test.log"}, {rhubarb, spicy}])) |
|
|
|
} |
|
|
|
]. |
|
|
|
|
|
|
|