diff --git a/include/lgDef.hrl b/include/lgDef.hrl index bcfbf2c..bff8133 100644 --- a/include/lgDef.hrl +++ b/include/lgDef.hrl @@ -24,33 +24,31 @@ %% lgBkdConsole的选项 -type lgConsoleOpt() :: - {id, atom() | {atom(), atom()}} | - {use_stderr, boolean()} | - {group_leader, false | pid() | atom()} | - {fmtTer, atom()} | - {fmtCfg, list()}. + {id, atom() | {atom(), atom()}} | %% 接收器 id + {level, lgAtomLevel() | atom()} | %% 接收器 等级级别 + {use_stderr, boolean()} | %% 错误输出io + {group_leader, false | pid() | atom()} | %% group_leader + {fmtTer, atom()} | %% 日志格式化的模块名 + {fmtCfg, list()}. %% 日志格式化的格式 %% lgBkdFile的选项 -type lgFileOpt() :: - {id, atom()} | - {file, binary()} | - {level, lgAtomLevel() | atom()} | - {size, non_neg_integer()} | - {date, string()} | - {count, non_neg_integer()} | - {rotator, atom()} | - {hwm, non_neg_integer()} | - %% 对于特定的接收器事件队列刷新,请使用改选项 - {flushQueue, boolean()} | - %% 对于接收器 如果flush_queue为true,则可以设置消息队列长度阈值,在该阈值处将开始丢弃消息。默认阈值为0, - %% 这意味着如果flush_queue为true,则超过高水位标记时将丢弃消息,而不管消息队列的长度如何。: - {flushThr, non_neg_integer()} | - {syncInt, non_neg_integer()} | - {syncSize, non_neg_integer()} | - {syncOn, lgAtomLevel()} | - {checkInt, non_neg_integer()} | - {fmtTer, atom()} | - {fmtCfg, term()}. + {id, atom()} | %% 接收器 id + {file, binary()} | %% base log file whole log name: logRoot ++ timeMin ++ "_" ++ this name + {level, lgAtomLevel() | atom()} | %% 接收器 等级级别 + {size, non_neg_integer()} | %% 单个日志文件最大的字节数 为0则不旋转日志文件 + {date, string()} | %% 日志旋转的日期配置 + {count, non_neg_integer()} | %% 无用 + {rotator, atom()} | %% 日志旋转模块 + {hwm, non_neg_integer()} | %% 每一秒能能接收的日志数量 undefined 则不限制每秒接受的日志数量 + {flushQueue, boolean()} | %% 对于特定的接收器事件队列刷新,对于接收器 请使用改选项 + {flushThr, non_neg_integer()} | %% 如果flush_queue为true,则可以设置消息队列长度阈值,如果超过该阈值处将开始丢弃消息。默认阈值为0,这意味着如果flush_queue为true,则超过高水位标记时将丢弃消息,而不管消息队列的长度如何。: + {syncInt, non_neg_integer()} | %% 日志文件同步间隔 + {syncSize, non_neg_integer()} | %% 日志文件同步大小 + {syncOn, lgAtomLevel()} | %% 日志文件立即同步的 日志登记级别 + {checkInt, non_neg_integer()} | %% 日志文件检查的时间间隔 + {fmtTer, atom()} | %% 日志格式化的模块 + {fmtCfg, term()}. %% 日志格式化的格式 %% BkdFile选项默认值 -define(LgDefLogLevel, info). @@ -73,8 +71,8 @@ -define(LgDefHandler, [ {lgBkdConsole, [{level, '>=info'}]}, - {lgBkdFile, [{id, error}, {file, <<"error.log">>}, {level, '>=error'}, {size, 10485760}, {date, "$D0"}, {count, 5}]}, - {lgBkdFile, [{id, console}, {file, <<"console.log">>}, {level, '>=debug'}, {size, 10485760}, {date, "$D0"}, {count, 5}]} + {lgBkdFile, [{id, error}, {file, <<"error.log">>}, {level, '>=error'}, {size, 10485760}, {date, <<"$D0">>}, {count, 5}]}, + {lgBkdFile, [{id, console}, {file, <<"console.log">>}, {level, '>=debug'}, {size, 10485760}, {date, <<"$D0">>}, {count, 5}]} ]). -record(lgShaper, { diff --git a/src/utils/lgUtil.erl b/src/utils/lgUtil.erl index d26aab8..a95000d 100644 --- a/src/utils/lgUtil.erl +++ b/src/utils/lgUtil.erl @@ -24,13 +24,6 @@ , calcNextRotateMs/1 , calcNextRotateMs/2 , calcNextRotateDt/2 - , validate_trace/1 - , check_traces/4 - , isLoggAble/3 - , trace_filter/1 - , trace_filter/2 - , parsePath/1 - , find_file/2 , checkHwm/1 , checkHwm/2 , makeInnerSinkName/1 @@ -39,6 +32,13 @@ , get_env/2 , get_opt/3 , sup_get/2 + , isLoggAble/3 + , parsePath/1 + , validate_trace/1 + , check_traces/4 + , trace_filter/1 + , trace_filter/2 + , find_file/2 ]). levels() -> @@ -193,12 +193,10 @@ msToBinStr(MsTick) -> {{Y, M, D}, {H, Mi, S}} = erlang:universaltime_to_localtime(erlang:posixtime_to_universaltime(ThisSec)), <<(integer_to_binary(Y))/binary, "-", (i2b(M))/binary, "-", (i2b(D))/binary, " ", (i2b(H))/binary, ":", (i2b(Mi))/binary, ":", (i2b(S))/binary, ".", (i3b(ThisMs))/binary>>. - curYMDHMStr() -> {{Y, M, D}, {H, Mi, _S}} = lgTime:curDateTime(), <<(integer_to_binary(Y))/binary, (i2b(M))/binary, (i2b(D))/binary, (i2b(H))/binary, (i2b(Mi))/binary>>. - i2b(Num) -> if Num < 10 -> @@ -376,6 +374,142 @@ calcNextRotate({date, SHour, SMinute, SDate}, CurDate, CurTime) -> end end. + +%% conditionally check the Hwm if the event would not have been filtered +checkHwm(#lgShaper{filter = Filter} = Shaper, Event) -> + case Filter =/= undefined andalso Filter(Event) of + true -> + {true, 0, Shaper}; + _ -> + checkHwm(Shaper) + end. + +%% 日志速率限制S i.e. 即传入消息的高水位标记 +checkHwm(#lgShaper{id = Id, hwm = Hwm, mps = Mps, lastTime = LastTime, dropped = Drop, flushQueue = FlushQueue, flushThr = FlushThr, timer = Timer, filter = Filter} = Shaper) -> + if + Hwm == undefined -> + {true, 0, Shaper}; + Mps < Hwm -> + NowTime = lgTime:now(), + case LastTime == NowTime of + true -> + {true, 0, Shaper#lgShaper{mps = Mps + 1}}; + _ -> + %different second - reset mps + {true, 0, Shaper#lgShaper{mps = 1, lastTime = NowTime}} + end; + true -> + %% are we still in the same second? + NowTimeMs = lgTime:nowMs(), + NowTime = NowTimeMs div 1000, + case LastTime == NowTime of + true -> + PastMs = NowTimeMs rem 1000, + %% still in same second, but have exceeded the high water mark + NewDrops = ?IIF(isNeedFlush(FlushQueue, FlushThr), dropMsg(NowTime, Filter, 0), 0), + NewTimer = ?IIF(erlang:read_timer(Timer) =/= false, Timer, erlang:send_after(1000 - PastMs, self(), {mShaperExpired, Id})), + {false, 0, Shaper#lgShaper{dropped = Drop + NewDrops + 1, timer = NewTimer}}; + _ -> + _ = erlang:cancel_timer(Shaper#lgShaper.timer), + %% different second, reset all counters and allow it + {drop, Drop, Shaper#lgShaper{mps = 1, lastTime = NowTime}} + end + end. + +isNeedFlush(true, FlushThreshold) -> + case FlushThreshold of + 0 -> + true; + _ -> + PInfo = process_info(self(), message_queue_len), + element(2, PInfo) > FlushThreshold + end; +isNeedFlush(_FlushQueue, _FlushThreshold) -> + false. + +dropMsg(LastTime, Filter, Count) -> + CurTime = lgTime:now(), + case CurTime == LastTime of + true -> + receive + %% we only discard gen_event notifications, because + %% otherwise we might discard gen_event internal + %% messages, such as trapped EXITs + {'$gen_info', Event} -> + NewCount = ?IIF(Filter(Event), Count, Count + 1), + dropMsg(LastTime, Filter, NewCount) + after 0 -> + Count + end; + _ -> + Count + end. + +%% @private Build an atom for the gen_event process based on a sink name. +%% For historical reasons, the default gen_event process for eLog itself is named +%% `eLogEvent'. For all other sinks, it is SinkName++`_lgEvent' +makeInnerSinkName(Sink) -> + binary_to_atom(<<(atom_to_binary(Sink, utf8))/binary, "Event">>). + +-spec isFileChanged(FileName :: file:name_all(), Inode :: pos_integer(), Ctime :: file:date_time()) -> {boolean(), file:file_info() | undefined}. +isFileChanged(FileName, Inode, Ctime) -> + case file:read_file_info(FileName, [raw]) of + {ok, FileInfo} -> + case os:type() of + {win32, _} -> + % Note: on win32, Inode is always zero So check the file's ctime to see if it needs to be re-opened + {Ctime =/= FileInfo#file_info.ctime, FileInfo}; + _ -> + {Inode =/= FileInfo#file_info.inode, FileInfo} + end; + _ -> + {true, undefined} + end. + +-spec get_env(Par :: atom(), Def :: term()) -> Val :: term(). +get_env(Key, Def) -> + case application:get_env(?LgAppName, Key) of + {ok, Val} -> + Val; + _ -> + Def + end. + +get_opt(Key, Opts, Def) -> + case lists:keyfind(Key, 1, Opts) of + false -> + Def; + V -> + element(2, V) + end. + +-spec sup_get(term(), [proplists:property()]) -> term(). +sup_get(Tag, Report) -> + case lists:keysearch(Tag, 1, Report) of + {value, {_, Value}} -> + Value; + _ -> + "" + end. + +%% From OTP sasl's sasl_report.erl ... These functions aren't +%% exported. +-spec isErrorReport(atom()) -> boolean(). +isErrorReport(supervisor_report) -> true; +isErrorReport(crash_report) -> true; +isErrorReport(_) -> false. + +-spec isLoggAble(lgMsg(), lgMaskLevel(), term()) -> boolean(). +isLoggAble(LgMsg, Mask, MyName) -> + #lgMsg{severity = Severity, destinations = Destinations} = LgMsg, + (Severity band Mask) =/= 0 orelse lists:member(MyName, Destinations). + +parsePath(FBName) -> + LogRoot = lgUtil:get_env(logRoot, ?LgDefLogRoot), + TimeFileName = <<(lgUtil:curYMDHMStr())/binary, "_", FBName/binary>>, + WholeFileName = filename:join(LogRoot, TimeFileName), + filename:absname(WholeFileName). + -spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}. trace_filter(Query) -> trace_filter(?LgDefTracer, Query). @@ -486,17 +620,6 @@ check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) -> [] end. --spec isLoggAble(lgMsg(), lgMaskLevel(), term()) -> boolean(). -isLoggAble(LgMsg, Mask, MyName) -> - #lgMsg{severity = Severity, destinations = Destinations} = LgMsg, - (Severity band Mask) =/= 0 orelse lists:member(MyName, Destinations). - -parsePath(FBName) -> - LogRoot = lgUtil:get_env(logRoot, ?LgDefLogRoot), - TimeFileName = <<(lgUtil:curYMDHMStr())/binary, "_", FBName/binary>>, - WholeFileName = filename:join(LogRoot, TimeFileName), - filename:absname(WholeFileName). - %% Find a file among the already installed handlers. %% %% The file is already expanded (i.e. lgUtil:expand_path already added the @@ -515,128 +638,4 @@ find_file(File1, [{{lgBkdFile, File2}, _Handler, _Sink} = HandlerInfo | Handlers find_file(File1, Handlers) end; find_file(File1, [_HandlerInfo | Handlers]) -> - find_file(File1, Handlers). - -%% conditionally check the Hwm if the event would not have been filtered -checkHwm(#lgShaper{filter = Filter} = Shaper, Event) -> - case Filter =/= undefined andalso Filter(Event) of - true -> - {true, 0, Shaper}; - _ -> - checkHwm(Shaper) - end. - -%% 日志速率限制S i.e. 即传入消息的高水位标记 -checkHwm(#lgShaper{id = Id, hwm = Hwm, mps = Mps, lastTime = LastTime, dropped = Drop, flushQueue = FlushQueue, flushThr = FlushThr, timer = Timer, filter = Filter} = Shaper) -> - if - Hwm == undefined -> - {true, 0, Shaper}; - Mps < Hwm -> - NowTime = lgTime:now(), - case LastTime == NowTime of - true -> - {true, 0, Shaper#lgShaper{mps = Mps + 1}}; - _ -> - %different second - reset mps - {true, 0, Shaper#lgShaper{mps = 1, lastTime = NowTime}} - end; - true -> - %% are we still in the same second? - NowTimeMs = lgTime:nowMs(), - NowTime = NowTimeMs div 1000, - case LastTime == NowTime of - true -> - PastMs = NowTimeMs rem 1000, - %% still in same second, but have exceeded the high water mark - NewDrops = ?IIF(isNeedFlush(FlushQueue, FlushThr), dropMsg(NowTime, Filter, 0), 0), - NewTimer = ?IIF(erlang:read_timer(Timer) =/= false, Timer, erlang:send_after(1000 - PastMs, self(), {mShaperExpired, Id})), - {false, 0, Shaper#lgShaper{dropped = Drop + NewDrops + 1, timer = NewTimer}}; - _ -> - _ = erlang:cancel_timer(Shaper#lgShaper.timer), - %% different second, reset all counters and allow it - {drop, Drop, Shaper#lgShaper{mps = 1, lastTime = NowTime}} - end - end. - -isNeedFlush(true, FlushThreshold) -> - case FlushThreshold of - 0 -> - true; - _ -> - PInfo = process_info(self(), message_queue_len), - element(2, PInfo) > FlushThreshold - end; -isNeedFlush(_FlushQueue, _FlushThreshold) -> - false. - -dropMsg(LastTime, Filter, Count) -> - CurTime = lgTime:now(), - case CurTime == LastTime of - true -> - receive - %% we only discard gen_event notifications, because - %% otherwise we might discard gen_event internal - %% messages, such as trapped EXITs - {'$gen_info', Event} -> - NewCount = ?IIF(Filter(Event), Count, Count + 1), - dropMsg(LastTime, Filter, NewCount) - after 0 -> - Count - end; - _ -> - Count - end. - -%% @private Build an atom for the gen_event process based on a sink name. -%% For historical reasons, the default gen_event process for eLog itself is named -%% `eLogEvent'. For all other sinks, it is SinkName++`_lgEvent' -makeInnerSinkName(Sink) -> - binary_to_atom(<<(atom_to_binary(Sink, utf8))/binary, "Event">>). - --spec isFileChanged(FileName :: file:name_all(), Inode :: pos_integer(), Ctime :: file:date_time()) -> {boolean(), file:file_info() | undefined}. -isFileChanged(FileName, Inode, Ctime) -> - case file:read_file_info(FileName, [raw]) of - {ok, FileInfo} -> - case os:type() of - {win32, _} -> - % Note: on win32, Inode is always zero So check the file's ctime to see if it needs to be re-opened - {Ctime =/= FileInfo#file_info.ctime, FileInfo}; - _ -> - {Inode =/= FileInfo#file_info.inode, FileInfo} - end; - _ -> - {true, undefined} - end. - --spec get_env(Par :: atom(), Def :: term()) -> Val :: term(). -get_env(Key, Def) -> - case application:get_env(?LgAppName, Key) of - {ok, Val} -> - Val; - _ -> - Def - end. - -get_opt(Key, Opts, Def) -> - case lists:keyfind(Key, 1, Opts) of - false -> - Def; - V -> - element(2, V) - end. - --spec sup_get(term(), [proplists:property()]) -> term(). -sup_get(Tag, Report) -> - case lists:keysearch(Tag, 1, Report) of - {value, {_, Value}} -> - Value; - _ -> - "" - end. - -%% From OTP sasl's sasl_report.erl ... These functions aren't -%% exported. --spec isErrorReport(atom()) -> boolean(). -isErrorReport(supervisor_report) -> true; -isErrorReport(crash_report) -> true; -isErrorReport(_) -> false. \ No newline at end of file + find_file(File1, Handlers). \ No newline at end of file