From b3fc8fd78575ad35b37efb28108ee2758019ad0c Mon Sep 17 00:00:00 2001 From: Wilson Li Date: Fri, 22 Sep 2017 15:50:39 +0900 Subject: [PATCH] Added Hourly Rotation Option, Added Rotator Option --- src/lager_file_backend.erl | 35 ++++++++++++++++-------- src/lager_util.erl | 56 ++++++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/lager_file_backend.erl b/src/lager_file_backend.erl index 1537b31..f8fe57e 100644 --- a/src/lager_file_backend.erl +++ b/src/lager_file_backend.erl @@ -53,6 +53,7 @@ -define(DEFAULT_ROTATION_SIZE, 10485760). %% 10mb -define(DEFAULT_ROTATION_DATE, "$D0"). %% midnight -define(DEFAULT_ROTATION_COUNT, 5). +-define(DEFAULT_ROTATION_MOD, lager_util). -define(DEFAULT_SYNC_LEVEL, error). -define(DEFAULT_SYNC_INTERVAL, 1000). -define(DEFAULT_SYNC_SIZE, 1024*64). %% 64kb @@ -67,6 +68,7 @@ size = 0 :: integer(), date :: undefined | string(), count = 10 :: integer(), + rotator :: atom(), shaper :: lager_shaper(), formatter :: atom(), formatter_config :: any(), @@ -79,7 +81,8 @@ -type option() :: {file, string()} | {level, lager:log_level()} | {size, non_neg_integer()} | {date, string()} | - {count, non_neg_integer()} | {high_water_mark, non_neg_integer()} | + {count, non_neg_integer()} | {rotator, atom()} | + {high_water_mark, non_neg_integer()} | {sync_interval, non_neg_integer()} | {sync_size, non_neg_integer()} | {sync_on, lager:log_level()} | {check_interval, non_neg_integer()} | {formatter, atom()} | @@ -108,16 +111,16 @@ init(LogFileConfig) when is_list(LogFileConfig) -> {error, {fatal, bad_config}}; Config -> %% probabably a better way to do this, but whatever - [RelName, Level, Date, Size, Count, HighWaterMark, Flush, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] = - [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, high_water_mark, flush_queue, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]], + [RelName, Level, Date, Size, Count, Rotator, HighWaterMark, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] = + [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, rotator, high_water_mark, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]], FlushThr = proplists:get_value(flush_threshold, Config, 0), Name = lager_util:expand_path(RelName), schedule_rotation(Name, Date), Shaper = lager_util:maybe_flush(Flush, #lager_shaper{hwm=HighWaterMark, flush_threshold = FlushThr, id=Name}), - State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, shaper=Shaper, 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 + 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:create_logfile(Name, {SyncSize, SyncInterval}) of {ok, {FD, Inode, _}} -> State0#state{fd=FD, inode=Inode}; {error, Reason} -> @@ -184,8 +187,8 @@ handle_event(_Event, State) -> {ok, State}. %% @private -handle_info({rotate, File}, #state{name=File,count=Count,date=Date} = State) -> - _ = lager_util:rotate_logfile(File, Count), +handle_info({rotate, File}, #state{name=File,count=Count,date=Date,rotator=Rotator} = State) -> + _ = Rotator:rotate_logfile(File, Count), State1 = close_file(State), schedule_rotation(File, Date), {ok, State1}; @@ -229,14 +232,14 @@ config_to_id(Config) -> write(#state{name=Name, fd=FD, inode=Inode, flap=Flap, size=RotSize, - count=Count} = State, Timestamp, Level, Msg) -> + count=Count, rotator=Rotator} = State, Timestamp, Level, Msg) -> LastCheck = timer:now_diff(Timestamp, State#state.last_check) 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 + case Rotator:ensure_logfile(Name, FD, Inode, {State#state.sync_size, State#state.sync_interval}) of {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize -> - case lager_util:rotate_logfile(Name, Count) of + case Rotator:rotate_logfile(Name, Count) of ok -> %% go around the loop again, we'll do another rotation check and hit the next clause of ensure_logfile write(State, Timestamp, Level, Msg); @@ -309,6 +312,7 @@ validate_logfile_proplist(List) -> 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, []} @@ -347,6 +351,13 @@ validate_logfile_proplist([{count, Count}|Tail], 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 -> diff --git a/src/lager_util.erl b/src/lager_util.erl index ee26ad2..aa1f443 100644 --- a/src/lager_util.erl +++ b/src/lager_util.erl @@ -25,7 +25,7 @@ -export([ levels/0, level_to_num/1, level_to_chr/1, num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1, - open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1, + create_logfile/2, open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1, localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1, calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3, trace_filter/1, trace_filter/2, expand_path/1, find_file/2, check_hwm/1, check_hwm/2, @@ -140,6 +140,9 @@ level_to_atom(String) -> erlang:error(badarg) end. +create_logfile(Name, Buffer) -> + open_logfile(Name, Buffer). + open_logfile(Name, Buffer) -> case filelib:ensure_dir(Name) of ok -> @@ -244,19 +247,34 @@ format_time({{Y, M, D}, {H, Mi, S}}) -> {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)], [i2l(H), $:, i2l(Mi), $:, i2l(S)]}. +parse_rotation_hour_spec([], Res) -> + {ok, Res}; +parse_rotation_hour_spec([$H, M1, M2], Res) -> + case list_to_integer([M1, M2]) of + X when X >= 0, X =< 59 -> + {ok, Res ++ [{minute, X}]}; + _ -> + {error, invalid_date_spec} + end; +parse_rotation_hour_spec([$H, M], Res) when M >= $0, M =< $9 -> + {ok, Res ++ [{minute, M - 48}]}; +parse_rotation_hour_spec(_,_) -> + {error, invalid_date_spec}. + +%% Default to 00:00:00 rotation parse_rotation_day_spec([], Res) -> - {ok, Res ++ [{hour, 0}]}; -parse_rotation_day_spec([$D, D1, D2], Res) -> + {ok, Res ++ [{hour ,0}]}; +parse_rotation_day_spec([$D, D1, D2|T], Res) -> case list_to_integer([D1, D2]) of X when X >= 0, X =< 23 -> - {ok, Res ++ [{hour, X}]}; + parse_rotation_hour_spec(T, Res ++ [{hour, X}]); _ -> {error, invalid_date_spec} end; -parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 -> - {ok, Res ++ [{hour, D - 48}]}; -parse_rotation_day_spec(_, _) -> - {error, invalid_date_spec}. +parse_rotation_day_spec([$D, D|T], Res) when D >= $0, D =< $9 -> + parse_rotation_hour_spec(T, Res ++ [{hour, D - 48 }]); +parse_rotation_day_spec(X, Res) -> + parse_rotation_hour_spec(X, Res). parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 -> Week = W - 48, @@ -295,6 +313,17 @@ calculate_next_rotation(Spec) -> calculate_next_rotation([], Now) -> Now; +calculate_next_rotation([{minute, X}|T], {{_, _, _}, {Hour, Minute, _}} = Now) when Minute < X -> + %% rotation is this hour + NewNow = setelement(2, Now, {Hour, X, 0}), + calculate_next_rotation(T, NewNow); +calculate_next_rotation([{minute, X}|T], Now) -> + %% rotation is next hour + Seconds = calendar:datetime_to_gregorian_seconds(Now) + 3600, + DateTime = calendar:gregorian_seconds_to_datetime(Seconds), + {_, {NewHour, _, _}} = DateTime, + NewNow = setelement(2, DateTime, {NewHour, X, 0}), + calculate_next_rotation(T, NewNow); calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X -> %% rotation is today, sometime NewNow = setelement(2, Now, {X, 0, 0}), @@ -630,10 +659,13 @@ maybe_flush(Flag, #lager_shaper{} = S) when is_boolean(Flag) -> -ifdef(TEST). parse_test() -> + ?assertEqual({ok, [{minute, 0}]}, parse_rotation_date_spec("$H0")), + ?assertEqual({ok, [{minute, 59}]}, parse_rotation_date_spec("$H59")), ?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")), ?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")), ?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")), ?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")), + ?assertEqual({ok, [{day, 0}, {hour, 12}, {minute, 30}]}, parse_rotation_date_spec("$W0D12H30")), ?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")), ?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")), ?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")), @@ -645,6 +677,8 @@ parse_test() -> ok. parse_fail_test() -> + ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$H")), + ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$H60")), ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")), ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")), ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")), @@ -658,6 +692,12 @@ parse_fail_test() -> ok. rotation_calculation_test() -> + ?assertMatch({{2000, 1, 1}, {13, 0, 0}}, + calculate_next_rotation([{minute, 0}], {{2000, 1, 1}, {12, 34, 43}})), + ?assertMatch({{2000, 1, 1}, {12, 45, 0}}, + calculate_next_rotation([{minute, 45}], {{2000, 1, 1}, {12, 34, 43}})), + ?assertMatch({{2000, 1, 2}, {0, 0, 0}}, + calculate_next_rotation([{minute, 0}], {{2000, 1, 1}, {23, 45, 43}})), ?assertMatch({{2000, 1, 2}, {0, 0, 0}}, calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})), ?assertMatch({{2000, 1, 1}, {16, 0, 0}},