diff --git a/etc/emqx.conf b/etc/emqx.conf index fb7263f85..4cdd6e4d7 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -427,7 +427,7 @@ log.to = file ## this level will be logged. ## ## Default: warning -log.level = warning +log.level = debug ## The dir for log files. ## @@ -471,9 +471,10 @@ log.formatter = text ## %M: minute ## %S: second ## %6N: microseconds -## %z: timezone +## %3N: milliseconds +## %z: timezone, [+|-]HH:MM or [+|-]HH:MM:SS. Depends on `log.formatter.text.date.timezone` ## For example: -## log.formatter.text.date.format = emqx-server-date: %Y-%m-%dT%H:%M:%S:%6N %z +## log.formatter.text.date.format = emqx-server-date: %Y-%m-%dT%H:%M:%S.%6N %z ## Default: rfc3339 # log.formatter.text.date.format = rfc3339 @@ -481,9 +482,9 @@ log.formatter = text ## Only takes effect when log.formatter.text.date.format not rfc3339. ## ## Value: local | z | UTC TimeOffset -## TimeOffset: [+|-]HH:MM:SS, for example: +08:00 -## z: UTC zulu timezone. +## TimeOffset: [+|-]HH:MM or [+|-]HH:MM:SS, for example: +08:00. ## local is the system local time timezone. +## z: UTC zulu timezone. ## Default: local # log.formatter.text.date.timezone = local diff --git a/priv/emqx.schema b/priv/emqx.schema index 7fde30c02..dc15d87b3 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -530,13 +530,13 @@ end}. %% @doc format logs as text, date format part {mapping, "log.formatter.text.date.format", "kernel.logger", [ - {default, rfc3339}, + {default, "rfc3339"}, {datatype, string} ]}. %% @doc format logs as text, date format part. local or any UTC offset {mapping, "log.formatter.text.date.timezone", "kernel.logger", [ - {default, local}, + {default, "local"}, {datatype, string} ]}. @@ -642,7 +642,7 @@ end}. }}; text -> DateFormat = - case cuttlefish:conf_get("log.formatter.text.date.format", Conf) of + case cuttlefish:conf_get("log.formatter.text.date.format", Conf, "rfc3339") of "rfc3339" -> rfc3339; DateStr -> @@ -655,7 +655,9 @@ end}. DST(<<"%H", Tail/binary>>, Formatter) -> DST(Tail, [hour | Formatter]); DST(<<"%M", Tail/binary>>, Formatter) -> DST(Tail, [minute | Formatter]); DST(<<"%S", Tail/binary>>, Formatter) -> DST(Tail, [second | Formatter]); - DST(<<"%6N", Tail/binary>>, Formatter) -> DST(Tail, [nano_second | Formatter]); + DST(<<"%N", Tail/binary>>, Formatter) -> DST(Tail, [nanosecond | Formatter]); + DST(<<"%3N", Tail/binary>>, Formatter) -> DST(Tail, [millisecond | Formatter]); + DST(<<"%6N", Tail/binary>>, Formatter) -> DST(Tail, [microsecond | Formatter]); DST(<<"%z", Tail/binary>>, Formatter) -> DST(Tail, [timezone | Formatter]); DST(<>, [Str | Formatter]) when is_list(Str) -> DST(Tail, [lists:append(Str, [Char]) | Formatter]); @@ -665,7 +667,7 @@ end}. DateStrTrans(list_to_binary(DateStr), []) end, {DateOffsetStr, DateOffset} = - case cuttlefish:conf_get("log.formatter.text.date.timezone", Conf) of + case cuttlefish:conf_get("log.formatter.text.date.timezone", Conf, "local") of "z" -> {"z", 0}; "Z" -> @@ -688,9 +690,9 @@ end}. Str = case S of 0 -> - io_lib:format("~c~p:~p", [Sign, H, M]); + io_lib:format("~c~2.10.0B:~2.10.0B", [Sign, H, M]); S -> - io_lib:format("~c~p:~p:~p", [Sign, H, M, S]) + io_lib:format("~c~2.10.0B:~2.10.0B:~2.10.0B", [Sign, H, M, S]) end, {Str, OffsetSecond}; DateOStr -> diff --git a/src/emqx_calendar.erl b/src/emqx_calendar.erl new file mode 100644 index 000000000..ae9bae763 --- /dev/null +++ b/src/emqx_calendar.erl @@ -0,0 +1,512 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2019-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed 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(emqx_calendar). + +-define(SECONDS_PER_MINUTE, 60). +-define(SECONDS_PER_HOUR, 3600). +-define(SECONDS_PER_DAY, 86400). +-define(DAYS_PER_YEAR, 365). +-define(DAYS_PER_LEAP_YEAR, 366). +-define(DAYS_FROM_0_TO_1970, 719528). +-define(SECONDS_FROM_0_TO_1970, ?DAYS_FROM_0_TO_1970 * ?SECONDS_PER_DAY). + +-export([ formatter/1 + , format/3 + , format/4 + , format/5 + , parse/3 + , parse/4 + , offset_second/1 + ]). + +-export([test/0]). + +-define(DATE_PART, + [ year + , month + , day + , hour + , minute + , second + , nanosecond + , millisecond + , microsecond + ]). + +-define(DATE_ZONE_NAME, + [ timezone + , timezone1 + , timezone2 + ]). + +formatter(FormatterStr) when is_list(FormatterStr) -> + formatter(list_to_binary(FormatterStr)); +formatter(FormatterBin) when is_binary(FormatterBin) -> + do_formatter(FormatterBin, []). + +offset_second(Offset) -> + do_offset_second(Offset). + +format(Time, Unit, Formatter) -> + format(Time, Unit, undefined, Formatter). + +format(Time, Unit, Offset, FormatterBin) when is_binary(FormatterBin) -> + format(Time, Unit, Offset, formatter(FormatterBin)); +format(Time, Unit, Offset, Formatter) -> + do_format(Time, time_unit(Unit), offset_second(Offset), Formatter). + +%% for logger format +format(Time, Unit, Offset, Zones, Formatter) -> + do_format(Time, time_unit(Unit), offset_second(Offset), Zones, Formatter). + +test() -> + Units = [second, millisecond, microsecond, nanosecond], + Formatters = [ + <<"%Y-%m-%d %H:%M:%S:%N%z">>, + <<"%Y-%m-%d %H:%M:%S:%N%:z">>, + <<"%Y-%m-%d %H:%M:%S:%N%::z">>, + <<"%Y-%m-%d %H:%M:%S:%N">>, + <<"%Y-%m-%d %H:%M:%S:%3N">>, + <<"%Y-%m-%d %H:%M:%S:%6N">> + ], + Offsets = [ + 0, + 8 * 3600, + -8 * 3600, + <<"+08:00">>, + <<"+08:00:01">>, + <<"+0802">>, + <<"-08:00">>, + <<"local">> + ], + [ + [ + [begin + Time = erlang:system_time(U), + FormatDate = erlang:list_to_binary(format(Time, U, O, F)), + PTime = emqx_calendar:parse(FormatDate, U, O, F), + io:format("~p ~p ~p ~p ~n~p~n~p~n", [U, O, F, FormatDate, Time, PTime]), + % Equal = (PTime == Time), + % F2 = + % fun() -> + % Delta = Time - PTime, + % DeltaFmt = erlang:list_to_binary(format(Delta, U, 0, <<"%Y-%m-%d %H:%M:%S:%N">>)), + % io:format("delta ~p~n ~p~n-----~n", [Delta, DeltaFmt]) + % end, + % Equal orelse F2() + ok + end||O <- Offsets] + || F <-Formatters] + || U <-Units], + ok. + +parse(DateStr, Unit, FormatterBin) -> + parse(DateStr, Unit, 0, FormatterBin). + +parse(DateStr, Unit, Offset, FormatterBin) when is_binary(FormatterBin) -> + parse(DateStr, Unit, Offset, formatter(FormatterBin)); +parse(DateStr, Unit, Offset, Formatter) -> + do_parse(DateStr, Unit, offset_second(Offset), Formatter). + +do_parse(DateStr, Unit, Offset, Formatter) -> + DateInfo = do_parse_date_str(DateStr, Formatter, #{}), + {Precise, PrecisionUnit} = precise(DateInfo), + Counter = + fun + (year, V, Res) -> + Res + dy(V) * ?SECONDS_PER_DAY * Precise - (?SECONDS_FROM_0_TO_1970 * Precise); + (month, V, Res) -> + Res + nano_sm(V); + (day, V, Res) -> + Res + (V * ?SECONDS_PER_DAY * Precise); + (hour, V, Res) -> + Res + (V * ?SECONDS_PER_HOUR * Precise); + (minute, V, Res) -> + Res + (V * ?SECONDS_PER_MINUTE * Precise); + (second, V, Res) -> + Res + V * Precise; + (millisecond, V, Res) -> + case PrecisionUnit of + millisecond -> + Res + V; + microsecond -> + Res + (V * 1000); + nanosecond -> + Res + (V * 1000000) + end; + (microsecond, V, Res) -> + case PrecisionUnit of + microsecond -> + Res + V; + nanosecond -> + Res + (V * 1000) + end; + (nanosecond, V, Res) -> + Res + V; + (parsed_offset, V, Res) -> + Res - V + end, + Counter1 = + fun(K,V,R) -> + Res = Counter(K,V,R), + % io:format("K, V, R = ~p, ~p, ~p, Res = ~p~n", [K, V, R, Res - R]), + Res + end, + Count0 = maps:fold(Counter1, 0, DateInfo) - (?SECONDS_PER_DAY * Precise), + Count = + case maps:is_key(parsed_offset, DateInfo) of + true -> + Count0; + false -> + (Offset * Precise) + Count0 + end, + erlang:convert_time_unit(Count, PrecisionUnit, Unit). + +precise(#{nanosecond := _}) -> {1000_000_000, nanosecond}; +precise(#{microsecond := _}) -> {1000_000, microsecond}; +precise(#{millisecond := _}) -> {1000, millisecond}; +precise(#{second := _}) -> {1, second}; +precise(_) -> {1, second}. + +do_parse_date_str(<<>>, _, Result) -> Result; +do_parse_date_str(_, [], Result) -> Result; +do_parse_date_str(Date, [Key | Formatter], Result) -> + Size = date_size(Key), + <> = Date, + case lists:member(Key, ?DATE_PART) of + true -> + do_parse_date_str(Tail, Formatter, Result#{Key => erlang:binary_to_integer(DatePart)}); + false -> + case lists:member(Key, ?DATE_ZONE_NAME) of + true -> + io:format("DatePart -------------- ~p ~p~n", [DatePart, offset_second(DatePart)]), + do_parse_date_str(Tail, Formatter, Result#{parsed_offset => offset_second(DatePart)}); + false -> + do_parse_date_str(Tail, Formatter, Result) + end + end. + +date_size(Str) when is_list(Str) -> erlang:length(Str); +date_size(DateName) -> + Map = #{ + year => 4, + month => 2, + day => 2, + hour => 2, + minute => 2, + second => 2, + millisecond => 3, + microsecond => 6, + nanosecond => 9, + timezone => 5, + timezone1 => 6, + timezone2 => 9 + }, + maps:get(DateName, Map). + +nano_sm(Month) -> dm(Month) * ?SECONDS_PER_DAY * 1000_000. + +dm(Month) -> + MonthDays = #{ + 1 => 0, + 2 => 31, + 3 => 59, + 4 => 90, + 5 => 120, + 6 => 151, + 7 => 181, + 8 => 212, + 9 => 243, + 10 => 273, + 11 => 304, + 12 => 334 + }, + maps:get(Month, MonthDays). + +%% ------------------------------------------------------------------------------------------------- +%% internal +%% emqx_calendar:format(erlang:system_time(second), <<"second">>, <<"+8:00">> ,<<"%Y-%m-%d-%H:%M:%S%Z">>). + +time_unit(second) -> second; +time_unit(millisecond) -> millisecond; +time_unit(microsecond) -> microsecond; +time_unit(nanosecond) -> nanosecond; +time_unit("second") -> second; +time_unit("millisecond") -> millisecond; +time_unit("microsecond") -> microsecond; +time_unit("nanosecond") -> nanosecond; +time_unit(<<"second">>) -> second; +time_unit(<<"millisecond">>) -> millisecond; +time_unit(<<"microsecond">>) -> microsecond; +time_unit(<<"nanosecond">>) -> nanosecond. + +%% ------------------------------------------------------------------------------------------------- +%% internal: format part + +do_formatter(<<>>, Formatter) -> lists:reverse(Formatter); +do_formatter(<<"%Y", Tail/binary>>, Formatter) -> do_formatter(Tail, [year | Formatter]); +do_formatter(<<"%m", Tail/binary>>, Formatter) -> do_formatter(Tail, [month | Formatter]); +do_formatter(<<"%d", Tail/binary>>, Formatter) -> do_formatter(Tail, [day | Formatter]); +do_formatter(<<"%H", Tail/binary>>, Formatter) -> do_formatter(Tail, [hour | Formatter]); +do_formatter(<<"%M", Tail/binary>>, Formatter) -> do_formatter(Tail, [minute | Formatter]); +do_formatter(<<"%S", Tail/binary>>, Formatter) -> do_formatter(Tail, [second | Formatter]); +do_formatter(<<"%N", Tail/binary>>, Formatter) -> do_formatter(Tail, [nanosecond | Formatter]); +do_formatter(<<"%3N", Tail/binary>>, Formatter) -> do_formatter(Tail, [millisecond | Formatter]); +do_formatter(<<"%6N", Tail/binary>>, Formatter) -> do_formatter(Tail, [microsecond | Formatter]); +do_formatter(<<"%z", Tail/binary>>, Formatter) -> do_formatter(Tail, [timezone | Formatter]); +do_formatter(<<"%:z", Tail/binary>>, Formatter) -> do_formatter(Tail, [timezone1 | Formatter]); +do_formatter(<<"%::z", Tail/binary>>, Formatter) -> do_formatter(Tail, [timezone2 | Formatter]); +do_formatter(<>, [Str | Formatter]) when is_list(Str) -> + do_formatter(Tail, [lists:append(Str, [Char]) | Formatter]); +do_formatter(<>, Formatter) -> do_formatter(Tail, [[Char] | Formatter]). + +do_offset_second(OffsetSecond) when is_integer(OffsetSecond) -> OffsetSecond; +do_offset_second(undefined) -> 0; +do_offset_second("local") -> do_offset_second(local); +do_offset_second(<<"local">>) -> do_offset_second(local); +do_offset_second(local) -> + UniversalTime = calendar:system_time_to_universal_time(erlang:system_time(second), second), + LocalTime = erlang:universaltime_to_localtime(UniversalTime), + LocalSecs = calendar:datetime_to_gregorian_seconds(LocalTime), + UniversalSecs = calendar:datetime_to_gregorian_seconds(UniversalTime), + LocalSecs - UniversalSecs; +do_offset_second(Offset) when is_binary(Offset) -> + do_offset_second(erlang:binary_to_list(Offset)); +do_offset_second("Z") -> 0; +do_offset_second("z") -> 0; +do_offset_second(Offset) when is_list(Offset) -> + Sign = hd(Offset), + ((Sign == $+) orelse (Sign == $-)) + orelse error({bad_zone, Offset}), + Signs = #{$+ => 1, $- => -1}, + PosNeg = maps:get(Sign, Signs), + [Sign | HM] = Offset, + {HourStr, MinuteStr, SecondStr} = + case string:tokens(HM, ":") of + [H, M] -> + {H, M, "0"}; + [H, M, S] -> + {H, M, S}; + [HHMM] when erlang:length(HHMM) == 4 -> + {string:sub_string(HHMM, 1,2), string:sub_string(HHMM, 3,4), "0"}; + _ -> + error({bad_zone, Offset}) + end, + Hour = erlang:list_to_integer(HourStr), + Minute = erlang:list_to_integer(MinuteStr), + Second = erlang:list_to_integer(SecondStr), + (Hour =< 23) orelse error({bad_hour, Hour}), + (Minute =< 59) orelse error({bad_minute, Minute}), + (Second =< 59) orelse error({bad_second, Second}), + PosNeg * (Hour * 3600 + Minute * 60 + Second). + +do_format(Time, Unit, Offset, Formatter) -> + Timezones = formatter_timezones(Offset, Formatter, #{}), + do_format(Time, Unit, Offset, Timezones, Formatter). + +do_format(Time, Unit, Offset, Timezones, Formatter) -> + Adjustment = erlang:convert_time_unit(Offset, second, Unit), + AdjustedTime = Time + Adjustment, + Factor = factor(Unit), + Secs = AdjustedTime div Factor, + DateTime = system_time_to_datetime(Secs), + {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, + Date = #{ + year => padding(Year, 4), + month => padding(Month, 2), + day => padding(Day, 2), + hour => padding(Hour, 2), + minute => padding(Min, 2), + second => padding(Sec, 2), + millisecond => trans_x_second(Unit, millisecond, Time), + microsecond => trans_x_second(Unit, microsecond, Time), + nanosecond => trans_x_second(Unit, nanosecond, Time) + }, + DateWithZone = maps:merge(Date, Timezones), + [maps:get(Key, DateWithZone, Key) || Key <- Formatter]. + +formatter_timezones(_Offset, [], Zones) -> Zones; +formatter_timezones(Offset, [Timezone | Formatter], Zones) -> + case lists:member(Timezone, [timezone, timezone1, timezone2]) of + true -> + NZones = Zones#{Timezone => offset_to_timezone(Offset, Timezone)}, + formatter_timezones(Offset, Formatter, NZones); + false -> + formatter_timezones(Offset, Formatter, Zones) + end. + +offset_to_timezone(Offset, Timezone) -> + Sign = + case Offset >= 0 of + true -> + $+; + false -> + $- + end, + {H, M, S} = seconds_to_time(abs(Offset)), + %% TODO: Support zone define %:::z + %% Numeric time zone with ":" to necessary precision (e.g., -04, +05:30). + case Timezone of + timezone -> + %% +0800 + io_lib:format("~c~2.10.0B~2.10.0B", [Sign, H, M]); + timezone1 -> + %% +08:00 + io_lib:format("~c~2.10.0B:~2.10.0B", [Sign, H, M]); + timezone2 -> + %% +08:00:00 + io_lib:format("~c~2.10.0B:~2.10.0B:~2.10.0B", [Sign, H, M, S]) + end. + +factor(second) -> 1; +factor(millisecond) -> 1000; +factor(microsecond) -> 1000000; +factor(nanosecond) -> 1000000000. + +system_time_to_datetime(Seconds) -> + gregorian_seconds_to_datetime(Seconds + ?SECONDS_FROM_0_TO_1970). + +gregorian_seconds_to_datetime(Secs) when Secs >= 0 -> + Days = Secs div ?SECONDS_PER_DAY, + Rest = Secs rem ?SECONDS_PER_DAY, + {gregorian_days_to_date(Days), seconds_to_time(Rest)}. + +seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY -> + Secs0 = Secs rem ?SECONDS_PER_DAY, + Hour = Secs0 div ?SECONDS_PER_HOUR, + Secs1 = Secs0 rem ?SECONDS_PER_HOUR, + Minute = Secs1 div ?SECONDS_PER_MINUTE, + Second = Secs1 rem ?SECONDS_PER_MINUTE, + {Hour, Minute, Second}. + +gregorian_days_to_date(Days) -> + {Year, DayOfYear} = day_to_year(Days), + {Month, DayOfMonth} = year_day_to_date(Year, DayOfYear), + {Year, Month, DayOfMonth}. + +day_to_year(DayOfEpoch) when DayOfEpoch >= 0 -> + YMax = DayOfEpoch div ?DAYS_PER_YEAR, + YMin = DayOfEpoch div ?DAYS_PER_LEAP_YEAR, + {Y1, D1} = dty(YMin, YMax, DayOfEpoch, dy(YMin), dy(YMax)), + {Y1, DayOfEpoch - D1}. + +year_day_to_date(Year, DayOfYear) -> + ExtraDay = + case is_leap_year(Year) of + true -> + 1; + false -> + 0 + end, + {Month, Day} = year_day_to_date2(ExtraDay, DayOfYear), + {Month, Day + 1}. + +dty(Min, Max, _D1, DMin, _DMax) when Min == Max -> + {Min, DMin}; +dty(Min, Max, D1, DMin, DMax) -> + Diff = Max - Min, + Mid = Min + Diff * (D1 - DMin) div (DMax - DMin), + MidLength = + case is_leap_year(Mid) of + true -> + ?DAYS_PER_LEAP_YEAR; + false -> + ?DAYS_PER_YEAR + end, + case dy(Mid) of + D2 when D1 < D2 -> + NewMax = Mid - 1, + dty(Min, NewMax, D1, DMin, dy(NewMax)); + D2 when D1 - D2 >= MidLength -> + NewMin = Mid + 1, + dty(NewMin, Max, D1, dy(NewMin), DMax); + D2 -> + {Mid, D2} + end. + +dy(Y) when Y =< 0 -> + 0; +dy(Y) -> + X = Y - 1, + X div 4 - X div 100 + X div 400 + X * ?DAYS_PER_YEAR + ?DAYS_PER_LEAP_YEAR. + +is_leap_year(Y) when is_integer(Y), Y >= 0 -> + is_leap_year1(Y). + +is_leap_year1(Year) when Year rem 4 =:= 0, Year rem 100 > 0 -> + true; +is_leap_year1(Year) when Year rem 400 =:= 0 -> + true; +is_leap_year1(_) -> + false. + +year_day_to_date2(_, Day) when Day < 31 -> + {1, Day}; +year_day_to_date2(E, Day) when 31 =< Day, Day < 59 + E -> + {2, Day - 31}; +year_day_to_date2(E, Day) when 59 + E =< Day, Day < 90 + E -> + {3, Day - (59 + E)}; +year_day_to_date2(E, Day) when 90 + E =< Day, Day < 120 + E -> + {4, Day - (90 + E)}; +year_day_to_date2(E, Day) when 120 + E =< Day, Day < 151 + E -> + {5, Day - (120 + E)}; +year_day_to_date2(E, Day) when 151 + E =< Day, Day < 181 + E -> + {6, Day - (151 + E)}; +year_day_to_date2(E, Day) when 181 + E =< Day, Day < 212 + E -> + {7, Day - (181 + E)}; +year_day_to_date2(E, Day) when 212 + E =< Day, Day < 243 + E -> + {8, Day - (212 + E)}; +year_day_to_date2(E, Day) when 243 + E =< Day, Day < 273 + E -> + {9, Day - (243 + E)}; +year_day_to_date2(E, Day) when 273 + E =< Day, Day < 304 + E -> + {10, Day - (273 + E)}; +year_day_to_date2(E, Day) when 304 + E =< Day, Day < 334 + E -> + {11, Day - (304 + E)}; +year_day_to_date2(E, Day) when 334 + E =< Day -> + {12, Day - (334 + E)}. + +trans_x_second(FromUnit, ToUnit, Time) -> + XSecond = do_trans_x_second(FromUnit, ToUnit, Time), + Len = + case ToUnit of + millisecond -> 3; + microsecond -> 6; + nanosecond -> 9 + end, + padding(XSecond, Len). + +do_trans_x_second(second, second, Time) -> Time div 60; +do_trans_x_second(second, _, _Time) -> 0; + +do_trans_x_second(millisecond, millisecond, Time) -> Time rem 1000; +do_trans_x_second(millisecond, microsecond, _Time) -> 0; +do_trans_x_second(millisecond, nanosecond, _Time) -> 0; + +do_trans_x_second(microsecond, millisecond, Time) -> Time div 1000 rem 1000; +do_trans_x_second(microsecond, microsecond, Time) -> Time rem 1000000; +do_trans_x_second(microsecond, nanosecond, _Time) -> 0; + +do_trans_x_second(nanosecond, millisecond, Time) -> Time div 1000000 rem 1000; +do_trans_x_second(nanosecond, microsecond, Time) -> Time div 1000 rem 1000000; +do_trans_x_second(nanosecond, nanosecond, Time) -> Time rem 1000000000. + +padding(Data, Len) when is_integer(Data) -> + padding(integer_to_list(Data), Len); +padding(Data, Len) when Len > 0 andalso erlang:length(Data) < Len -> + [$0 | padding(Data, Len - 1)]; +padding(Data, _Len) -> + Data. diff --git a/src/emqx_logger_textfmt.erl b/src/emqx_logger_textfmt.erl index df4118ba4..b13871384 100644 --- a/src/emqx_logger_textfmt.erl +++ b/src/emqx_logger_textfmt.erl @@ -16,16 +16,6 @@ -module(emqx_logger_textfmt). --define(SECONDS_PER_MINUTE, 60). --define(SECONDS_PER_HOUR, 3600). --define(SECONDS_PER_DAY, 86400). --define(DAYS_PER_YEAR, 365). --define(DAYS_PER_LEAP_YEAR, 366). --define(DAYS_FROM_0_TO_1970, 719528). --define(DAYS_FROM_0_TO_10000, 2932897). --define(SECONDS_FROM_0_TO_1970, ?DAYS_FROM_0_TO_1970 * ?SECONDS_PER_DAY). --define(SECONDS_FROM_0_TO_10000, ?DAYS_FROM_0_TO_10000 * ?SECONDS_PER_DAY). - -export([format/2]). -export([check_config/1]). @@ -39,7 +29,7 @@ ]). check_config(Config0) -> - Config = maps:without([date_format, timezone_offset, timezone], Config0), + Config = maps:without([timezone_offset, timezone, date_format], Config0), logger_formatter:check_config(Config). format(#{msg := Msg0, meta := Meta} = Event, @@ -51,8 +41,15 @@ format(#{msg := Msg0, meta := Meta} = Event, format(#{msg := Msg0, meta := Meta} = Event, #{timezone_offset := TZO, timezone := TZ, date_format := DFS} = Config) -> Msg = maybe_merge(Msg0, Meta), - Time = maps:get(time, Event, undefined), - [date_format(Time, TZO, TZ, DFS) | logger_formatter:format(Event#{msg := Msg}, Config)]. + Time = + case maps:get(time, Event, undefined) of + undefined -> + erlang:system_time(microsecond); + T -> + T + end, + Date = emqx_calendar:format(Time, microsecond, TZO, TZ, DFS), + [Date | logger_formatter:format(Event#{msg := Msg}, Config)]. maybe_merge({report, Report}, Meta) when is_map(Report) -> {report, maps:merge(Report, filter(Meta))}; @@ -61,150 +58,3 @@ maybe_merge(Report, _Meta) -> filter(Meta) -> maps:without(?WITHOUT_MERGE, Meta). - -date_format(undefined, Offset, OffsetStr, Formatter) -> - Time = erlang:system_time(microsecond), - date_format(Time, Offset, OffsetStr, Formatter); -date_format(Time, Offset, OffsetStr, Formatter) -> - Adjustment = erlang:convert_time_unit(Offset, second, microsecond), - AdjustedTime = Time + Adjustment, - %% Factor 1000000 for microsecond. - Factor = 1000000, - Secs = AdjustedTime div Factor, - DateTime = system_time_to_datetime(Secs), - {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, - FractionStr = fraction_str(Factor, AdjustedTime), - Date = #{ - year => padding(Year, 4), - month => padding(Month, 2), - day => padding(Day, 2), - hour => padding(Hour, 2), - minute => padding(Min, 2), - second => padding(Sec, 2), - nano_second => FractionStr, - timezone => OffsetStr - }, - [maps:get(Key, Date, Key) || Key <- Formatter]. - -system_time_to_datetime(Seconds) -> - gregorian_seconds_to_datetime(Seconds + ?SECONDS_FROM_0_TO_1970). - -gregorian_seconds_to_datetime(Secs) when Secs >= 0 -> - Days = Secs div ?SECONDS_PER_DAY, - Rest = Secs rem ?SECONDS_PER_DAY, - {gregorian_days_to_date(Days), seconds_to_time(Rest)}. - -seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY -> - Secs0 = Secs rem ?SECONDS_PER_DAY, - Hour = Secs0 div ?SECONDS_PER_HOUR, - Secs1 = Secs0 rem ?SECONDS_PER_HOUR, - Minute = Secs1 div ?SECONDS_PER_MINUTE, - Second = Secs1 rem ?SECONDS_PER_MINUTE, - {Hour, Minute, Second}. - -gregorian_days_to_date(Days) -> - {Year, DayOfYear} = day_to_year(Days), - {Month, DayOfMonth} = year_day_to_date(Year, DayOfYear), - {Year, Month, DayOfMonth}. - -day_to_year(DayOfEpoch) when DayOfEpoch >= 0 -> - YMax = DayOfEpoch div ?DAYS_PER_YEAR, - YMin = DayOfEpoch div ?DAYS_PER_LEAP_YEAR, - {Y1, D1} = dty(YMin, YMax, DayOfEpoch, dy(YMin), dy(YMax)), - {Y1, DayOfEpoch - D1}. - -year_day_to_date(Year, DayOfYear) -> - ExtraDay = - case is_leap_year(Year) of - true -> - 1; - false -> - 0 - end, - {Month, Day} = year_day_to_date2(ExtraDay, DayOfYear), - {Month, Day + 1}. - -dty(Min, Max, _D1, DMin, _DMax) when Min == Max -> - {Min, DMin}; -dty(Min, Max, D1, DMin, DMax) -> - Diff = Max - Min, - Mid = Min + Diff * (D1 - DMin) div (DMax - DMin), - MidLength = - case is_leap_year(Mid) of - true -> - ?DAYS_PER_LEAP_YEAR; - false -> - ?DAYS_PER_YEAR - end, - case dy(Mid) of - D2 when D1 < D2 -> - NewMax = Mid - 1, - dty(Min, NewMax, D1, DMin, dy(NewMax)); - D2 when D1 - D2 >= MidLength -> - NewMin = Mid + 1, - dty(NewMin, Max, D1, dy(NewMin), DMax); - D2 -> - {Mid, D2} - end. - -dy(Y) when Y =< 0 -> - 0; -dy(Y) -> - X = Y - 1, - X div 4 - X div 100 + X div 400 + X * ?DAYS_PER_YEAR + ?DAYS_PER_LEAP_YEAR. - -is_leap_year(Y) when is_integer(Y), Y >= 0 -> - is_leap_year1(Y). - -is_leap_year1(Year) when Year rem 4 =:= 0, Year rem 100 > 0 -> - true; -is_leap_year1(Year) when Year rem 400 =:= 0 -> - true; -is_leap_year1(_) -> - false. - -year_day_to_date2(_, Day) when Day < 31 -> - {1, Day}; -year_day_to_date2(E, Day) when 31 =< Day, Day < 59 + E -> - {2, Day - 31}; -year_day_to_date2(E, Day) when 59 + E =< Day, Day < 90 + E -> - {3, Day - (59 + E)}; -year_day_to_date2(E, Day) when 90 + E =< Day, Day < 120 + E -> - {4, Day - (90 + E)}; -year_day_to_date2(E, Day) when 120 + E =< Day, Day < 151 + E -> - {5, Day - (120 + E)}; -year_day_to_date2(E, Day) when 151 + E =< Day, Day < 181 + E -> - {6, Day - (151 + E)}; -year_day_to_date2(E, Day) when 181 + E =< Day, Day < 212 + E -> - {7, Day - (181 + E)}; -year_day_to_date2(E, Day) when 212 + E =< Day, Day < 243 + E -> - {8, Day - (212 + E)}; -year_day_to_date2(E, Day) when 243 + E =< Day, Day < 273 + E -> - {9, Day - (243 + E)}; -year_day_to_date2(E, Day) when 273 + E =< Day, Day < 304 + E -> - {10, Day - (273 + E)}; -year_day_to_date2(E, Day) when 304 + E =< Day, Day < 334 + E -> - {11, Day - (304 + E)}; -year_day_to_date2(E, Day) when 334 + E =< Day -> - {12, Day - (334 + E)}. - -fraction_str(1, _Time) -> - ""; -fraction_str(Factor, Time) -> - Fraction = Time rem Factor, - S = integer_to_list(abs(Fraction)), - [padding(S, log10(Factor) - length(S))]. - -log10(1000) -> - 3; -log10(1000000) -> - 6; -log10(1000000000) -> - 9. - -padding(Data, Len) when is_integer(Data) -> - padding(integer_to_list(Data), Len); -padding(Data, Len) when Len > 0 andalso erlang:length(Data) < Len -> - [$0 | padding(Data, Len - 1)]; -padding(Data, _Len) -> - Data.