You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

797 lines
38 KiB

14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
  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 File backend for lager, with multiple file support.
  17. %% Multiple files are supported, each with the path and the loglevel being
  18. %% configurable. The configuration paramter for this backend is a list of
  19. %% key-value 2-tuples. See the init() function for the available options.
  20. %% This backend supports external and internal log
  21. %% rotation and will re-open handles to files if the inode changes. It will
  22. %% also rotate the files itself if the size of the file exceeds the
  23. %% `size' and keep `count' rotated files. `date' is
  24. %% an alternate rotation trigger, based on time. See the README for
  25. %% documentation.
  26. %% For performance, the file backend does delayed writes, although it will
  27. %% sync at specific log levels, configured via the `sync_on' option. By default
  28. %% the error level or above will trigger a sync.
  29. -module(lager_file_backend).
  30. -include("lager.hrl").
  31. -behaviour(gen_event).
  32. -ifdef(TEST).
  33. -include_lib("eunit/include/eunit.hrl").
  34. -include_lib("kernel/include/file.hrl").
  35. -compile([{parse_transform, lager_transform}]).
  36. -endif.
  37. -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
  38. code_change/3]).
  39. -export([config_to_id/1]).
  40. -define(DEFAULT_LOG_LEVEL, info).
  41. -define(DEFAULT_ROTATION_SIZE, 10485760). %% 10mb
  42. -define(DEFAULT_ROTATION_DATE, "$D0"). %% midnight
  43. -define(DEFAULT_ROTATION_COUNT, 5).
  44. -define(DEFAULT_SYNC_LEVEL, error).
  45. -define(DEFAULT_SYNC_INTERVAL, 1000).
  46. -define(DEFAULT_SYNC_SIZE, 1024*64). %% 64kb
  47. -define(DEFAULT_CHECK_INTERVAL, 1000).
  48. -record(state, {
  49. name :: string(),
  50. level :: {'mask', integer()},
  51. fd :: file:io_device(),
  52. inode :: integer(),
  53. flap=false :: boolean(),
  54. size = 0 :: integer(),
  55. date :: undefined | string(),
  56. count = 10 :: integer(),
  57. formatter :: atom(),
  58. formatter_config :: any(),
  59. sync_on :: {'mask', integer()},
  60. check_interval = ?DEFAULT_CHECK_INTERVAL :: non_neg_integer(),
  61. sync_interval = ?DEFAULT_SYNC_INTERVAL :: non_neg_integer(),
  62. sync_size = ?DEFAULT_SYNC_SIZE :: non_neg_integer(),
  63. last_check = os:timestamp() :: erlang:timestamp()
  64. }).
  65. -type option() :: {file, string()} | {level, lager:log_level()} |
  66. {size, non_neg_integer()} | {date, string()} |
  67. {count, non_neg_integer()} | {sync_interval, non_neg_integer()} |
  68. {sync_size, non_neg_integer()} | {sync_on, lager:log_level()} |
  69. {check_interval, non_neg_integer()} | {formatter, atom()} |
  70. {formatter_config, term()}.
  71. -spec init([option(),...]) -> {ok, #state{}} | {error, bad_config}.
  72. init({FileName, LogLevel}) when is_list(FileName), is_atom(LogLevel) ->
  73. %% backwards compatability hack
  74. init([{file, FileName}, {level, LogLevel}]);
  75. init({FileName, LogLevel, Size, Date, Count}) when is_list(FileName), is_atom(LogLevel) ->
  76. %% backwards compatability hack
  77. init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}]);
  78. init([{FileName, LogLevel, Size, Date, Count}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) ->
  79. %% backwards compatability hack
  80. init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}, {formatter, Formatter}, {formatter_config, FormatterConfig}]);
  81. init([LogFile,{Formatter}]) ->
  82. %% backwards compatability hack
  83. init([LogFile,{Formatter,[]}]);
  84. init([{FileName, LogLevel}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) ->
  85. %% backwards compatability hack
  86. init([{file, FileName}, {level, LogLevel}, {formatter, Formatter}, {formatter_config, FormatterConfig}]);
  87. init(LogFileConfig) when is_list(LogFileConfig) ->
  88. case validate_logfile_proplist(LogFileConfig) of
  89. false ->
  90. %% falied to validate config
  91. {error, {fatal, bad_config}};
  92. Config ->
  93. %% probabably a better way to do this, but whatever
  94. [Name, Level, Date, Size, Count, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] =
  95. [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]],
  96. schedule_rotation(Name, Date),
  97. State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, formatter=Formatter,
  98. formatter_config=FormatterConfig, sync_on=SyncOn, sync_interval=SyncInterval, sync_size=SyncSize,
  99. check_interval=CheckInterval},
  100. State = case lager_util:open_logfile(Name, {SyncSize, SyncInterval}) of
  101. {ok, {FD, Inode, _}} ->
  102. State0#state{fd=FD, inode=Inode};
  103. {error, Reason} ->
  104. ?INT_LOG(error, "Failed to open log file ~s with error ~s", [Name, file:format_error(Reason)]),
  105. State0#state{flap=true}
  106. end,
  107. {ok, State}
  108. end.
  109. %% @private
  110. handle_call({set_loglevel, Level}, #state{name=Ident} = State) ->
  111. case validate_loglevel(Level) of
  112. false ->
  113. {ok, {error, bad_loglevel}, State};
  114. Levels ->
  115. ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]),
  116. {ok, ok, State#state{level=Levels}}
  117. end;
  118. handle_call(get_loglevel, #state{level=Level} = State) ->
  119. {ok, Level, State};
  120. handle_call(_Request, State) ->
  121. {ok, ok, State}.
  122. %% @private
  123. handle_event({log, Message},
  124. #state{name=Name, level=L,formatter=Formatter,formatter_config=FormatConfig} = State) ->
  125. case lager_util:is_loggable(Message,L,{lager_file_backend, Name}) of
  126. true ->
  127. {ok,write(State, lager_msg:timestamp(Message), lager_msg:severity_as_int(Message), Formatter:format(Message,FormatConfig)) };
  128. false ->
  129. {ok, State}
  130. end;
  131. handle_event(_Event, State) ->
  132. {ok, State}.
  133. %% @private
  134. handle_info({rotate, File}, #state{name=File,count=Count,date=Date} = State) ->
  135. _ = lager_util:rotate_logfile(File, Count),
  136. schedule_rotation(File, Date),
  137. {ok, State};
  138. handle_info(_Info, State) ->
  139. {ok, State}.
  140. %% @private
  141. terminate(_Reason, #state{fd=FD}) ->
  142. %% flush and close any file handles
  143. _ = file:datasync(FD),
  144. _ = file:close(FD),
  145. ok.
  146. %% @private
  147. code_change(_OldVsn, State, _Extra) ->
  148. {ok, State}.
  149. %% @private convert the config into a gen_event handler ID
  150. config_to_id({Name,_Severity}) when is_list(Name) ->
  151. {?MODULE, Name};
  152. config_to_id({Name,_Severity,_Size,_Rotation,_Count}) ->
  153. {?MODULE, Name};
  154. config_to_id([{Name,_Severity,_Size,_Rotation,_Count}, _Format]) ->
  155. {?MODULE, Name};
  156. config_to_id([{Name,_Severity}, _Format]) when is_list(Name) ->
  157. {?MODULE, Name};
  158. config_to_id(Config) ->
  159. case proplists:get_value(file, Config) of
  160. undefined ->
  161. erlang:error(no_file);
  162. File ->
  163. {?MODULE, File}
  164. end.
  165. write(#state{name=Name, fd=FD, inode=Inode, flap=Flap, size=RotSize,
  166. count=Count} = State, Timestamp, Level, Msg) ->
  167. LastCheck = timer:now_diff(Timestamp, State#state.last_check) div 1000,
  168. case LastCheck >= State#state.check_interval orelse FD == undefined of
  169. true ->
  170. %% need to check for rotation
  171. case lager_util:ensure_logfile(Name, FD, Inode, {State#state.sync_size, State#state.sync_interval}) of
  172. {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
  173. case lager_util:rotate_logfile(Name, Count) of
  174. ok ->
  175. %% go around the loop again, we'll do another rotation check and hit the next clause of ensure_logfile
  176. write(State, Timestamp, Level, Msg);
  177. {error, Reason} ->
  178. case Flap of
  179. true ->
  180. State;
  181. _ ->
  182. ?INT_LOG(error, "Failed to rotate log file ~s with error ~s", [Name, file:format_error(Reason)]),
  183. State#state{flap=true}
  184. end
  185. end;
  186. {ok, {NewFD, NewInode, _}} ->
  187. %% update our last check and try again
  188. do_write(State#state{last_check=Timestamp, fd=NewFD, inode=NewInode}, Level, Msg);
  189. {error, Reason} ->
  190. case Flap of
  191. true ->
  192. State;
  193. _ ->
  194. ?INT_LOG(error, "Failed to reopen log file ~s with error ~s", [Name, file:format_error(Reason)]),
  195. State#state{flap=true}
  196. end
  197. end;
  198. false ->
  199. do_write(State, Level, Msg)
  200. end.
  201. do_write(#state{fd=FD, name=Name, flap=Flap} = State, Level, Msg) ->
  202. %% delayed_write doesn't report errors
  203. _ = file:write(FD, unicode:characters_to_binary(Msg)),
  204. {mask, SyncLevel} = State#state.sync_on,
  205. case (Level band SyncLevel) /= 0 of
  206. true ->
  207. %% force a sync on any message that matches the 'sync_on' bitmask
  208. Flap2 = case file:datasync(FD) of
  209. {error, Reason2} when Flap == false ->
  210. ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
  211. [Name, file:format_error(Reason2)]),
  212. true;
  213. ok ->
  214. false;
  215. _ ->
  216. Flap
  217. end,
  218. State#state{flap=Flap2};
  219. _ ->
  220. State
  221. end.
  222. validate_loglevel(Level) ->
  223. try lager_util:config_to_mask(Level) of
  224. Levels ->
  225. Levels
  226. catch
  227. _:_ ->
  228. false
  229. end.
  230. validate_logfile_proplist(List) ->
  231. try validate_logfile_proplist(List, []) of
  232. Res ->
  233. case proplists:get_value(file, Res) of
  234. undefined ->
  235. ?INT_LOG(error, "Missing required file option", []),
  236. false;
  237. _File ->
  238. %% merge with the default options
  239. {ok, DefaultRotationDate} = lager_util:parse_rotation_date_spec(?DEFAULT_ROTATION_DATE),
  240. lists:keymerge(1, lists:sort(Res), lists:sort([
  241. {level, validate_loglevel(?DEFAULT_LOG_LEVEL)}, {date, DefaultRotationDate},
  242. {size, ?DEFAULT_ROTATION_SIZE}, {count, ?DEFAULT_ROTATION_COUNT},
  243. {sync_on, validate_loglevel(?DEFAULT_SYNC_LEVEL)}, {sync_interval, ?DEFAULT_SYNC_INTERVAL},
  244. {sync_size, ?DEFAULT_SYNC_SIZE}, {check_interval, ?DEFAULT_CHECK_INTERVAL},
  245. {formatter, lager_default_formatter}, {formatter_config, []}
  246. ]))
  247. end
  248. catch
  249. {bad_config, Msg, Value} ->
  250. ?INT_LOG(error, "~s ~p for file ~p",
  251. [Msg, Value, proplists:get_value(file, List)]),
  252. false
  253. end.
  254. validate_logfile_proplist([], Acc) ->
  255. Acc;
  256. validate_logfile_proplist([{file, File}|Tail], Acc) ->
  257. %% is there any reasonable validation we can do here?
  258. validate_logfile_proplist(Tail, [{file, File}|Acc]);
  259. validate_logfile_proplist([{level, Level}|Tail], Acc) ->
  260. case validate_loglevel(Level) of
  261. false ->
  262. throw({bad_config, "Invalid loglevel", Level});
  263. Res ->
  264. validate_logfile_proplist(Tail, [{level, Res}|Acc])
  265. end;
  266. validate_logfile_proplist([{size, Size}|Tail], Acc) ->
  267. case Size of
  268. S when is_integer(S), S >= 0 ->
  269. validate_logfile_proplist(Tail, [{size, Size}|Acc]);
  270. _ ->
  271. throw({bad_config, "Invalid rotation size", Size})
  272. end;
  273. validate_logfile_proplist([{count, Count}|Tail], Acc) ->
  274. case Count of
  275. C when is_integer(C), C >= 0 ->
  276. validate_logfile_proplist(Tail, [{count, Count}|Acc]);
  277. _ ->
  278. throw({bad_config, "Invalid rotation count", Count})
  279. end;
  280. validate_logfile_proplist([{date, Date}|Tail], Acc) ->
  281. case lager_util:parse_rotation_date_spec(Date) of
  282. {ok, Spec} ->
  283. validate_logfile_proplist(Tail, [{date, Spec}|Acc]);
  284. {error, _} when Date == "" ->
  285. %% legacy config allowed blanks
  286. validate_logfile_proplist(Tail, [{date, undefined}|Acc]);
  287. {error, _} ->
  288. throw({bad_config, "Invalid rotation date", Date})
  289. end;
  290. validate_logfile_proplist([{sync_interval, SyncInt}|Tail], Acc) ->
  291. case SyncInt of
  292. Val when is_integer(Val), Val >= 0 ->
  293. validate_logfile_proplist(Tail, [{sync_interval, Val}|Acc]);
  294. _ ->
  295. throw({bad_config, "Invalid sync interval", SyncInt})
  296. end;
  297. validate_logfile_proplist([{sync_size, SyncSize}|Tail], Acc) ->
  298. case SyncSize of
  299. Val when is_integer(Val), Val >= 0 ->
  300. validate_logfile_proplist(Tail, [{sync_size, Val}|Acc]);
  301. _ ->
  302. throw({bad_config, "Invalid sync size", SyncSize})
  303. end;
  304. validate_logfile_proplist([{check_interval, CheckInt}|Tail], Acc) ->
  305. case CheckInt of
  306. Val when is_integer(Val), Val >= 0 ->
  307. validate_logfile_proplist(Tail, [{check_interval, Val}|Acc]);
  308. always ->
  309. validate_logfile_proplist(Tail, [{check_interval, 0}|Acc]);
  310. _ ->
  311. throw({bad_config, "Invalid check interval", CheckInt})
  312. end;
  313. validate_logfile_proplist([{sync_on, Level}|Tail], Acc) ->
  314. case validate_loglevel(Level) of
  315. false ->
  316. throw({bad_config, "Invalid sync on level", Level});
  317. Res ->
  318. validate_logfile_proplist(Tail, [{sync_on, Res}|Acc])
  319. end;
  320. validate_logfile_proplist([{formatter, Fmt}|Tail], Acc) ->
  321. case is_atom(Fmt) of
  322. true ->
  323. validate_logfile_proplist(Tail, [{formatter, Fmt}|Acc]);
  324. false ->
  325. throw({bad_config, "Invalid formatter module", Fmt})
  326. end;
  327. validate_logfile_proplist([{formatter_config, FmtCfg}|Tail], Acc) ->
  328. case is_list(FmtCfg) of
  329. true ->
  330. validate_logfile_proplist(Tail, [{formatter_config, FmtCfg}|Acc]);
  331. false ->
  332. throw({bad_config, "Invalid formatter config", FmtCfg})
  333. end;
  334. validate_logfile_proplist([Other|_Tail], _Acc) ->
  335. throw({bad_config, "Invalid option", Other}).
  336. schedule_rotation(_, undefined) ->
  337. ok;
  338. schedule_rotation(Name, Date) ->
  339. erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}),
  340. ok.
  341. -ifdef(TEST).
  342. get_loglevel_test() ->
  343. {ok, Level, _} = handle_call(get_loglevel,
  344. #state{name="bar", level=lager_util:config_to_mask(info), fd=0, inode=0}),
  345. ?assertEqual(Level, lager_util:config_to_mask(info)),
  346. {ok, Level2, _} = handle_call(get_loglevel,
  347. #state{name="foo", level=lager_util:config_to_mask(warning), fd=0, inode=0}),
  348. ?assertEqual(Level2, lager_util:config_to_mask(warning)).
  349. rotation_test_() ->
  350. {foreach,
  351. fun() ->
  352. [file:delete(F)||F <- filelib:wildcard("test.log*")],
  353. SyncLevel = validate_loglevel(?DEFAULT_SYNC_LEVEL),
  354. SyncSize = ?DEFAULT_SYNC_SIZE,
  355. SyncInterval = ?DEFAULT_SYNC_INTERVAL,
  356. CheckInterval = 0, %% hard to test delayed mode
  357. #state{name="test.log", level=?DEBUG, sync_on=SyncLevel,
  358. sync_size=SyncSize, sync_interval=SyncInterval, check_interval=CheckInterval}
  359. end,
  360. fun(_) ->
  361. [file:delete(F)||F <- filelib:wildcard("test.log*")]
  362. end,
  363. [fun(DefaultState = #state{sync_size=SyncSize, sync_interval = SyncInterval}) ->
  364. {"External rotation should work",
  365. fun() ->
  366. {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", {SyncSize, SyncInterval}),
  367. State0 = DefaultState#state{fd=FD, inode=Inode},
  368. ?assertMatch(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode},
  369. write(State0, os:timestamp(), ?DEBUG, "hello world")),
  370. file:delete("test.log"),
  371. Result = write(State0, os:timestamp(), ?DEBUG, "hello world"),
  372. %% assert file has changed
  373. ?assert(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode} =/= Result),
  374. ?assertMatch(#state{name="test.log", level=?DEBUG}, Result),
  375. file:rename("test.log", "test.log.1"),
  376. Result2 = write(Result, os:timestamp(), ?DEBUG, "hello world"),
  377. %% assert file has changed
  378. ?assert(Result =/= Result2),
  379. ?assertMatch(#state{name="test.log", level=?DEBUG}, Result2),
  380. ok
  381. end}
  382. end,
  383. fun(DefaultState = #state{sync_size=SyncSize, sync_interval = SyncInterval}) ->
  384. {"Internal rotation and delayed write",
  385. fun() ->
  386. CheckInterval = 3000, % 3 sec
  387. RotationSize = 15,
  388. PreviousCheck = os:timestamp(),
  389. {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", {SyncSize, SyncInterval}),
  390. State0 = DefaultState#state{
  391. fd=FD, inode=Inode, size=RotationSize,
  392. check_interval=CheckInterval, last_check=PreviousCheck},
  393. %% new message within check interval with sync_on level
  394. Msg1Timestamp = add_secs(PreviousCheck, 1),
  395. State0 = State1 = write(State0, Msg1Timestamp, ?ERROR, "big big message 1"),
  396. %% new message within check interval under sync_on level
  397. %% not written to disk yet
  398. Msg2Timestamp = add_secs(PreviousCheck, 2),
  399. State0 = State2 = write(State1, Msg2Timestamp, ?DEBUG, "buffered message 2"),
  400. %% although file size is big enough...
  401. {ok, FInfo} = file:read_file_info("test.log"),
  402. ?assert(RotationSize < FInfo#file_info.size),
  403. %% ...no rotation yet
  404. ?assertEqual(PreviousCheck, State2#state.last_check),
  405. ?assertNot(filelib:is_regular("test.log.0")),
  406. %% new message after check interval
  407. Msg3Timestamp = add_secs(PreviousCheck, 4),
  408. _State3 = write(State2, Msg3Timestamp, ?DEBUG, "message 3"),
  409. %% rotation happened
  410. ?assert(filelib:is_regular("test.log.0")),
  411. {ok, Bin1} = file:read_file("test.log.0"),
  412. {ok, Bin2} = file:read_file("test.log"),
  413. %% message 1-3 written to file
  414. ?assertEqual(<<"big big message 1buffered message 2">>, Bin1),
  415. %% message 4 buffered, not yet written to file
  416. ?assertEqual(<<"">>, Bin2),
  417. ok
  418. end}
  419. end
  420. ]}.
  421. add_secs({Mega, Secs, Micro}, Add) ->
  422. NewSecs = Secs + Add,
  423. {Mega + NewSecs div 10000000, NewSecs rem 10000000, Micro}.
  424. filesystem_test_() ->
  425. {foreach,
  426. fun() ->
  427. file:write_file("test.log", ""),
  428. error_logger:tty(false),
  429. application:load(lager),
  430. application:set_env(lager, handlers, [{lager_test_backend, info}]),
  431. application:set_env(lager, error_logger_redirect, false),
  432. application:set_env(lager, async_threshold, undefined),
  433. lager:start()
  434. end,
  435. fun(_) ->
  436. file:delete("test.log"),
  437. file:delete("test.log.0"),
  438. file:delete("test.log.1"),
  439. application:stop(lager),
  440. application:stop(goldrush),
  441. error_logger:tty(true)
  442. end,
  443. [
  444. {"under normal circumstances, file should be opened",
  445. fun() ->
  446. gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]),
  447. lager:log(error, self(), "Test message"),
  448. {ok, Bin} = file:read_file("test.log"),
  449. Pid = pid_to_list(self()),
  450. ?assertMatch([_, _, "[error]", Pid, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
  451. end
  452. },
  453. {"don't choke on unicode",
  454. fun() ->
  455. gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]),
  456. lager:log(error, self(),"~ts", [[20013,25991,27979,35797]]),
  457. {ok, Bin} = file:read_file("test.log"),
  458. Pid = pid_to_list(self()),
  459. ?assertMatch([_, _, "[error]", Pid, [228,184,173,230,150,135,230,181,139,232,175,149, $\n]], re:split(Bin, " ", [{return, list}, {parts, 5}]))
  460. end
  461. },
  462. {"don't choke on latin-1",
  463. fun() ->
  464. %% XXX if this test fails, check that this file is encoded latin-1, not utf-8!
  465. gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info}, {lager_default_formatter}]),
  466. lager:log(error, self(),"~ts", [[76, 198, 221, 206, 78, $-, 239]]),
  467. {ok, Bin} = file:read_file("test.log"),
  468. Pid = pid_to_list(self()),
  469. Res = re:split(Bin, " ", [{return, list}, {parts, 5}]),
  470. ?assertMatch([_, _, "[error]", Pid, [76,195,134,195,157,195,142,78,45,195,175,$\n]], Res)
  471. end
  472. },
  473. {"file can't be opened on startup triggers an error message",
  474. fun() ->
  475. {ok, FInfo} = file:read_file_info("test.log"),
  476. file:write_file_info("test.log", FInfo#file_info{mode = 0}),
  477. gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info, 10*1024*1024, "$D0", 5}),
  478. ?assertEqual(1, lager_test_backend:count()),
  479. {_Level, _Time,Message,_Metadata} = lager_test_backend:pop(),
  480. ?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message))
  481. end
  482. },
  483. {"file that becomes unavailable at runtime should trigger an error message",
  484. fun() ->
  485. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}]),
  486. ?assertEqual(0, lager_test_backend:count()),
  487. lager:log(error, self(), "Test message"),
  488. ?assertEqual(1, lager_test_backend:count()),
  489. file:delete("test.log"),
  490. file:write_file("test.log", ""),
  491. {ok, FInfo} = file:read_file_info("test.log"),
  492. file:write_file_info("test.log", FInfo#file_info{mode = 0}),
  493. lager:log(error, self(), "Test message"),
  494. ?assertEqual(3, lager_test_backend:count()),
  495. lager_test_backend:pop(),
  496. lager_test_backend:pop(),
  497. {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
  498. ?assertEqual("Failed to reopen log file test.log with error permission denied", lists:flatten(Message))
  499. end
  500. },
  501. {"unavailable files that are fixed at runtime should start having log messages written",
  502. fun() ->
  503. {ok, FInfo} = file:read_file_info("test.log"),
  504. OldPerms = FInfo#file_info.mode,
  505. file:write_file_info("test.log", FInfo#file_info{mode = 0}),
  506. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"},{check_interval, 0}]),
  507. ?assertEqual(1, lager_test_backend:count()),
  508. {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
  509. ?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)),
  510. file:write_file_info("test.log", FInfo#file_info{mode = OldPerms}),
  511. lager:log(error, self(), "Test message"),
  512. {ok, Bin} = file:read_file("test.log"),
  513. Pid = pid_to_list(self()),
  514. ?assertMatch([_, _, "[error]", Pid, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
  515. end
  516. },
  517. {"external logfile rotation/deletion should be handled",
  518. fun() ->
  519. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}]),
  520. ?assertEqual(0, lager_test_backend:count()),
  521. lager:log(error, self(), "Test message1"),
  522. ?assertEqual(1, lager_test_backend:count()),
  523. file:delete("test.log"),
  524. file:write_file("test.log", ""),
  525. lager:log(error, self(), "Test message2"),
  526. {ok, Bin} = file:read_file("test.log"),
  527. Pid = pid_to_list(self()),
  528. ?assertMatch([_, _, "[error]", Pid, "Test message2\n"], re:split(Bin, " ", [{return, list}, {parts, 5}])),
  529. file:rename("test.log", "test.log.0"),
  530. lager:log(error, self(), "Test message3"),
  531. {ok, Bin2} = file:read_file("test.log"),
  532. ?assertMatch([_, _, "[error]", Pid, "Test message3\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}]))
  533. end
  534. },
  535. {"internal size rotation should work",
  536. fun() ->
  537. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 0}, {size, 10}]),
  538. lager:log(error, self(), "Test message1"),
  539. lager:log(error, self(), "Test message1"),
  540. ?assert(filelib:is_regular("test.log.0"))
  541. end
  542. },
  543. {"internal time rotation should work",
  544. fun() ->
  545. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {check_interval, 1000}]),
  546. lager:log(error, self(), "Test message1"),
  547. lager:log(error, self(), "Test message1"),
  548. whereis(lager_event) ! {rotate, "test.log"},
  549. lager:log(error, self(), "Test message1"),
  550. ?assert(filelib:is_regular("test.log.0"))
  551. end
  552. },
  553. {"sync_on option should work",
  554. fun() ->
  555. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "=info"}, {check_interval, 5000}, {sync_interval, 5000}]),
  556. lager:log(error, self(), "Test message1"),
  557. lager:log(error, self(), "Test message1"),
  558. ?assertEqual({ok, <<>>}, file:read_file("test.log")),
  559. lager:log(info, self(), "Test message1"),
  560. {ok, Bin} = file:read_file("test.log"),
  561. ?assert(<<>> /= Bin)
  562. end
  563. },
  564. {"sync_on none option should work (also tests sync_interval)",
  565. fun() ->
  566. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5000}, {sync_interval, 1000}]),
  567. lager:log(error, self(), "Test message1"),
  568. lager:log(error, self(), "Test message1"),
  569. ?assertEqual({ok, <<>>}, file:read_file("test.log")),
  570. lager:log(info, self(), "Test message1"),
  571. ?assertEqual({ok, <<>>}, file:read_file("test.log")),
  572. timer:sleep(2000),
  573. {ok, Bin} = file:read_file("test.log"),
  574. ?assert(<<>> /= Bin)
  575. end
  576. },
  577. {"sync_size option should work",
  578. fun() ->
  579. gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "none"}, {check_interval, 5001}, {sync_size, 640}, {sync_interval, 5001}]),
  580. lager:log(error, self(), "Test messageis64bytes"),
  581. lager:log(error, self(), "Test messageis64bytes"),
  582. lager:log(error, self(), "Test messageis64bytes"),
  583. lager:log(error, self(), "Test messageis64bytes"),
  584. lager:log(error, self(), "Test messageis64bytes"),
  585. ?assertEqual({ok, <<>>}, file:read_file("test.log")),
  586. lager:log(error, self(), "Test messageis64bytes"),
  587. lager:log(error, self(), "Test messageis64bytes"),
  588. lager:log(error, self(), "Test messageis64bytes"),
  589. lager:log(error, self(), "Test messageis64bytes"),
  590. ?assertEqual({ok, <<>>}, file:read_file("test.log")),
  591. %% now we've written enough bytes
  592. lager:log(error, self(), "Test messageis64bytes"),
  593. {ok, Bin} = file:read_file("test.log"),
  594. ?assert(<<>> /= Bin)
  595. end
  596. },
  597. {"runtime level changes",
  598. fun() ->
  599. gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, {"test.log", info}),
  600. ?assertEqual(0, lager_test_backend:count()),
  601. lager:log(info, self(), "Test message1"),
  602. lager:log(error, self(), "Test message2"),
  603. {ok, Bin} = file:read_file("test.log"),
  604. Lines = length(re:split(Bin, "\n", [{return, list}, trim])),
  605. ?assertEqual(Lines, 2),
  606. ?assertEqual(ok, lager:set_loglevel(lager_file_backend, "test.log", warning)),
  607. lager:log(info, self(), "Test message3"), %% this won't get logged
  608. lager:log(error, self(), "Test message4"),
  609. {ok, Bin2} = file:read_file("test.log"),
  610. Lines2 = length(re:split(Bin2, "\n", [{return, list}, trim])),
  611. ?assertEqual(Lines2, 3)
  612. end
  613. },
  614. {"invalid runtime level changes",
  615. fun() ->
  616. gen_event:add_handler(lager_event, lager_file_backend, [{"test.log", info, 10*1024*1024, "$D0", 5}, {lager_default_formatter}]),
  617. gen_event:add_handler(lager_event, lager_file_backend, {"test3.log", info}),
  618. ?assertEqual({error, bad_module}, lager:set_loglevel(lager_file_backend, "test.log", warning))
  619. end
  620. },
  621. {"tracing should work",
  622. fun() ->
  623. gen_event:add_handler(lager_event, lager_file_backend,
  624. {"test.log", critical}),
  625. lager:error("Test message"),
  626. ?assertEqual({ok, <<>>}, file:read_file("test.log")),
  627. {Level, _} = lager_config:get(loglevel),
  628. lager_config:set(loglevel, {Level, [{[{module,
  629. ?MODULE}], ?DEBUG,
  630. {lager_file_backend, "test.log"}}]}),
  631. lager:error("Test message"),
  632. timer:sleep(1000),
  633. {ok, Bin} = file:read_file("test.log"),
  634. ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
  635. end
  636. },
  637. {"tracing should not duplicate messages",
  638. fun() ->
  639. gen_event:add_handler(lager_event, lager_file_backend,
  640. [{file, "test.log"}, {level, critical}, {check_interval, always}]),
  641. lager:critical("Test message"),
  642. {ok, Bin1} = file:read_file("test.log"),
  643. ?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin1, " ", [{return, list}, {parts, 5}])),
  644. ok = file:delete("test.log"),
  645. {Level, _} = lager_config:get(loglevel),
  646. lager_config:set(loglevel, {Level, [{[{module,
  647. ?MODULE}], ?DEBUG,
  648. {lager_file_backend, "test.log"}}]}),
  649. lager:critical("Test message"),
  650. {ok, Bin2} = file:read_file("test.log"),
  651. ?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}])),
  652. ok = file:delete("test.log"),
  653. lager:error("Test message"),
  654. {ok, Bin3} = file:read_file("test.log"),
  655. ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
  656. end
  657. },
  658. {"tracing to a dedicated file should work",
  659. fun() ->
  660. file:delete("foo.log"),
  661. {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}]),
  662. lager:error("Test message"),
  663. %% not elegible for trace
  664. lager:log(error, self(), "Test message"),
  665. {ok, Bin3} = file:read_file("foo.log"),
  666. ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
  667. end
  668. }
  669. ]
  670. }.
  671. formatting_test_() ->
  672. {foreach,
  673. fun() ->
  674. file:write_file("test.log", ""),
  675. file:write_file("test2.log", ""),
  676. error_logger:tty(false),
  677. application:load(lager),
  678. application:set_env(lager, handlers, [{lager_test_backend, info}]),
  679. application:set_env(lager, error_logger_redirect, false),
  680. lager:start()
  681. end,
  682. fun(_) ->
  683. file:delete("test.log"),
  684. file:delete("test2.log"),
  685. application:stop(lager),
  686. application:stop(goldrush),
  687. error_logger:tty(true)
  688. end,
  689. [{"Should have two log files, the second prefixed with 2>",
  690. fun() ->
  691. gen_event:add_handler(lager_event, lager_file_backend,[{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]),
  692. gen_event:add_handler(lager_event, lager_file_backend,[{"test2.log", debug},{lager_default_formatter,["2> [",severity,"] ", message, "\n"]}]),
  693. lager:log(error, self(), "Test message"),
  694. ?assertMatch({ok, <<"[error] Test message\n">>},file:read_file("test.log")),
  695. ?assertMatch({ok, <<"2> [error] Test message\n">>},file:read_file("test2.log"))
  696. end
  697. }
  698. ]}.
  699. config_validation_test_() ->
  700. [
  701. {"missing file",
  702. ?_assertEqual(false,
  703. validate_logfile_proplist([{level, info},{size, 10}]))
  704. },
  705. {"bad level",
  706. ?_assertEqual(false,
  707. validate_logfile_proplist([{file, "test.log"}, {level, blah},{size, 10}]))
  708. },
  709. {"bad size",
  710. ?_assertEqual(false,
  711. validate_logfile_proplist([{file, "test.log"}, {size, infinity}]))
  712. },
  713. {"bad count",
  714. ?_assertEqual(false,
  715. validate_logfile_proplist([{file, "test.log"}, {count, infinity}]))
  716. },
  717. {"bad date",
  718. ?_assertEqual(false,
  719. validate_logfile_proplist([{file, "test.log"}, {date, "midnight"}]))
  720. },
  721. {"blank date is ok",
  722. ?_assertMatch([_|_],
  723. validate_logfile_proplist([{file, "test.log"}, {date, ""}]))
  724. },
  725. {"bad sync_interval",
  726. ?_assertEqual(false,
  727. validate_logfile_proplist([{file, "test.log"}, {sync_interval, infinity}]))
  728. },
  729. {"bad sync_size",
  730. ?_assertEqual(false,
  731. validate_logfile_proplist([{file, "test.log"}, {sync_size, infinity}]))
  732. },
  733. {"bad check_interval",
  734. ?_assertEqual(false,
  735. validate_logfile_proplist([{file, "test.log"}, {check_interval, infinity}]))
  736. },
  737. {"bad sync_on level",
  738. ?_assertEqual(false,
  739. validate_logfile_proplist([{file, "test.log"}, {sync_on, infinity}]))
  740. },
  741. {"bad formatter module",
  742. ?_assertEqual(false,
  743. validate_logfile_proplist([{file, "test.log"}, {formatter, "io:format"}]))
  744. },
  745. {"bad formatter config",
  746. ?_assertEqual(false,
  747. validate_logfile_proplist([{file, "test.log"}, {formatter_config, blah}]))
  748. },
  749. {"unknown option",
  750. ?_assertEqual(false,
  751. validate_logfile_proplist([{file, "test.log"}, {rhubarb, spicy}]))
  752. }
  753. ].
  754. -endif.