Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

368 rader
16 KiB

14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
  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.