25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

368 lines
16 KiB

14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
14 년 전
  1. %% -------------------------------------------------------------------
  2. %%
  3. %% Copyright (c) 2011-2017 Basho Technologies, Inc.
  4. %%
  5. %% This file is provided to you under the Apache License,
  6. %% Version 2.0 (the "License"); you may not use this file
  7. %% except in compliance with the License. You may obtain
  8. %% a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing,
  13. %% software distributed under the License is distributed on an
  14. %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. %% KIND, either express or implied. See the License for the
  16. %% specific language governing permissions and limitations
  17. %% under the License.
  18. %%
  19. %% -------------------------------------------------------------------
  20. %% @doc Lager crash log writer. This module implements a gen_server which writes
  21. %% error_logger error messages out to a file in their original format. The
  22. %% location to which it logs is configured by the application var `crash_log'.
  23. %% Omitting this variable disables crash logging. Crash logs are printed safely
  24. %% using trunc_io via code mostly lifted from riak_err.
  25. %%
  26. %% The `crash_log_msg_size' application var is used to specify the maximum
  27. %% size of any message to be logged. `crash_log_size' is used to specify the
  28. %% maximum size of the crash log before it will be rotated (0 will disable).
  29. %% Time based rotation is configurable via `crash_log_date', the syntax is
  30. %% documented in the README. To control the number of rotated files to be
  31. %% retained, use `crash_log_count'.
  32. -module(lager_crash_log).
  33. -include("lager.hrl").
  34. -behaviour(gen_server).
  35. -ifdef(TEST).
  36. -include_lib("eunit/include/eunit.hrl").
  37. -include_lib("kernel/include/file.hrl").
  38. -endif.
  39. %% callbacks
  40. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
  41. code_change/3]).
  42. -export([start_link/6, start/6]).
  43. -record(state, {
  44. name :: string(),
  45. fd :: pid() | undefined,
  46. inode :: integer() | undefined,
  47. ctime :: file:date_time() | undefined,
  48. fmtmaxbytes :: integer(),
  49. size :: integer(),
  50. date :: undefined | string(),
  51. count :: integer(),
  52. flap=false :: boolean(),
  53. rotator :: atom()
  54. }).
  55. %% @private
  56. start_link(Filename, MaxBytes, Size, Date, Count, Rotator) ->
  57. gen_server:start_link({local, ?MODULE}, ?MODULE, [Filename, MaxBytes,
  58. Size, Date, Count, Rotator], []).
  59. %% @private
  60. start(Filename, MaxBytes, Size, Date, Count, Rotator) ->
  61. gen_server:start({local, ?MODULE}, ?MODULE, [Filename, MaxBytes, Size,
  62. Date, Count, Rotator], []).
  63. %% @private
  64. init([RelFilename, MaxBytes, Size, Date, Count, Rotator]) ->
  65. Filename = lager_util:expand_path(RelFilename),
  66. case Rotator:open_logfile(Filename, false) of
  67. {ok, {FD, Inode, Ctime, _Size}} ->
  68. schedule_rotation(Date),
  69. {ok, #state{name=Filename, fd=FD, inode=Inode, ctime=Ctime,
  70. fmtmaxbytes=MaxBytes, size=Size, count=Count, date=Date,
  71. rotator=Rotator}};
  72. {error, Reason} ->
  73. ?INT_LOG(error, "Failed to open crash log file ~s with error: ~s",
  74. [Filename, file:format_error(Reason)]),
  75. {ok, #state{name=Filename, fmtmaxbytes=MaxBytes, flap=true,
  76. size=Size, count=Count, date=Date, rotator=Rotator}}
  77. end.
  78. %% @private
  79. handle_call({log, _} = Log, _From, State) ->
  80. {Reply, NewState} = do_log(Log, State),
  81. {reply, Reply, NewState};
  82. handle_call(_Call, _From, State) ->
  83. {reply, ok, State}.
  84. %% @private
  85. handle_cast({log, _} = Log, State) ->
  86. {_, NewState} = do_log(Log, State),
  87. {noreply, NewState};
  88. handle_cast(_Request, State) ->
  89. {noreply, State}.
  90. %% @private
  91. handle_info(rotate, #state{name=Name, count=Count, date=Date, rotator=Rotator} = State) ->
  92. _ = Rotator:rotate_logfile(Name, Count),
  93. schedule_rotation(Date),
  94. {noreply, State};
  95. handle_info(_Info, State) ->
  96. {noreply, State}.
  97. %% @private
  98. terminate(_Reason, _State) ->
  99. ok.
  100. %% @private
  101. code_change(_OldVsn, State, _Extra) ->
  102. {ok, State}.
  103. schedule_rotation(undefined) ->
  104. ok;
  105. schedule_rotation(Date) ->
  106. erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), rotate),
  107. ok.
  108. %% ===== Begin code lifted from riak_err =====
  109. -spec limited_fmt(string(), list(), integer()) -> iolist().
  110. %% @doc Format Fmt and Args similar to what io_lib:format/2 does but with
  111. %% limits on how large the formatted string may be.
  112. %%
  113. %% If the Args list's size is larger than TermMaxSize, then the
  114. %% formatting is done by trunc_io:print/2, where FmtMaxBytes is used
  115. %% to limit the formatted string's size.
  116. limited_fmt(Fmt, Args, FmtMaxBytes) ->
  117. lager:safe_format(Fmt, Args, FmtMaxBytes).
  118. limited_str(Term, FmtMaxBytes) ->
  119. {Str, _} = lager_trunc_io:print(Term, FmtMaxBytes),
  120. Str.
  121. other_node_suffix(Pid) when node(Pid) =/= node() ->
  122. "** at node " ++ atom_to_list(node(Pid)) ++ " **\n";
  123. other_node_suffix(_) ->
  124. "".
  125. perhaps_a_sasl_report(error_report, {Pid, Type, Report}, FmtMaxBytes) ->
  126. case lager_stdlib:is_my_error_report(Type) of
  127. true ->
  128. {sasl_type_to_report_head(Type), Pid,
  129. sasl_limited_str(Type, Report, FmtMaxBytes), true};
  130. false ->
  131. {ignore, ignore, ignore, false}
  132. end;
  133. %perhaps_a_sasl_report(info_report, {Pid, Type, Report}, FmtMaxBytes) ->
  134. %case lager_stdlib:is_my_info_report(Type) of
  135. %true ->
  136. %{sasl_type_to_report_head(Type), Pid,
  137. %sasl_limited_str(Type, Report, FmtMaxBytes), false};
  138. %false ->
  139. %{ignore, ignore, ignore, false}
  140. %end;
  141. perhaps_a_sasl_report(_, _, _) ->
  142. {ignore, ignore, ignore, false}.
  143. sasl_type_to_report_head(supervisor_report) ->
  144. "SUPERVISOR REPORT";
  145. sasl_type_to_report_head(crash_report) ->
  146. "CRASH REPORT";
  147. sasl_type_to_report_head(progress) ->
  148. "PROGRESS REPORT".
  149. sasl_limited_str(supervisor_report, Report, FmtMaxBytes) ->
  150. Name = lager_stdlib:sup_get(supervisor, Report),
  151. Context = lager_stdlib:sup_get(errorContext, Report),
  152. Reason = lager_stdlib:sup_get(reason, Report),
  153. Offender = lager_stdlib:sup_get(offender, Report),
  154. FmtString = " Supervisor: ~p~n Context: ~p~n Reason: "
  155. "~s~n Offender: ~s~n~n",
  156. {ReasonStr, _} = lager_trunc_io:print(Reason, FmtMaxBytes),
  157. {OffenderStr, _} = lager_trunc_io:print(Offender, FmtMaxBytes),
  158. io_lib:format(FmtString, [Name, Context, ReasonStr, OffenderStr]);
  159. sasl_limited_str(progress, Report, FmtMaxBytes) ->
  160. [begin
  161. {Str, _} = lager_trunc_io:print(Data, FmtMaxBytes),
  162. io_lib:format(" ~16w: ~s~n", [Tag, Str])
  163. end || {Tag, Data} <- Report];
  164. sasl_limited_str(crash_report, Report, FmtMaxBytes) ->
  165. lager_stdlib:proc_lib_format(Report, FmtMaxBytes).
  166. do_log({log, Event}, #state{name=Name, fd=FD, inode=Inode, ctime=Ctime, flap=Flap,
  167. fmtmaxbytes=FmtMaxBytes, size=RotSize, count=Count, rotator=Rotator} = State) ->
  168. %% borrowed from riak_err
  169. {ReportStr, Pid, MsgStr, _ErrorP} = case Event of
  170. {error, _GL, {Pid1, Fmt, Args}} ->
  171. {"ERROR REPORT", Pid1, limited_fmt(Fmt, Args, FmtMaxBytes), true};
  172. {error_report, _GL, {Pid1, std_error, Rep}} ->
  173. {"ERROR REPORT", Pid1, limited_str(Rep, FmtMaxBytes) ++ "\n", true};
  174. {error_report, _GL, Other} ->
  175. perhaps_a_sasl_report(error_report, Other, FmtMaxBytes);
  176. _ ->
  177. {ignore, ignore, ignore, false}
  178. end,
  179. if ReportStr == ignore ->
  180. {ok, State};
  181. true ->
  182. case Rotator:ensure_logfile(Name, FD, Inode, Ctime, false) of
  183. {ok, {_FD, _Inode, _Ctime, Size}} when RotSize /= 0, Size > RotSize ->
  184. _ = Rotator:rotate_logfile(Name, Count),
  185. handle_cast({log, Event}, State);
  186. {ok, {NewFD, NewInode, NewCtime, _Size}} ->
  187. {Date, TS} = lager_util:format_time(
  188. lager_stdlib:maybe_utc(erlang:localtime())),
  189. Time = [Date, " ", TS," =", ReportStr, "====\n"],
  190. NodeSuffix = other_node_suffix(Pid),
  191. Msg = io_lib:format("~s~s~s", [Time, MsgStr, NodeSuffix]),
  192. case file:write(NewFD, unicode:characters_to_binary(Msg)) of
  193. {error, Reason} when Flap == false ->
  194. ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
  195. [Name, file:format_error(Reason)]),
  196. {ok, State#state{fd=NewFD, inode=NewInode, ctime=NewCtime, flap=true}};
  197. ok ->
  198. {ok, State#state{fd=NewFD, inode=NewInode, ctime=NewCtime, flap=false}};
  199. _ ->
  200. {ok, State#state{fd=NewFD, inode=NewInode, ctime=NewCtime}}
  201. end;
  202. {error, Reason} ->
  203. case Flap of
  204. true ->
  205. {ok, State};
  206. _ ->
  207. ?INT_LOG(error, "Failed to reopen crash log ~s with error: ~s",
  208. [Name, file:format_error(Reason)]),
  209. {ok, State#state{flap=true}}
  210. end
  211. end
  212. end.
  213. -ifdef(TEST).
  214. filesystem_test_() ->
  215. {foreach,
  216. fun() ->
  217. {ok, TestDir} = lager_util:get_test_dir(),
  218. CrashLog = filename:join(TestDir, "crash_test.log"),
  219. ok = lager_test_util:safe_write_file(CrashLog, []),
  220. ok = error_logger:tty(false),
  221. ok = lager_test_util:safe_application_load(lager),
  222. ok = application:set_env(lager, handlers, [{lager_test_backend, info}]),
  223. ok = application:set_env(lager, error_logger_redirect, true),
  224. ok = application:unset_env(lager, crash_log),
  225. ok = lager:start(),
  226. ok = timer:sleep(1000),
  227. ok = lager_test_backend:flush(),
  228. CrashLog
  229. end,
  230. fun(_CrashLog) ->
  231. case whereis(lager_crash_log) of
  232. P when is_pid(P) ->
  233. gen_server:stop(P);
  234. _ ->
  235. ok
  236. end,
  237. ok = application:stop(lager),
  238. ok = application:stop(goldrush),
  239. ok = lager_util:delete_test_dir(),
  240. ok = error_logger:tty(true)
  241. end, [
  242. fun(CrashLog) ->
  243. {"under normal circumstances, file should be opened",
  244. fun() ->
  245. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  246. _ = gen_event:which_handlers(error_logger),
  247. sync_error_logger:error_msg("Test message\n"),
  248. {ok, Bin} = file:read_file(CrashLog),
  249. ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
  250. end}
  251. end,
  252. fun(CrashLog) ->
  253. {"file can't be opened on startup triggers an error message",
  254. fun() ->
  255. {ok, FInfo0} = file:read_file_info(CrashLog, [raw]),
  256. FInfo1 = FInfo0#file_info{mode = 0},
  257. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo1)),
  258. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  259. % Note: required on win32, do this early to prevent subsequent failures
  260. % from preventing cleanup
  261. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo0)),
  262. ?assertEqual(1, lager_test_backend:count()),
  263. {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
  264. ?assertEqual(
  265. "Failed to open crash log file " ++ CrashLog ++ " with error: permission denied",
  266. lists:flatten(Message))
  267. end}
  268. end,
  269. fun(CrashLog) ->
  270. {"file that becomes unavailable at runtime should trigger an error message",
  271. fun() ->
  272. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  273. ?assertEqual(0, lager_test_backend:count()),
  274. sync_error_logger:error_msg("Test message\n"),
  275. _ = gen_event:which_handlers(error_logger),
  276. ?assertEqual(1, lager_test_backend:count()),
  277. file:delete(CrashLog),
  278. ok = lager_test_util:safe_write_file(CrashLog, ""),
  279. {ok, FInfo0} = file:read_file_info(CrashLog, [raw]),
  280. FInfo1 = FInfo0#file_info{mode = 0},
  281. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo1)),
  282. sync_error_logger:error_msg("Test message\n"),
  283. _ = gen_event:which_handlers(error_logger),
  284. % Note: required on win32, do this early to prevent subsequent failures
  285. % from preventing cleanup
  286. ?assertEqual(ok, file:write_file_info(CrashLog, FInfo0)),
  287. ?assertEqual(3, lager_test_backend:count()),
  288. lager_test_backend:pop(),
  289. {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
  290. ?assertEqual(
  291. "Failed to reopen crash log " ++ CrashLog ++ " with error: permission denied",
  292. lists:flatten(Message))
  293. end}
  294. end,
  295. fun(CrashLog) ->
  296. {"unavailable files that are fixed at runtime should start having log messages written",
  297. fun() ->
  298. {ok, FInfo} = file:read_file_info(CrashLog, [raw]),
  299. OldPerms = FInfo#file_info.mode,
  300. file:write_file_info(CrashLog, FInfo#file_info{mode = 0}),
  301. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  302. ?assertEqual(1, lager_test_backend:count()),
  303. {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
  304. ?assertEqual(
  305. "Failed to open crash log file " ++ CrashLog ++ " with error: permission denied",
  306. lists:flatten(Message)),
  307. file:write_file_info(CrashLog, FInfo#file_info{mode = OldPerms}),
  308. sync_error_logger:error_msg("Test message~n"),
  309. _ = gen_event:which_handlers(error_logger),
  310. {ok, Bin} = file:read_file(CrashLog),
  311. ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
  312. end}
  313. end,
  314. fun(CrashLog) ->
  315. {"external logfile rotation/deletion should be handled",
  316. fun() ->
  317. {ok, _} = ?MODULE:start_link(CrashLog, 65535, 0, undefined, 0, lager_rotator_default),
  318. ?assertEqual(0, lager_test_backend:count()),
  319. sync_error_logger:error_msg("Test message~n"),
  320. _ = gen_event:which_handlers(error_logger),
  321. {ok, Bin} = file:read_file(CrashLog),
  322. ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}])),
  323. ?assertEqual(ok, file:delete(CrashLog)),
  324. ?assertEqual(ok, lager_test_util:safe_write_file(CrashLog, "")),
  325. sync_error_logger:error_msg("Test message1~n"),
  326. _ = gen_event:which_handlers(error_logger),
  327. {ok, Bin1} = file:read_file(CrashLog),
  328. ?assertMatch([_, "Test message1\n"], re:split(Bin1, "\n", [{return, list}, {parts, 2}])),
  329. file:rename(CrashLog, CrashLog ++ ".0"),
  330. sync_error_logger:error_msg("Test message2~n"),
  331. _ = gen_event:which_handlers(error_logger),
  332. {ok, Bin2} = file:read_file(CrashLog),
  333. ?assertMatch([_, "Test message2\n"], re:split(Bin2, "\n", [{return, list}, {parts, 2}]))
  334. end}
  335. end
  336. ]}.
  337. -endif.