Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

338 Zeilen
14 KiB

vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
vor 14 Jahren
  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.