rewrite from lager
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

294 satır
13 KiB

4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
  1. -module(rumBkdFile).
  2. %% @doc File backend for lager, with multiple file support.
  3. %% Multiple files are supported, each with the path and the loglevel being configurable.
  4. %% The configuration paramter for this backend is a list of key-value 2-tuples. See the init() function for the available options.
  5. %% This backend supports external and internal log rotation and will re-open handles to files if the inode changes.
  6. %% It will also rotate the files itself if the size of the file exceeds the `size' and keep `count' rotated files.
  7. %% `date' is an alternate rotation trigger, based on time. See the README for documentation.
  8. %% For performance, the file backend does delayed writes, although it will sync at specific log levels, configured via the `sync_on' option.
  9. %% By default the error level or above will trigger a sync.
  10. -include("rumDef.hrl").
  11. -include_lib("kernel/include/file.hrl").
  12. -behaviour(gen_emm).
  13. -export([configToId/1]).
  14. -export([
  15. init/1
  16. , handleCall/2
  17. , handleEvent/2
  18. , handleInfo/2
  19. , terminate/2
  20. , code_change/3
  21. ]).
  22. -record(state, {
  23. fileName :: string(),
  24. level :: rumMaskLevel(),
  25. fd :: file:io_device() | undefined,
  26. inode :: integer() | undefined,
  27. ctime :: file:date_time() | undefined,
  28. flap = false :: boolean(),
  29. size = 0 :: integer(),
  30. date :: undefined | string(),
  31. count = 10 :: integer(),
  32. rotator = lager_util :: atom(),
  33. shaper :: rumShaper(),
  34. formatter :: atom(),
  35. formatterConfig :: any(),
  36. syncOn :: integer(),
  37. checkInterval = ?RumDefCheckInterval :: non_neg_integer(), %% 单位毫秒
  38. syncInterval = ?RumDefSyncInterval :: non_neg_integer(),
  39. syncSize = ?RumDefSyncSize :: non_neg_integer(),
  40. lastCheck = rumTime:nowMs() :: erlang:timestamp(), %% 单位毫秒
  41. osType :: atom()
  42. }).
  43. -spec init([rumFileOpt(), ...]) -> {ok, #state{}} | {error, atom()}.
  44. init(Opts) ->
  45. true = checkOpts(Opts, false),
  46. RelName = rumUtil:get_opt(file, Opts, undefined),
  47. CfgLevel = rumUtil:get_opt(level, Opts, ?RumDefLogLevel),
  48. CfgDate = rumUtil:get_opt(date, Opts, ?RumDefRotateDate),
  49. Size = rumUtil:get_opt(size, Opts, ?RumDefRotateSize),
  50. Count = rumUtil:get_opt(count, Opts, ?RumDefRotateCnt),
  51. Rotator = rumUtil:get_opt(rotator, Opts, ?RumDefRotateMod),
  52. HighWaterMark = rumUtil:get_opt(high_water_mark, Opts, ?RumDefCheckHWM),
  53. Flush = rumUtil:get_opt(flush_queue, Opts, ?RumDefFlushQueue),
  54. FlushThr = rumUtil:get_opt(flush_threshold, Opts, ?RumDefFlushThreshold),
  55. SyncInterval = rumUtil:get_opt(sync_interval, Opts, ?RumDefSyncInterval),
  56. CfgCheckInterval = rumUtil:get_opt(check_interval, Opts, ?RumDefCheckInterval),
  57. SyncSize = rumUtil:get_opt(sync_size, Opts, ?RumDefSyncSize),
  58. CfgSyncOn = rumUtil:get_opt(sync_on, Opts, ?RumDefSyncLevel),
  59. Formatter = rumUtil:get_opt(formatter, Opts, ?RumDefFormatter),
  60. FormatterConfig = rumUtil:get_opt(formatter_config, Opts, ?RumDefFormatterCfg),
  61. %% 需要二次转换的配置在这里处理
  62. Level = rumUtil:configToMask(CfgLevel),
  63. SyncOn = rumUtil:configToMask(CfgSyncOn),
  64. CheckInterval = ?IIF(CfgCheckInterval == always, 0, CfgCheckInterval),
  65. {ok, Date} = rumUtil:parseRotateSpec(CfgDate),
  66. FileName = rumUtil:parsePath(RelName),
  67. scheduleRotation(Date, FileName),
  68. Shaper = rumUtil:maybeFlush(Flush, #rumShaper{hwm = HighWaterMark, flushThreshold = FlushThr, id = FileName}),
  69. TemState = #state{
  70. fileName = FileName, level = Level, size = Size, date = Date
  71. , count = Count, rotator = Rotator, shaper = Shaper
  72. , formatter = Formatter, formatterConfig = FormatterConfig
  73. , syncOn = SyncOn, syncInterval = SyncInterval
  74. , syncSize = SyncSize, checkInterval = CheckInterval
  75. },
  76. case Rotator:createLogFile(FileName, {SyncSize, SyncInterval}) of
  77. {ok, Fd, Inode, CTime, _Size} ->
  78. {ok, TemState#state{fd = Fd, inode = Inode, ctime = CTime}};
  79. {error, Reason} ->
  80. ?INT_LOG(error, "Failed to open log file ~ts with error ~s", [FileName, file:format_error(Reason)]),
  81. {ok, TemState#state{flap = true}}
  82. end.
  83. handleCall(mGetLogLevel, #state{level = Level} = State) ->
  84. {reply, Level, State};
  85. handleCall({mSetLogLevel, Level}, #state{fileName = Ident} = State) ->
  86. case rumUtil:validateLogLevel(Level) of
  87. false ->
  88. {reply, {error, bad_loglevel}, State};
  89. LevelMask ->
  90. ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]),
  91. {reply, ok, State#state{level = LevelMask}}
  92. end;
  93. handleCall({mSetLogHwm, Hwm}, #state{shaper = Shaper, fileName = FileName} = State) ->
  94. case checkOpts([{high_water_mark, Hwm}], true) of
  95. false ->
  96. {reply, {error, bad_log_hwm}, State};
  97. _ ->
  98. NewShaper = Shaper#rumShaper{hwm = Hwm},
  99. ?INT_LOG(notice, "Changed loghwm of ~ts to ~p", [FileName, Hwm]),
  100. {reply, {last_loghwm, Shaper#rumShaper.hwm}, State#state{shaper = NewShaper}}
  101. end;
  102. handleCall(mRotate, State = #state{fileName = File}) ->
  103. {ok, NewState} = handleInfo({mRotate, File}, State),
  104. {reply, ok, NewState};
  105. handleCall(_Msg, State) ->
  106. ?ERR("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  107. {reply, ok, State}.
  108. handleEvent({mWriteLog, Message}, #state{fileName = FileName, level = Level, shaper = Shaper, formatter = Formatter, formatterConfig = FormatConfig} = State) ->
  109. case rumUtil:isLoggAble(Message, Level, {rumBkdFile, FileName}) of
  110. true ->
  111. case rumUtil:checkHwm(Shaper) of
  112. {true, _Drop, NewShaper} ->
  113. {ok, writeLog(State#state{shaper = NewShaper}, rumMsg:timestamp(Message), rumMsg:severity_as_int(Message), Formatter:format(Message, FormatConfig))};
  114. {drop, Drop, NewShaper} ->
  115. TemState =
  116. case Drop =< 0 of
  117. true ->
  118. State;
  119. _ ->
  120. Report = eFmt:format(<<"rumBkdFile dropped ~p messages in the last second that exceeded the limit of ~p messages/sec">>, [Drop, NewShaper#rumShaper.hwm]),
  121. ReportMsg = rumMsg:new(Report, warning, [], []),
  122. writeLog(State, rumMsg:timestamp(ReportMsg), rumMsg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig))
  123. end,
  124. {ok, writeLog(TemState#state{shaper = NewShaper}, rumMsg:timestamp(Message), rumMsg:severity_as_int(Message), Formatter:format(Message, FormatConfig))};
  125. {false, _, NewShaper} ->
  126. {ok, State#state{shaper = NewShaper}}
  127. end;
  128. _ ->
  129. kpS
  130. end;
  131. handleEvent(_Msg, _State) ->
  132. ?ERR("~p event receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  133. kpS.
  134. handleInfo({mRotate, File}, #state{fileName = File, count = Count, date = Date, rotator = Rotator} = State) ->
  135. NewState = closeFile(State),
  136. _ = Rotator:rotateLogFile(File, Count),
  137. scheduleRotation(File, Date),
  138. {ok, NewState};
  139. handleInfo({mShaperExpired, Name}, #state{shaper = Shaper, fileName = Name, formatter = Formatter, formatterConfig = FormatConfig} = State) ->
  140. case Shaper#rumShaper.dropped of
  141. 0 ->
  142. ignore;
  143. Dropped ->
  144. Report = eFmt:format(<<"rumBkdFile dropped ~p messages in the last second that exceeded the limit of ~p messages/sec">>, [Dropped, Shaper#rumShaper.hwm]),
  145. ReportMsg = rumMsg:new(Report, warning, [], []),
  146. writeLog(State, rumMsg:timestamp(ReportMsg), rumMsg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig))
  147. end,
  148. {ok, State#state{shaper = Shaper#rumShaper{dropped = 0, mps = 0, lastTime = rumTime:now()}}};
  149. handleInfo(_Msg, _State) ->
  150. ?ERR("~p info receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  151. kpS.
  152. terminate(_Reason, State) ->
  153. %% leaving this function call unmatched makes dialyzer cranky
  154. _ = closeFile(State),
  155. ok.
  156. code_change(_OldVsn, State, _Extra) ->
  157. {ok, State}.
  158. writeLog(#state{fileName = FileName, fd = Fd, inode = Inode, ctime = CTime, flap = Flap, size = RotSize, count = Count, rotator = Rotator, lastCheck = LastCheck, checkInterval = CheckInterval, syncSize = SyncSize, syncInterval = SyncInterval} = State, Timestamp, Level, Msg) ->
  159. case isWriteCheck(Fd, LastCheck, CheckInterval, FileName, Inode, CTime, Timestamp) of
  160. true ->
  161. %% need to check for rotation
  162. case Rotator:ensureLogFile(FileName, Fd, Inode, CTime, {SyncSize, SyncInterval}) of
  163. {ok, NewFD, NewInode, NewCTime, FileSize} ->
  164. case RotSize > 0 andalso FileSize > RotSize of
  165. true ->
  166. TemState = closeFile(State),
  167. case Rotator:rotateLogFile(FileName, Count) of
  168. ok ->
  169. %% go around the loop again, we'll do another rotation check and hit the next clause of ensureLogFile
  170. writeLog(TemState, Timestamp, Level, Msg);
  171. {error, Reason} ->
  172. ?IIF(Flap, State, begin ?INT_LOG(error, "Failed to rotate log file ~ts with error ~s", [FileName, file:format_error(Reason)]), State#state{flap = true} end)
  173. end;
  174. _ ->
  175. %% update our last check and try again
  176. TemState = State#state{lastCheck = Timestamp, fd = NewFD, inode = NewInode, ctime = NewCTime},
  177. writeFile(TemState, Level, Msg)
  178. end;
  179. {error, Reason} ->
  180. ?IIF(Flap, State, begin ?INT_LOG(error, "Failed to reopen log file ~ts with error ~s", [FileName, file:format_error(Reason)]), State#state{flap = true} end)
  181. end;
  182. _ ->
  183. writeFile(State, Level, Msg)
  184. end.
  185. writeFile(#state{fd = Fd, fileName = FileName, flap = Flap, syncOn = SyncOn} = State, Level, Msg) ->
  186. %% delayed_write doesn't report errors
  187. _ = file:write(Fd, unicode:characters_to_binary(Msg)),
  188. case (Level band SyncOn) =/= 0 of
  189. true ->
  190. %% force a sync on any message that matches the 'syncOn' bitmask
  191. NewFlap =
  192. case file:datasync(Fd) of
  193. {error, Reason} when Flap == false ->
  194. ?INT_LOG(error, "Failed to write log message to file ~ts: ~s", [FileName, file:format_error(Reason)]),
  195. true;
  196. ok ->
  197. false;
  198. _ ->
  199. Flap
  200. end,
  201. State#state{flap = NewFlap};
  202. _ ->
  203. State
  204. end.
  205. isWriteCheck(Fd, LastCheck, CheckInterval, Name, Inode, CTime, Timestamp) ->
  206. DiffTime = abs(Timestamp - LastCheck),
  207. case DiffTime >= CheckInterval orelse Fd == undefined of
  208. true ->
  209. true;
  210. _ ->
  211. % We need to know if the file has changed "out from under lager" so we don't write to an invalid Fd
  212. {Result, _FInfo} = rumUtil:isFileChanged(Name, Inode, CTime),
  213. Result
  214. end.
  215. %% Convert the config into a gen_event handler ID
  216. configToId(Config) ->
  217. case rumUtil:get_opt(file, Config, undefined) of
  218. undefined ->
  219. erlang:error(no_file);
  220. File ->
  221. {?MODULE, File}
  222. end.
  223. checkOpts([], IsFile) ->
  224. ?IIF(IsFile, true, {error, no_file_name});
  225. checkOpts([{file, _File} | Tail], _IsFile) ->
  226. checkOpts(Tail, true);
  227. checkOpts([{level, Level} | Tail], IsFile) ->
  228. ?IIF(rumUtil:validateLogLevel(Level) /= false, checkOpts(Tail, IsFile), {error, {invalid_log_level, Level}});
  229. checkOpts([{size, Size} | Tail], IsFile) when is_integer(Size), Size >= 0 ->
  230. checkOpts(Tail, IsFile);
  231. checkOpts([{count, Count} | Tail], IsFile) when is_integer(Count), Count >= 0 ->
  232. checkOpts(Tail, IsFile);
  233. checkOpts([{rotator, Rotator} | Tail], IsFile) when is_atom(Rotator) ->
  234. checkOpts(Tail, IsFile);
  235. checkOpts([{high_water_mark, HighWaterMark} | Tail], IsFile) when is_integer(HighWaterMark), HighWaterMark >= 0 ->
  236. checkOpts(Tail, IsFile);
  237. checkOpts([{date, _Date} | Tail], IsFile) ->
  238. checkOpts(Tail, IsFile);
  239. checkOpts([{sync_interval, SyncInt} | Tail], IsFile) when is_integer(SyncInt), SyncInt >= 0 ->
  240. checkOpts(Tail, IsFile);
  241. checkOpts([{sync_size, SyncSize} | Tail], IsFile) when is_integer(SyncSize), SyncSize >= 0 ->
  242. checkOpts(Tail, IsFile);
  243. checkOpts([{check_interval, CheckInt} | Tail], IsFile) when is_integer(CheckInt), CheckInt >= 0; CheckInt == always ->
  244. checkOpts(Tail, IsFile);
  245. checkOpts([{sync_on, Level} | Tail], IsFile) ->
  246. ?IIF(rumUtil:validateLogLevel(Level) /= false, checkOpts(Tail, IsFile), {error, {invalid_sync_on, Level}});
  247. checkOpts([{formatter, Fmt} | Tail], IsFile) when is_atom(Fmt) ->
  248. checkOpts(Tail, IsFile);
  249. checkOpts([{formatter_config, FmtCfg} | Tail], IsFile) when is_list(FmtCfg) ->
  250. checkOpts(Tail, IsFile);
  251. checkOpts([{flush_queue, FlushCfg} | Tail], IsFile) when is_boolean(FlushCfg) ->
  252. checkOpts(Tail, IsFile);
  253. checkOpts([{flush_threshold, Thr} | Tail], IsFile) when is_integer(Thr), Thr >= 0 ->
  254. checkOpts(Tail, IsFile);
  255. checkOpts([Other | _Tail], _IsFile) ->
  256. {error, {invalid_opt, Other}}.
  257. scheduleRotation(undefined, _FileName) ->
  258. ok;
  259. scheduleRotation(Date, Name) ->
  260. erlang:send_after(rumUtil:calcNextRotateMs(Date), self(), {mRotate, Name}),
  261. ok.
  262. closeFile(#state{fd = Fd} = State) ->
  263. case Fd of
  264. undefined -> State;
  265. _ ->
  266. %% Flush and close any file handles.
  267. %% delayed write can cause file:close not to do a close
  268. _ = file:datasync(Fd),
  269. _ = file:close(Fd),
  270. _ = file:close(Fd),
  271. State#state{fd = undefined}
  272. end.