rewrite from lager
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

336 行
15 KiB

4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
4 年前
  1. -module(rumCrashLog).
  2. %% @doc eRum crash log writer。它将error_logger错误消息以其原始格式发送到`crash_log`指定的文件, 如果`crash_log`未设置则禁用crash logging
  3. %% Crash logs are printed safely using trunc_io via code mostly lifted from riak_err.
  4. %% `crashLogMsgSize` 应用程序var用于指定最大值要记录的任何消息的大小。
  5. %% `crashLogFileSize` 用于指定崩溃日志的最大大小,然后将其旋转(0将禁用)。
  6. %% `crashLogDate` 基于时间的轮播可通过配置,语法为自述文件中记录了
  7. %% `crashLogCount` 控制要旋转的文件数已保留。
  8. -behaviour(gen_srv).
  9. -include("eRum.hrl").
  10. -ifdef(TEST).
  11. -include_lib("eunit/include/eunit.hrl").
  12. -include_lib("kernel/include/file.hrl").
  13. -endif.
  14. -export([
  15. start/6
  16. , start_link/6
  17. ]).
  18. -export([
  19. init/1
  20. , handleCall/3
  21. , handleCast/2
  22. , handleInfo/2
  23. , terminate/2
  24. , code_change/3
  25. ]).
  26. -record(state, {
  27. fileName :: string() %% 文件名
  28. , fd :: pid() | undefined %% 文件句柄
  29. , inode :: integer() | undefined %% 文件inode信息
  30. , ctime :: file:date_time() | undefined %% 文件创建时间
  31. , maxFmtSize :: integer() %% 单个消息最大字节数
  32. , maxFileSize :: integer() %% 单个日志文件最大字节数
  33. , date :: undefined | string() %% 旋转的时间格式
  34. , count :: integer() %% 要保留的已轮转崩溃日志的数量
  35. , flap = false :: boolean() %% ???? 这是干啥的呢
  36. , rotator :: atom() %% 旋转模块实例
  37. }).
  38. start(Filename, MaxFmtSize, MaxFileSize, Date, Count, Rotator) ->
  39. gen_srv:start({local, ?MODULE}, ?MODULE, {Filename, MaxFmtSize, MaxFileSize, Date, Count, Rotator}, []).
  40. start_link(Filename, MaxFmtSize, MaxFileSize, Date, Count, Rotator) ->
  41. gen_srv:start_link({local, ?MODULE}, ?MODULE, {Filename, MaxFmtSize, MaxFileSize, Date, Count, Rotator}, []).
  42. init({RelFilename, MaxFmtSize, MaxFileSize, CfgDate, Count, Rotator} = A) ->
  43. {ok, Date} = rumUtil:parseRotateSpec(CfgDate),
  44. Filename = rumUtil:parsePath(RelFilename),
  45. case Rotator:openLogFile(Filename, false) of
  46. {ok, Fd, Inode, CTime, _Size} ->
  47. scheduleRotation(Date),
  48. {ok, #state{fileName = Filename, fd = Fd, inode = Inode, ctime = CTime, maxFmtSize = MaxFmtSize, maxFileSize = MaxFileSize, date = Date, count = Count, rotator = Rotator}};
  49. {error, Reason} ->
  50. ?INT_LOG(error, "Failed to open crash log file ~ts with error: ~s", [Filename, file:format_error(Reason)]),
  51. {ok, #state{fileName = Filename, maxFmtSize = MaxFmtSize, maxFileSize = MaxFileSize, date = Date, count = Count, flap = true, rotator = Rotator}}
  52. end.
  53. handleCall({mWriteLog, Event}, State, _From) ->
  54. {Reply, NewState} = writeLog(Event, State),
  55. {reply, Reply, NewState};
  56. handleCall(_Msg, _State, _From) ->
  57. ?ERR("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  58. {reply, ok}.
  59. handleCast(_Msg, _State) ->
  60. ?ERR("~p cast receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  61. kpS.
  62. handleInfo({mWriteLog, Event}, State) ->
  63. {_, NewState} = writeLog(Event, State),
  64. {noreply, NewState};
  65. handleInfo(mRotate, #state{fileName = Name, count = Count, date = Date, rotator = Rotator}) ->
  66. _ = Rotator:rotateLogFile(Name, Count),
  67. scheduleRotation(Date),
  68. kpS;
  69. handleInfo(_Msg, _State) ->
  70. ?ERR("~p info receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  71. kpS.
  72. terminate(_Reason, _State) ->
  73. ok.
  74. code_change(_OldVsn, State, _Extra) ->
  75. {ok, State}.
  76. scheduleRotation(undefined) -> ok;
  77. scheduleRotation(Date) ->
  78. erlang:send_after(rumUtil:calcNextRotateMs(Date), self(), mRotate),
  79. ok.
  80. otherNodeSuffix(Pid) when node(Pid) =/= node() ->
  81. PidNode = node(Pid),
  82. case PidNode =/= node() of
  83. true ->
  84. <<"** at node ", (atom_to_binary(node(Pid), utf8))/binary, " **\n">>;
  85. _ ->
  86. <<"">>
  87. end.
  88. perhapsSaslReport(error_report, {Pid, Type, Report}, FmtMaxBytes) ->
  89. case rumStdlib:isErrorReport(Type) of
  90. true ->
  91. {saslTypeToReportHead(Type), Pid, saslLimitedStr(Type, Report, FmtMaxBytes), true};
  92. false ->
  93. {ignore, ignore, ignore, false}
  94. end;
  95. %perhaps_a_sasl_report(info_report, {Pid, Type, Report}, FmtMaxBytes) ->
  96. %case lager_stdlib:is_my_info_report(Type) of
  97. %true ->
  98. %{sasl_type_to_report_head(Type), Pid,
  99. %sasl_limited_str(Type, Report, FmtMaxBytes), false};
  100. %false ->
  101. %{ignore, ignore, ignore, false}
  102. %end;
  103. perhapsSaslReport(_, _, _) ->
  104. {ignore, ignore, ignore, false}.
  105. saslTypeToReportHead(supervisor_report) ->
  106. <<"SUPERVISOR REPORT">>;
  107. saslTypeToReportHead(crash_report) ->
  108. <<"CRASH REPORT">>;
  109. saslTypeToReportHead(progress) ->
  110. <<"PROGRESS REPORT">>.
  111. saslLimitedStr(supervisor_report, Report, FmtMaxBytes) ->
  112. Name = rumStdlib:sup_get(supervisor, Report),
  113. Context = rumStdlib:sup_get(errorContext, Report),
  114. Reason = rumStdlib:sup_get(reason, Report),
  115. Offender = rumStdlib:sup_get(offender, Report),
  116. FmtString = <<" Supervisor: ~p~n Context: ~p~n Reason: "
  117. "~s~n Offender: ~s~n~n">>,
  118. ReasonStr = eFmt:formatBin(<<"~p">>, [Reason], [{charsLimit, FmtMaxBytes}]),
  119. OffenderStr = eFmt:formatBin(<<"~p">>, [Offender], [{charsLimit, FmtMaxBytes}]),
  120. eFmt:format(FmtString, [Name, Context, ReasonStr, OffenderStr]);
  121. saslLimitedStr(progress, Report, FmtMaxBytes) ->
  122. [begin
  123. BinStr = eFmt:formatBin(<<"~p">>, [Data], [{charsLimit, FmtMaxBytes}]),
  124. eFmt:formatBin(" ~16w: ~s~n", [Tag, BinStr])
  125. end || {Tag, Data} <- Report];
  126. saslLimitedStr(crash_report, Report, FmtMaxBytes) ->
  127. rumStdlib:proc_lib_format(Report, FmtMaxBytes).
  128. writeLog(Event, #state{fileName = FileName, fd = FD, inode = Inode, ctime = Ctime, flap = Flap, maxFmtSize = FmtMaxBytes, maxFileSize = RotSize, count = Count, rotator = Rotator} = State) ->
  129. %% borrowed from riak_err
  130. {ReportStr, Pid, MsgStr, _ErrorP} =
  131. case Event of
  132. {error, _GL, {Pid1, Fmt, Args}} ->
  133. {<<"ERROR REPORT">>, Pid1, eFmt:formatBin(Fmt, Args, [{charsLimit, FmtMaxBytes}]), true};
  134. {error_report, _GL, {Pid1, std_error, Rep}} ->
  135. {<<"ERROR REPORT">>, Pid1, eFmt:formatBin(<<"~p\n">>, [Rep], [{charsLimit, FmtMaxBytes}]), true};
  136. {error_report, _GL, Other} ->
  137. perhapsSaslReport(error_report, Other, FmtMaxBytes);
  138. _ ->
  139. {ignore, ignore, ignore, false}
  140. end,
  141. case ReportStr of
  142. ignore ->
  143. {ok, State};
  144. _ ->
  145. case Rotator:ensureLogFile(FileName, FD, Inode, Ctime, false) of
  146. {ok, NewFD, NewInode, NewCtime, FileSize} ->
  147. case RotSize > 0 andalso FileSize > RotSize of
  148. true ->
  149. _ = Rotator:rotateLogFile(FileName, Count),
  150. handleCast({mWriteLog, Event}, State);
  151. _ ->
  152. TimeBinStr = rumUtil:msToBinStr(),
  153. Time = [TimeBinStr, <<" =">>, ReportStr, <<"====\n">>],
  154. NodeSuffix = otherNodeSuffix(Pid),
  155. Msg = eFmt:formatBin("~s~s~s", [Time, MsgStr, NodeSuffix]),
  156. case file:write(NewFD, unicode:characters_to_binary(Msg)) of
  157. {error, Reason} when Flap == false ->
  158. ?INT_LOG(error, "Failed to write log message to file ~ts: ~s", [FileName, file:format_error(Reason)]),
  159. {ok, State#state{fd = NewFD, inode = NewInode, ctime = NewCtime, flap = true}};
  160. ok ->
  161. {ok, State#state{fd = NewFD, inode = NewInode, ctime = NewCtime, flap = false}};
  162. _ ->
  163. {ok, State#state{fd = NewFD, inode = NewInode, ctime = NewCtime}}
  164. end
  165. end;
  166. {error, Reason} ->
  167. ?IIF(Flap, {ok, State}, begin ?INT_LOG(error, "Failed to reopen crash log ~ts with error: ~s", [FileName, file:format_error(Reason)]), {ok, State#state{flap = true}} end)
  168. end
  169. end.
  170. -ifdef(TEST).
  171. filesystem_test_() ->
  172. {foreach,
  173. fun() ->
  174. {ok, TestDir} = rumUtil:create_test_dir(),
  175. CrashLog = filename:join(TestDir, "crash_test.log"),
  176. ok = rumUtil:safe_write_file(CrashLog, []),
  177. ok = error_logger:tty(false),
  178. ok = rumUtil:safe_application_load(lager),
  179. ok = application:set_env(lager, handlers, [{lager_test_backend, info}]),
  180. ok = application:set_env(lager, errLoggerRedirect, true),
  181. ok = application:unset_env(lager, crash_log),
  182. ok = eRum:start(),
  183. ok = timer:sleep(1000),
  184. ok = lager_test_backend:flush(),
  185. CrashLog
  186. end,
  187. fun(_CrashLog) ->
  188. case whereis(lager_crash_log) of
  189. P when is_pid(P) ->
  190. gen_server:stop(P);
  191. _ ->
  192. ok
  193. end,
  194. ok = application:stop(lager),
  195. ok = application:stop(goldrush),
  196. ok = rumUtil:delete_test_dir(),
  197. ok = error_logger:tty(true)
  198. end, [
  199. fun(CrashLog) ->
  200. {"under normal circumstances, file should be opened",
  201. fun() ->
  202. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  203. _ = gen_event:which_handlers(error_logger),
  204. sync_error_logger:error_msg("Test message\n"),
  205. {ok, Bin} = file:read_file(CrashLog),
  206. ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
  207. end}
  208. end,
  209. fun(CrashLog) ->
  210. {"file can't be opened on startup triggers an error message",
  211. fun() ->
  212. {ok, FInfo0} = file:read_file_info(CrashLog, [raw]),
  213. FInfo1 = FInfo0#file_info{mode = 0},
  214. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo1)),
  215. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  216. % Note: required on win32, do this early to prevent subsequent failures
  217. % from preventing cleanup
  218. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo0)),
  219. ?assertEqual(1, lager_test_backend:count()),
  220. {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
  221. ?assertEqual(
  222. "Failed to open crash log file " ++ CrashLog ++ " with error: permission denied",
  223. lists:flatten(Message))
  224. end}
  225. end,
  226. fun(CrashLog) ->
  227. {"file that becomes unavailable at runtime should trigger an error message",
  228. fun() ->
  229. case os:type() of
  230. {win32, _} ->
  231. % Note: test is skipped on win32 due to the fact that a file can't be deleted or renamed
  232. % while a process has an open file handle referencing it
  233. ok;
  234. _ ->
  235. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  236. ?assertEqual(0, lager_test_backend:count()),
  237. sync_error_logger:error_msg("Test message\n"),
  238. _ = gen_event:which_handlers(error_logger),
  239. ?assertEqual(1, lager_test_backend:count()),
  240. ?assertEqual(ok, file:delete(CrashLog)),
  241. ?assertEqual(ok, rumUtil:safe_write_file(CrashLog, "")),
  242. {ok, FInfo0} = file:read_file_info(CrashLog, [raw]),
  243. FInfo1 = FInfo0#file_info{mode = 0},
  244. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo1)),
  245. sync_error_logger:error_msg("Test message\n"),
  246. _ = gen_event:which_handlers(error_logger),
  247. % Note: required on win32, do this early to prevent subsequent failures
  248. % from preventing cleanup
  249. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo0)),
  250. ?assertEqual(3, lager_test_backend:count()),
  251. lager_test_backend:pop(),
  252. {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
  253. ?assertEqual(
  254. "Failed to reopen crash log " ++ CrashLog ++ " with error: permission denied",
  255. lists:flatten(Message))
  256. end
  257. end}
  258. end,
  259. fun(CrashLog) ->
  260. {"unavailable files that are fixed at runtime should start having log messages written",
  261. fun() ->
  262. {ok, FInfo} = file:read_file_info(CrashLog, [raw]),
  263. OldPerms = FInfo#file_info.mode,
  264. file:write_file_info(CrashLog, FInfo#file_info{mode = 0}),
  265. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  266. ?assertEqual(1, lager_test_backend:count()),
  267. {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
  268. ?assertEqual(
  269. "Failed to open crash log file " ++ CrashLog ++ " with error: permission denied",
  270. lists:flatten(Message)),
  271. file:write_file_info(CrashLog, FInfo#file_info{mode = OldPerms}),
  272. sync_error_logger:error_msg("Test message~n"),
  273. _ = gen_event:which_handlers(error_logger),
  274. {ok, Bin} = file:read_file(CrashLog),
  275. ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
  276. end}
  277. end,
  278. fun(CrashLog) ->
  279. {"external logfile rotation/deletion should be handled",
  280. fun() ->
  281. case os:type() of
  282. {win32, _} ->
  283. % Note: test is skipped on win32 due to the fact that a file can't be deleted or renamed
  284. % while a process has an open file handle referencing it
  285. ok;
  286. _ ->
  287. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  288. ?assertEqual(0, lager_test_backend:count()),
  289. sync_error_logger:error_msg("Test message~n"),
  290. _ = gen_event:which_handlers(error_logger),
  291. {ok, Bin} = file:read_file(CrashLog),
  292. ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}])),
  293. ?assertEqual(ok, file:delete(CrashLog)),
  294. ?assertEqual(ok, rumUtil:safe_write_file(CrashLog, "")),
  295. sync_error_logger:error_msg("Test message1~n"),
  296. _ = gen_event:which_handlers(error_logger),
  297. {ok, Bin1} = file:read_file(CrashLog),
  298. ?assertMatch([_, "Test message1\n"], re:split(Bin1, "\n", [{return, list}, {parts, 2}])),
  299. file:rename(CrashLog, CrashLog ++ ".0"),
  300. sync_error_logger:error_msg("Test message2~n"),
  301. _ = gen_event:which_handlers(error_logger),
  302. {ok, Bin2} = file:read_file(CrashLog),
  303. ?assertMatch([_, "Test message2\n"], re:split(Bin2, "\n", [{return, list}, {parts, 2}]))
  304. end
  305. end}
  306. end
  307. ]}.
  308. -endif.