|
|
@ -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. |
|
|
|
find_file(File1, Handlers). |