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

338 行
14 KiB

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