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.

1103 line
48 KiB

14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
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 File backend for lager, with multiple file support.
  21. %% Multiple files are supported, each with the path and the loglevel being
  22. %% configurable. The configuration paramter for this backend is a list of
  23. %% key-value 2-tuples. See the init() function for the available options.
  24. %% This backend supports external and internal log
  25. %% rotation and will re-open handles to files if the inode changes. It will
  26. %% also rotate the files itself if the size of the file exceeds the
  27. %% `size' and keep `count' rotated files. `date' is
  28. %% an alternate rotation trigger, based on time. See the README for
  29. %% documentation.
  30. %% For performance, the file backend does delayed writes, although it will
  31. %% sync at specific log levels, configured via the `sync_on' option. By default
  32. %% the error level or above will trigger a sync.
  33. -module(lager_file_backend).
  34. -include("lager.hrl").
  35. -behaviour(gen_event).
  36. -ifdef(TEST).
  37. -include_lib("eunit/include/eunit.hrl").
  38. -include_lib("kernel/include/file.hrl").
  39. -compile([{parse_transform, lager_transform}]).
  40. -endif.
  41. -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
  42. code_change/3]).
  43. -export([config_to_id/1]).
  44. -define(DEFAULT_LOG_LEVEL, info).
  45. -define(DEFAULT_ROTATION_SIZE, 10485760). %% 10mb
  46. -define(DEFAULT_ROTATION_DATE, "$D0"). %% midnight
  47. -define(DEFAULT_ROTATION_COUNT, 5).
  48. -define(DEFAULT_SYNC_LEVEL, error).
  49. -define(DEFAULT_SYNC_INTERVAL, 1000).
  50. -define(DEFAULT_SYNC_SIZE, 1024*64). %% 64kb
  51. -define(DEFAULT_CHECK_INTERVAL, 1000).
  52. -record(state, {
  53. name :: string(),
  54. level :: {'mask', integer()},
  55. fd :: file:io_device() | undefined,
  56. inode :: integer() | undefined,
  57. flap=false :: boolean(),
  58. size = 0 :: integer(),
  59. date :: undefined | string(),
  60. count = 10 :: integer(),
  61. shaper :: lager_shaper(),
  62. formatter :: atom(),
  63. formatter_config :: any(),
  64. sync_on :: {'mask', integer()},
  65. check_interval = ?DEFAULT_CHECK_INTERVAL :: non_neg_integer(),
  66. sync_interval = ?DEFAULT_SYNC_INTERVAL :: non_neg_integer(),
  67. sync_size = ?DEFAULT_SYNC_SIZE :: non_neg_integer(),
  68. last_check = os:timestamp() :: erlang:timestamp()
  69. }).
  70. -type option() :: {file, string()} | {level, lager:log_level()} |
  71. {size, non_neg_integer()} | {date, string()} |
  72. {count, non_neg_integer()} | {high_water_mark, non_neg_integer()} |
  73. {sync_interval, non_neg_integer()} |
  74. {sync_size, non_neg_integer()} | {sync_on, lager:log_level()} |
  75. {check_interval, non_neg_integer()} | {formatter, atom()} |
  76. {formatter_config, term()}.
  77. -spec init([option(),...]) -> {ok, #state{}} | {error, {fatal,bad_config}}.
  78. init({FileName, LogLevel}) when is_list(FileName), is_atom(LogLevel) ->
  79. %% backwards compatibility hack
  80. init([{file, FileName}, {level, LogLevel}]);
  81. init({FileName, LogLevel, Size, Date, Count}) when is_list(FileName), is_atom(LogLevel) ->
  82. %% backwards compatibility hack
  83. init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}]);
  84. init([{FileName, LogLevel, Size, Date, Count}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) ->
  85. %% backwards compatibility hack
  86. init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}, {formatter, Formatter}, {formatter_config, FormatterConfig}]);
  87. init([LogFile,{Formatter}]) ->
  88. %% backwards compatibility hack
  89. init([LogFile,{Formatter,[]}]);
  90. init([{FileName, LogLevel}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) ->
  91. %% backwards compatibility hack
  92. init([{file, FileName}, {level, LogLevel}, {formatter, Formatter}, {formatter_config, FormatterConfig}]);
  93. init(LogFileConfig) when is_list(LogFileConfig) ->
  94. case validate_logfile_proplist(LogFileConfig) of
  95. false ->
  96. %% falied to validate config
  97. {error, {fatal, bad_config}};
  98. Config ->
  99. %% probabably a better way to do this, but whatever
  100. [RelName, Level, Date, Size, Count, HighWaterMark, Flush, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] =
  101. [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, high_water_mark, flush_queue, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]],
  102. FlushThr = proplists:get_value(flush_threshold, Config, 0),
  103. Name = lager_util:expand_path(RelName),
  104. schedule_rotation(Name, Date),
  105. Shaper = lager_util:maybe_flush(Flush, #lager_shaper{hwm=HighWaterMark, flush_threshold = FlushThr, id=Name}),
  106. State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, shaper=Shaper, formatter=Formatter,
  107. formatter_config=FormatterConfig, sync_on=SyncOn, sync_interval=SyncInterval, sync_size=SyncSize,
  108. check_interval=CheckInterval},
  109. State = case lager_util:open_logfile(Name, {SyncSize, SyncInterval}) of
  110. {ok, {FD, Inode, _}} ->
  111. State0#state{fd=FD, inode=Inode};
  112. {error, Reason} ->
  113. ?INT_LOG(error, "Failed to open log file ~s with error ~s", [Name, file:format_error(Reason)]),
  114. State0#state{flap=true}
  115. end,
  116. {ok, State}
  117. end.
  118. %% @private
  119. handle_call({set_loglevel, Level}, #state{name=Ident} = State) ->
  120. case validate_loglevel(Level) of
  121. false ->
  122. {ok, {error, bad_loglevel}, State};
  123. Levels ->
  124. ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]),
  125. {ok, ok, State#state{level=Levels}}
  126. end;
  127. handle_call(get_loglevel, #state{level=Level} = State) ->
  128. {ok, Level, State};
  129. handle_call({set_loghwm, Hwm}, #state{shaper=Shaper, name=Name} = State) ->
  130. case validate_logfile_proplist([{file, Name}, {high_water_mark, Hwm}]) of
  131. false ->
  132. {ok, {error, bad_log_hwm}, State};
  133. _ ->
  134. NewShaper = Shaper#lager_shaper{hwm=Hwm},
  135. ?INT_LOG(notice, "Changed loghwm of ~s to ~p", [Name, Hwm]),
  136. {ok, {last_loghwm, Shaper#lager_shaper.hwm}, State#state{shaper=NewShaper}}
  137. end;
  138. handle_call(rotate, State = #state{name=File}) ->
  139. {ok, NewState} = handle_info({rotate, File}, State),
  140. {ok, ok, NewState};
  141. handle_call(_Request, State) ->
  142. {ok, ok, State}.
  143. %% @private
  144. handle_event({log, Message},
  145. #state{name=Name, level=L, shaper=Shaper, formatter=Formatter,formatter_config=FormatConfig} = State) ->
  146. case lager_util:is_loggable(Message,L,{lager_file_backend, Name}) of
  147. true ->
  148. case lager_util:check_hwm(Shaper) of
  149. {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} ->
  150. NewState = case Drop > 0 of
  151. true ->
  152. Report = io_lib:format(
  153. "lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec",
  154. [Drop, Hwm]),
  155. ReportMsg = lager_msg:new(Report, warning, [], []),
  156. write(State, lager_msg:timestamp(ReportMsg),
  157. lager_msg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig));
  158. false ->
  159. State
  160. end,
  161. {ok,write(NewState#state{shaper=NewShaper},
  162. lager_msg:timestamp(Message), lager_msg:severity_as_int(Message),
  163. Formatter:format(Message,FormatConfig))};
  164. {false, _, NewShaper} ->
  165. {ok, State#state{shaper=NewShaper}}
  166. end;
  167. false ->
  168. {ok, State}
  169. end;
  170. handle_event(_Event, State) ->
  171. {ok, State}.
  172. %% @private
  173. handle_info({rotate, File}, #state{name=File,count=Count,date=Date} = State) ->
  174. _ = lager_util:rotate_logfile(File, Count),
  175. State1 = close_file(State),
  176. schedule_rotation(File, Date),
  177. {ok, State1};
  178. handle_info({shaper_expired, Name}, #state{shaper=Shaper, name=Name, formatter=Formatter, formatter_config=FormatConfig} = State) ->
  179. Report = io_lib:format(
  180. "lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec",
  181. [Shaper#lager_shaper.dropped, Shaper#lager_shaper.hwm]),
  182. ReportMsg = lager_msg:new(Report, warning, [], []),
  183. write(State, lager_msg:timestamp(ReportMsg),
  184. lager_msg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig)),
  185. {ok, State#state{shaper=Shaper#lager_shaper{dropped=0, mps=1, lasttime=os:timestamp()}}};
  186. handle_info(_Info, State) ->
  187. {ok, State}.
  188. %% @private
  189. terminate(_Reason, State) ->
  190. %% leaving this function call unmatched makes dialyzer cranky
  191. _ = close_file(State),
  192. ok.
  193. %% @private
  194. code_change(_OldVsn, State, _Extra) ->
  195. {ok, State}.
  196. %% Convert the config into a gen_event handler ID
  197. config_to_id({Name,_Severity}) when is_list(Name) ->
  198. {?MODULE, Name};
  199. config_to_id({Name,_Severity,_Size,_Rotation,_Count}) ->
  200. {?MODULE, Name};
  201. config_to_id([{Name,_Severity,_Size,_Rotation,_Count}, _Format]) ->
  202. {?MODULE, Name};
  203. config_to_id([{Name,_Severity}, _Format]) when is_list(Name) ->
  204. {?MODULE, Name};
  205. config_to_id(Config) ->
  206. case proplists:get_value(file, Config) of
  207. undefined ->
  208. erlang:error(no_file);
  209. File ->
  210. {?MODULE, File}
  211. end.
  212. write(#state{name=Name, fd=FD, inode=Inode, flap=Flap, size=RotSize,
  213. count=Count} = State, Timestamp, Level, Msg) ->
  214. LastCheck = timer:now_diff(Timestamp, State#state.last_check) div 1000,
  215. case LastCheck >= State#state.check_interval orelse FD == undefined of
  216. true ->
  217. %% need to check for rotation
  218. case lager_util:ensure_logfile(Name, FD, Inode, {State#state.sync_size, State#state.sync_interval}) of
  219. {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
  220. case lager_util:rotate_logfile(Name, Count) of
  221. ok ->
  222. %% go around the loop again, we'll do another rotation check and hit the next clause of ensure_logfile
  223. write(State, Timestamp, Level, Msg);
  224. {error, Reason} ->
  225. case Flap of
  226. true ->
  227. State;
  228. _ ->
  229. ?INT_LOG(error, "Failed to rotate log file ~s with error ~s", [Name, file:format_error(Reason)]),
  230. State#state{flap=true}
  231. end
  232. end;
  233. {ok, {NewFD, NewInode, _}} ->
  234. %% update our last check and try again
  235. do_write(State#state{last_check=Timestamp, fd=NewFD, inode=NewInode}, Level, Msg);
  236. {error, Reason} ->
  237. case Flap of
  238. true ->
  239. State;
  240. _ ->
  241. ?INT_LOG(error, "Failed to reopen log file ~s with error ~s", [Name, file:format_error(Reason)]),
  242. State#state{flap=true}
  243. end
  244. end;
  245. false ->
  246. do_write(State, Level, Msg)
  247. end.
  248. do_write(#state{fd=FD, name=Name, flap=Flap} = State, Level, Msg) ->
  249. %% delayed_write doesn't report errors
  250. _ = file:write(FD, unicode:characters_to_binary(Msg)),
  251. {mask, SyncLevel} = State#state.sync_on,
  252. case (Level band SyncLevel) /= 0 of
  253. true ->
  254. %% force a sync on any message that matches the 'sync_on' bitmask
  255. Flap2 = case file:datasync(FD) of
  256. {error, Reason2} when Flap == false ->
  257. ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
  258. [Name, file:format_error(Reason2)]),
  259. true;
  260. ok ->
  261. false;
  262. _ ->
  263. Flap
  264. end,
  265. State#state{flap=Flap2};
  266. _ ->
  267. State
  268. end.
  269. validate_loglevel(Level) ->
  270. try lager_util:config_to_mask(Level) of
  271. Levels ->
  272. Levels
  273. catch
  274. _:_ ->
  275. false
  276. end.
  277. validate_logfile_proplist(List) ->
  278. try validate_logfile_proplist(List, []) of
  279. Res ->
  280. case proplists:get_value(file, Res) of
  281. undefined ->
  282. ?INT_LOG(error, "Missing required file option", []),
  283. false;
  284. _File ->
  285. %% merge with the default options
  286. {ok, DefaultRotationDate} = lager_util:parse_rotation_date_spec(?DEFAULT_ROTATION_DATE),
  287. lists:keymerge(1, lists:sort(Res), lists:sort([
  288. {level, validate_loglevel(?DEFAULT_LOG_LEVEL)}, {date, DefaultRotationDate},
  289. {size, ?DEFAULT_ROTATION_SIZE}, {count, ?DEFAULT_ROTATION_COUNT},
  290. {sync_on, validate_loglevel(?DEFAULT_SYNC_LEVEL)}, {sync_interval, ?DEFAULT_SYNC_INTERVAL},
  291. {sync_size, ?DEFAULT_SYNC_SIZE}, {check_interval, ?DEFAULT_CHECK_INTERVAL},
  292. {formatter, lager_default_formatter}, {formatter_config, []}
  293. ]))
  294. end
  295. catch
  296. {bad_config, Msg, Value} ->
  297. ?INT_LOG(error, "~s ~p for file ~p",
  298. [Msg, Value, proplists:get_value(file, List)]),
  299. false
  300. end.
  301. validate_logfile_proplist([], Acc) ->
  302. Acc;
  303. validate_logfile_proplist([{file, File}|Tail], Acc) ->
  304. %% is there any reasonable validation we can do here?
  305. validate_logfile_proplist(Tail, [{file, File}|Acc]);
  306. validate_logfile_proplist([{level, Level}|Tail], Acc) ->
  307. case validate_loglevel(Level) of
  308. false ->
  309. throw({bad_config, "Invalid loglevel", Level});
  310. Res ->
  311. validate_logfile_proplist(Tail, [{level, Res}|Acc])
  312. end;
  313. validate_logfile_proplist([{size, Size}|Tail], Acc) ->
  314. case Size of
  315. S when is_integer(S), S >= 0 ->
  316. validate_logfile_proplist(Tail, [{size, Size}|Acc]);
  317. _ ->
  318. throw({bad_config, "Invalid rotation size", Size})
  319. end;
  320. validate_logfile_proplist([{count, Count}|Tail], Acc) ->
  321. case Count of
  322. C when is_integer(C), C >= 0 ->
  323. validate_logfile_proplist(Tail, [{count, Count}|Acc]);
  324. _ ->
  325. throw({bad_config, "Invalid rotation count", Count})
  326. end;
  327. validate_logfile_proplist([{high_water_mark, HighWaterMark}|Tail], Acc) ->
  328. case HighWaterMark of
  329. Hwm when is_integer(Hwm), Hwm >= 0 ->
  330. validate_logfile_proplist(Tail, [{high_water_mark, Hwm}|Acc]);
  331. _ ->
  332. throw({bad_config, "Invalid high water mark", HighWaterMark})
  333. end;
  334. validate_logfile_proplist([{date, Date}|Tail], Acc) ->
  335. case lager_util:parse_rotation_date_spec(Date) of
  336. {ok, Spec} ->
  337. validate_logfile_proplist(Tail, [{date, Spec}|Acc]);
  338. {error, _} when Date == "" ->
  339. %% legacy config allowed blanks
  340. validate_logfile_proplist(Tail, [{date, undefined}|Acc]);
  341. {error, _} ->
  342. throw({bad_config, "Invalid rotation date", Date})
  343. end;
  344. validate_logfile_proplist([{sync_interval, SyncInt}|Tail], Acc) ->
  345. case SyncInt of
  346. Val when is_integer(Val), Val >= 0 ->
  347. validate_logfile_proplist(Tail, [{sync_interval, Val}|Acc]);
  348. _ ->
  349. throw({bad_config, "Invalid sync interval", SyncInt})
  350. end;
  351. validate_logfile_proplist([{sync_size, SyncSize}|Tail], Acc) ->
  352. case SyncSize of
  353. Val when is_integer(Val), Val >= 0 ->
  354. validate_logfile_proplist(Tail, [{sync_size, Val}|Acc]);
  355. _ ->
  356. throw({bad_config, "Invalid sync size", SyncSize})
  357. end;
  358. validate_logfile_proplist([{check_interval, CheckInt}|Tail], Acc) ->
  359. case CheckInt of
  360. Val when is_integer(Val), Val >= 0 ->
  361. validate_logfile_proplist(Tail, [{check_interval, Val}|Acc]);
  362. always ->
  363. validate_logfile_proplist(Tail, [{check_interval, 0}|Acc]);
  364. _ ->
  365. throw({bad_config, "Invalid check interval", CheckInt})
  366. end;
  367. validate_logfile_proplist([{sync_on, Level}|Tail], Acc) ->
  368. case validate_loglevel(Level) of
  369. false ->
  370. throw({bad_config, "Invalid sync on level", Level});
  371. Res ->
  372. validate_logfile_proplist(Tail, [{sync_on, Res}|Acc])
  373. end;
  374. validate_logfile_proplist([{formatter, Fmt}|Tail], Acc) ->
  375. case is_atom(Fmt) of
  376. true ->
  377. validate_logfile_proplist(Tail, [{formatter, Fmt}|Acc]);
  378. false ->
  379. throw({bad_config, "Invalid formatter module", Fmt})
  380. end;
  381. validate_logfile_proplist([{formatter_config, FmtCfg}|Tail], Acc) ->
  382. case is_list(FmtCfg) of
  383. true ->
  384. validate_logfile_proplist(Tail, [{formatter_config, FmtCfg}|Acc]);
  385. false ->
  386. throw({bad_config, "Invalid formatter config", FmtCfg})
  387. end;
  388. validate_logfile_proplist([Other|_Tail], _Acc) ->
  389. throw({bad_config, "Invalid option", Other}).
  390. schedule_rotation(_, undefined) ->
  391. ok;
  392. schedule_rotation(Name, Date) ->
  393. erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}),
  394. ok.
  395. close_file(#state{fd=undefined} = State) ->
  396. State;
  397. close_file(#state{fd=FD} = State) ->
  398. %% Flush and close any file handles.
  399. _ = file:datasync(FD),
  400. _ = file:close(FD),
  401. State#state{fd=undefined}.
  402. -ifdef(TEST).
  403. get_loglevel_test() ->
  404. {ok, Level, _} = handle_call(get_loglevel,
  405. #state{name="bar", level=lager_util:config_to_mask(info), fd=0, inode=0}),
  406. ?assertEqual(Level, lager_util:config_to_mask(info)),
  407. {ok, Level2, _} = handle_call(get_loglevel,
  408. #state{name="foo", level=lager_util:config_to_mask(warning), fd=0, inode=0}),
  409. ?assertEqual(Level2, lager_util:config_to_mask(warning)).
  410. rotation_test_() ->
  411. {foreach,
  412. fun() ->
  413. SyncLevel = validate_loglevel(?DEFAULT_SYNC_LEVEL),
  414. SyncSize = ?DEFAULT_SYNC_SIZE,
  415. SyncInterval = ?DEFAULT_SYNC_INTERVAL,
  416. CheckInterval = 0, %% hard to test delayed mode
  417. TestDir = lager_util:create_test_dir(),
  418. TestLog = filename:join(TestDir, "test.log"),
  419. #state{name=TestLog, level=?DEBUG, sync_on=SyncLevel,
  420. sync_size=SyncSize, sync_interval=SyncInterval, check_interval=CheckInterval}
  421. end,
  422. fun(#state{name=TestLog}) ->
  423. lager_util:delete_test_dir(filename:dirname(TestLog))
  424. end, [
  425. fun(DefaultState = #state{name=TestLog, sync_size=SyncSize, sync_interval = SyncInterval}) ->
  426. {"External rotation should work",
  427. fun() ->
  428. {ok, {FD, Inode, _}} = lager_util:open_logfile(TestLog, {SyncSize, SyncInterval}),
  429. State0 = DefaultState#state{fd=FD, inode=Inode},
  430. ?assertMatch(#state{name=TestLog, level=?DEBUG, fd=FD, inode=Inode},
  431. write(State0, os:timestamp(), ?DEBUG, "hello world")),
  432. file:delete(TestLog),
  433. Result = write(State0, os:timestamp(), ?DEBUG, "hello world"),
  434. %% assert file has changed
  435. ?assert(#state{name=TestLog, level=?DEBUG, fd=FD, inode=Inode} =/= Result),
  436. ?assertMatch(#state{name=TestLog, level=?DEBUG}, Result),
  437. file:rename(TestLog, TestLog ++ ".1"),
  438. Result2 = write(Result, os:timestamp(), ?DEBUG, "hello world"),
  439. %% assert file has changed
  440. ?assert(Result =/= Result2),
  441. ?assertMatch(#state{name=TestLog, level=?DEBUG}, Result2),
  442. ok
  443. end}
  444. end,
  445. fun(DefaultState = #state{name=TestLog, sync_size=SyncSize, sync_interval = SyncInterval}) ->
  446. {"Internal rotation and delayed write",
  447. fun() ->
  448. TestLog0 = TestLog ++ ".0",
  449. CheckInterval = 3000, % 3 sec
  450. RotationSize = 15,
  451. PreviousCheck = os:timestamp(),
  452. {ok, {FD, Inode, _}} = lager_util:open_logfile(TestLog, {SyncSize, SyncInterval}),
  453. State0 = DefaultState#state{
  454. fd=FD, inode=Inode, size=RotationSize,
  455. check_interval=CheckInterval, last_check=PreviousCheck},
  456. %% new message within check interval with sync_on level
  457. Msg1Timestamp = add_secs(PreviousCheck, 1),
  458. State0 = State1 = write(State0, Msg1Timestamp, ?ERROR, "big big message 1"),
  459. %% new message within check interval under sync_on level
  460. %% not written to disk yet
  461. Msg2Timestamp = add_secs(PreviousCheck, 2),
  462. State0 = State2 = write(State1, Msg2Timestamp, ?DEBUG, "buffered message 2"),
  463. %% although file size is big enough...
  464. {ok, FInfo} = file:read_file_info(TestLog),
  465. ?assert(RotationSize < FInfo#file_info.size),
  466. %% ...no rotation yet
  467. ?assertEqual(PreviousCheck, State2#state.last_check),
  468. ?assertNot(filelib:is_regular(TestLog0)),
  469. %% new message after check interval
  470. Msg3Timestamp = add_secs(PreviousCheck, 4),
  471. _State3 = write(State2, Msg3Timestamp, ?DEBUG, "message 3"),
  472. %% rotation happened
  473. ?assert(filelib:is_regular(TestLog0)),
  474. {ok, Bin1} = file:read_file(TestLog0),
  475. {ok, Bin2} = file:read_file(TestLog),
  476. %% message 1-3 written to file
  477. ?assertEqual(<<"big big message 1buffered message 2">>, Bin1),
  478. %% message 4 buffered, not yet written to file
  479. ?assertEqual(<<"">>, Bin2),
  480. ok
  481. end}
  482. end
  483. ]}.
  484. add_secs({Mega, Secs, Micro}, Add) ->
  485. NewSecs = Secs + Add,
  486. {Mega + NewSecs div 10000000, NewSecs rem 10000000, Micro}.
  487. filesystem_test_() ->
  488. {foreach,
  489. fun() ->
  490. error_logger:tty(false),
  491. application:load(lager),
  492. application:set_env(lager, handlers, [{lager_test_backend, info}]),
  493. application:set_env(lager, error_logger_redirect, false),
  494. application:set_env(lager, async_threshold, undefined),
  495. lager:start(),
  496. %% race condition where lager logs its own start up
  497. %% makes several tests fail. See test/lager_test_backend
  498. %% around line 800 for more information.
  499. timer:sleep(5),
  500. lager_test_backend:flush()
  501. end,
  502. fun(_) ->
  503. application:stop(lager),
  504. application:stop(goldrush),
  505. error_logger:tty(true)
  506. end, [
  507. {"under normal circumstances, file should be opened",
  508. fun() ->
  509. TestDir = lager_util:create_test_dir(),
  510. TestLog = filename:join(TestDir, "test.log"),
  511. gen_event:add_handler(lager_event, lager_file_backend,
  512. [{TestLog, info}, {lager_default_formatter}]),
  513. lager:log(error, self(), "Test message"),
  514. {ok, Bin} = file:read_file(TestLog),
  515. Pid = pid_to_list(self()),
  516. ?assertMatch([_, _, "[error]", Pid, "Test message\n"],
  517. re:split(Bin, " ", [{return, list}, {parts, 5}])),
  518. lager_util:delete_test_dir(TestDir)
  519. end},
  520. {"don't choke on unicode",
  521. fun() ->
  522. TestDir = lager_util:create_test_dir(),
  523. TestLog = filename:join(TestDir, "test.log"),
  524. gen_event:add_handler(lager_event, lager_file_backend,
  525. [{TestLog, info}, {lager_default_formatter}]),
  526. lager:log(error, self(),"~ts", [[20013,25991,27979,35797]]),
  527. {ok, Bin} = file:read_file(TestLog),
  528. Pid = pid_to_list(self()),
  529. ?assertMatch([_, _, "[error]", Pid,
  530. [228,184,173,230,150,135,230,181,139,232,175,149, $\n]],
  531. re:split(Bin, " ", [{return, list}, {parts, 5}])),
  532. lager_util:delete_test_dir(TestDir)
  533. end},
  534. {"don't choke on latin-1",
  535. fun() ->
  536. TestDir = lager_util:create_test_dir(),
  537. TestLog = filename:join(TestDir, "test.log"),
  538. %% XXX if this test fails, check that this file is encoded latin-1, not utf-8!
  539. gen_event:add_handler(lager_event, lager_file_backend,
  540. [{TestLog, info}, {lager_default_formatter}]),
  541. lager:log(error, self(),"~ts", [[76, 198, 221, 206, 78, $-, 239]]),
  542. {ok, Bin} = file:read_file(TestLog),
  543. Pid = pid_to_list(self()),
  544. Res = re:split(Bin, " ", [{return, list}, {parts, 5}]),
  545. ?assertMatch([_, _, "[error]", Pid,
  546. [76,195,134,195,157,195,142,78,45,195,175,$\n]], Res),
  547. lager_util:delete_test_dir(TestDir)
  548. end},
  549. {"file can't be opened on startup triggers an error message",
  550. fun() ->
  551. TestDir = lager_util:create_test_dir(),
  552. TestLog = filename:join(TestDir, "test.log"),
  553. ?assertEqual(ok, file:write_file(TestLog, [])),
  554. {ok, FInfo} = file:read_file_info(TestLog),
  555. file:write_file_info(TestLog, FInfo#file_info{mode = 0}),
  556. gen_event:add_handler(lager_event, lager_file_backend,
  557. {TestLog, info, 10*1024*1024, "$D0", 5}),
  558. ?assertEqual(1, lager_test_backend:count()),
  559. {_Level, _Time,Message,_Metadata} = lager_test_backend:pop(),
  560. ?assertEqual(
  561. "Failed to open log file " ++ TestLog ++ " with error permission denied",
  562. lists:flatten(Message)),
  563. lager_util:delete_test_dir(TestDir)
  564. end},
  565. {"file that becomes unavailable at runtime should trigger an error message",
  566. fun() ->
  567. TestDir = lager_util:create_test_dir(),
  568. TestLog = filename:join(TestDir, "test.log"),
  569. gen_event:add_handler(lager_event, lager_file_backend,
  570. [{file, TestLog}, {level, info}, {check_interval, 0}]),
  571. ?assertEqual(0, lager_test_backend:count()),
  572. lager:log(error, self(), "Test message"),
  573. ?assertEqual(1, lager_test_backend:count()),
  574. file:delete(TestLog),
  575. file:write_file(TestLog, ""),
  576. {ok, FInfo} = file:read_file_info(TestLog),
  577. file:write_file_info(TestLog, FInfo#file_info{mode = 0}),
  578. lager:log(error, self(), "Test message"),
  579. ?assertEqual(3, lager_test_backend:count()),
  580. lager_test_backend:pop(),
  581. lager_test_backend:pop(),
  582. {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
  583. ?assertEqual(
  584. "Failed to reopen log file " ++ TestLog ++ " with error permission denied",
  585. lists:flatten(Message)),
  586. lager_util:delete_test_dir(TestDir)
  587. end},
  588. {"unavailable files that are fixed at runtime should start having log messages written",
  589. fun() ->
  590. TestDir = lager_util:create_test_dir(),
  591. TestLog = filename:join(TestDir, "test.log"),
  592. ?assertEqual(ok, file:write_file(TestLog, [])),
  593. {ok, FInfo} = file:read_file_info(TestLog),
  594. OldPerms = FInfo#file_info.mode,
  595. file:write_file_info(TestLog, FInfo#file_info{mode = 0}),
  596. gen_event:add_handler(lager_event, lager_file_backend,
  597. [{file, TestLog},{check_interval, 0}]),
  598. ?assertEqual(1, lager_test_backend:count()),
  599. {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(),
  600. ?assertEqual(
  601. "Failed to open log file " ++ TestLog ++ " with error permission denied",
  602. lists:flatten(Message)),
  603. file:write_file_info(TestLog, FInfo#file_info{mode = OldPerms}),
  604. lager:log(error, self(), "Test message"),
  605. {ok, Bin} = file:read_file(TestLog),
  606. Pid = pid_to_list(self()),
  607. ?assertMatch([_, _, "[error]", Pid, "Test message\n"],
  608. re:split(Bin, " ", [{return, list}, {parts, 5}])),
  609. lager_util:delete_test_dir(TestDir)
  610. end},
  611. {"external logfile rotation/deletion should be handled",
  612. fun() ->
  613. TestDir = lager_util:create_test_dir(),
  614. TestLog = filename:join(TestDir, "test.log"),
  615. TestLog0 = TestLog ++ ".0",
  616. gen_event:add_handler(lager_event, lager_file_backend,
  617. [{file, TestLog}, {level, info}, {check_interval, 0}]),
  618. ?assertEqual(0, lager_test_backend:count()),
  619. lager:log(error, self(), "Test message1"),
  620. ?assertEqual(1, lager_test_backend:count()),
  621. file:delete(TestLog),
  622. file:write_file(TestLog, ""),
  623. lager:log(error, self(), "Test message2"),
  624. {ok, Bin} = file:read_file(TestLog),
  625. Pid = pid_to_list(self()),
  626. ?assertMatch([_, _, "[error]", Pid, "Test message2\n"],
  627. re:split(Bin, " ", [{return, list}, {parts, 5}])),
  628. file:rename(TestLog, TestLog0),
  629. lager:log(error, self(), "Test message3"),
  630. {ok, Bin2} = file:read_file(TestLog),
  631. ?assertMatch([_, _, "[error]", Pid, "Test message3\n"],
  632. re:split(Bin2, " ", [{return, list}, {parts, 5}])),
  633. lager_util:delete_test_dir(TestDir)
  634. end},
  635. {"internal size rotation should work",
  636. fun() ->
  637. TestDir = lager_util:create_test_dir(),
  638. TestLog = filename:join(TestDir, "test.log"),
  639. TestLog0 = TestLog ++ ".0",
  640. gen_event:add_handler(lager_event, lager_file_backend,
  641. [{file, TestLog}, {level, info}, {check_interval, 0}, {size, 10}]),
  642. lager:log(error, self(), "Test message1"),
  643. lager:log(error, self(), "Test message1"),
  644. ?assert(filelib:is_regular(TestLog0)),
  645. lager_util:delete_test_dir(TestDir)
  646. end},
  647. {"internal time rotation should work",
  648. fun() ->
  649. TestDir = lager_util:create_test_dir(),
  650. TestLog = filename:join(TestDir, "test.log"),
  651. TestLog0 = TestLog ++ ".0",
  652. gen_event:add_handler(lager_event, lager_file_backend,
  653. [{file, TestLog}, {level, info}, {check_interval, 1000}]),
  654. lager:log(error, self(), "Test message1"),
  655. lager:log(error, self(), "Test message1"),
  656. whereis(lager_event) ! {rotate, TestLog},
  657. lager:log(error, self(), "Test message1"),
  658. ?assert(filelib:is_regular(TestLog0)),
  659. lager_util:delete_test_dir(TestDir)
  660. end},
  661. {"rotation call should work",
  662. fun() ->
  663. TestDir = lager_util:create_test_dir(),
  664. TestLog = filename:join(TestDir, "test.log"),
  665. TestLog0 = TestLog ++ ".0",
  666. gen_event:add_handler(lager_event, {lager_file_backend, TestLog},
  667. [{file, TestLog}, {level, info}, {check_interval, 1000}]),
  668. lager:log(error, self(), "Test message1"),
  669. lager:log(error, self(), "Test message1"),
  670. gen_event:call(lager_event, {lager_file_backend, TestLog}, rotate, infinity),
  671. lager:log(error, self(), "Test message1"),
  672. ?assert(filelib:is_regular(TestLog0)),
  673. lager_util:delete_test_dir(TestDir)
  674. end},
  675. {"sync_on option should work",
  676. fun() ->
  677. TestDir = lager_util:create_test_dir(),
  678. TestLog = filename:join(TestDir, "test.log"),
  679. gen_event:add_handler(lager_event, lager_file_backend, [{file, TestLog},
  680. {level, info}, {sync_on, "=info"}, {check_interval, 5000}, {sync_interval, 5000}]),
  681. lager:log(error, self(), "Test message1"),
  682. lager:log(error, self(), "Test message1"),
  683. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  684. lager:log(info, self(), "Test message1"),
  685. {ok, Bin} = file:read_file(TestLog),
  686. ?assert(<<>> /= Bin),
  687. lager_util:delete_test_dir(TestDir)
  688. end},
  689. {"sync_on none option should work (also tests sync_interval)",
  690. fun() ->
  691. TestDir = lager_util:create_test_dir(),
  692. TestLog = filename:join(TestDir, "test.log"),
  693. gen_event:add_handler(lager_event, lager_file_backend, [{file, TestLog},
  694. {level, info}, {sync_on, "none"}, {check_interval, 5000}, {sync_interval, 1000}]),
  695. lager:log(error, self(), "Test message1"),
  696. lager:log(error, self(), "Test message1"),
  697. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  698. lager:log(info, self(), "Test message1"),
  699. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  700. timer:sleep(2000),
  701. {ok, Bin} = file:read_file(TestLog),
  702. ?assert(<<>> /= Bin),
  703. lager_util:delete_test_dir(TestDir)
  704. end},
  705. {"sync_size option should work",
  706. fun() ->
  707. TestDir = lager_util:create_test_dir(),
  708. TestLog = filename:join(TestDir, "test.log"),
  709. gen_event:add_handler(lager_event, lager_file_backend, [{file, TestLog}, {level, info},
  710. {sync_on, "none"}, {check_interval, 5001}, {sync_size, 640}, {sync_interval, 5001}]),
  711. lager:log(error, self(), "Test messageis64bytes"),
  712. lager:log(error, self(), "Test messageis64bytes"),
  713. lager:log(error, self(), "Test messageis64bytes"),
  714. lager:log(error, self(), "Test messageis64bytes"),
  715. lager:log(error, self(), "Test messageis64bytes"),
  716. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  717. lager:log(error, self(), "Test messageis64bytes"),
  718. lager:log(error, self(), "Test messageis64bytes"),
  719. lager:log(error, self(), "Test messageis64bytes"),
  720. lager:log(error, self(), "Test messageis64bytes"),
  721. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  722. %% now we've written enough bytes
  723. lager:log(error, self(), "Test messageis64bytes"),
  724. {ok, Bin} = file:read_file(TestLog),
  725. ?assert(<<>> /= Bin),
  726. lager_util:delete_test_dir(TestDir)
  727. end},
  728. {"runtime level changes",
  729. fun() ->
  730. TestDir = lager_util:create_test_dir(),
  731. TestLog = filename:join(TestDir, "test.log"),
  732. gen_event:add_handler(lager_event, {lager_file_backend, TestLog}, {TestLog, info}),
  733. ?assertEqual(0, lager_test_backend:count()),
  734. lager:log(info, self(), "Test message1"),
  735. lager:log(error, self(), "Test message2"),
  736. {ok, Bin} = file:read_file(TestLog),
  737. Lines = length(re:split(Bin, "\n", [{return, list}, trim])),
  738. ?assertEqual(Lines, 2),
  739. ?assertEqual(ok, lager:set_loglevel(lager_file_backend, TestLog, warning)),
  740. lager:log(info, self(), "Test message3"), %% this won't get logged
  741. lager:log(error, self(), "Test message4"),
  742. {ok, Bin2} = file:read_file(TestLog),
  743. Lines2 = length(re:split(Bin2, "\n", [{return, list}, trim])),
  744. ?assertEqual(Lines2, 3),
  745. lager_util:delete_test_dir(TestDir)
  746. end},
  747. {"invalid runtime level changes",
  748. fun() ->
  749. TestDir = lager_util:create_test_dir(),
  750. TestLog = filename:join(TestDir, "test.log"),
  751. TestLog3 = filename:join(TestDir, "test3.log"),
  752. gen_event:add_handler(lager_event, lager_file_backend,
  753. [{TestLog, info, 10*1024*1024, "$D0", 5}, {lager_default_formatter}]),
  754. gen_event:add_handler(lager_event, lager_file_backend, {TestLog3, info}),
  755. ?assertEqual({error, bad_module}, lager:set_loglevel(lager_file_backend, TestLog, warning)),
  756. lager_util:delete_test_dir(TestDir)
  757. end},
  758. {"tracing should work",
  759. fun() ->
  760. TestDir = lager_util:create_test_dir(),
  761. TestLog = filename:join(TestDir, "test.log"),
  762. gen_event:add_handler(lager_event, lager_file_backend, {TestLog, critical}),
  763. lager:error("Test message"),
  764. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  765. {Level, _} = lager_config:get({lager_event, loglevel}),
  766. lager_config:set({lager_event, loglevel}, {Level,
  767. [{[{module, ?MODULE}], ?DEBUG, {lager_file_backend, TestLog}}]}),
  768. lager:error("Test message"),
  769. timer:sleep(1000),
  770. {ok, Bin} = file:read_file(TestLog),
  771. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  772. re:split(Bin, " ", [{return, list}, {parts, 5}])),
  773. lager_util:delete_test_dir(TestDir)
  774. end},
  775. {"tracing should not duplicate messages",
  776. fun() ->
  777. TestDir = lager_util:create_test_dir(),
  778. TestLog = filename:join(TestDir, "test.log"),
  779. gen_event:add_handler(lager_event, lager_file_backend,
  780. [{file, TestLog}, {level, critical}, {check_interval, always}]),
  781. lager:critical("Test message"),
  782. {ok, Bin1} = file:read_file(TestLog),
  783. ?assertMatch([_, _, "[critical]", _, "Test message\n"],
  784. re:split(Bin1, " ", [{return, list}, {parts, 5}])),
  785. ok = file:delete(TestLog),
  786. {Level, _} = lager_config:get({lager_event, loglevel}),
  787. lager_config:set({lager_event, loglevel},
  788. {Level, [{[{module, ?MODULE}], ?DEBUG, {lager_file_backend, TestLog}}]}),
  789. lager:critical("Test message"),
  790. {ok, Bin2} = file:read_file(TestLog),
  791. ?assertMatch([_, _, "[critical]", _, "Test message\n"],
  792. re:split(Bin2, " ", [{return, list}, {parts, 5}])),
  793. ok = file:delete(TestLog),
  794. lager:error("Test message"),
  795. {ok, Bin3} = file:read_file(TestLog),
  796. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  797. re:split(Bin3, " ", [{return, list}, {parts, 5}])),
  798. lager_util:delete_test_dir(TestDir)
  799. end},
  800. {"tracing to a dedicated file should work",
  801. fun() ->
  802. TestDir = lager_util:create_test_dir(),
  803. TestLog = filename:join(TestDir, "foo.log"),
  804. {ok, _} = lager:trace_file(TestLog, [{module, ?MODULE}]),
  805. lager:error("Test message"),
  806. %% not eligible for trace
  807. lager:log(error, self(), "Test message"),
  808. {ok, Bin3} = file:read_file(TestLog),
  809. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  810. re:split(Bin3, " ", [{return, list}, {parts, 5}])),
  811. lager_util:delete_test_dir(TestDir)
  812. end},
  813. {"tracing to a dedicated file should work even if root_log is set",
  814. fun() ->
  815. TestDir = lager_util:create_test_dir(),
  816. LogName = "foo.log",
  817. LogPath = filename:join(TestDir, LogName),
  818. application:set_env(lager, log_root, TestDir),
  819. {ok, _} = lager:trace_file(LogName, [{module, ?MODULE}]),
  820. lager:error("Test message"),
  821. %% not eligible for trace
  822. lager:log(error, self(), "Test message"),
  823. {ok, Bin3} = file:read_file(LogPath),
  824. application:unset_env(lager, log_root),
  825. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  826. re:split(Bin3, " ", [{return, list}, {parts, 5}])),
  827. lager_util:delete_test_dir(TestDir)
  828. end},
  829. {"tracing with options should work",
  830. fun() ->
  831. TestDir = lager_util:create_test_dir(),
  832. TestLog = filename:join(TestDir, "foo.log"),
  833. TestLog0 = TestLog ++ ".0",
  834. {ok, _} = lager:trace_file(TestLog, [{module, ?MODULE}],
  835. [{size, 20}, {check_interval, 1}]),
  836. lager:error("Test message"),
  837. ?assertNot(filelib:is_regular(TestLog0)),
  838. %% rotation is sensitive to intervals between
  839. %% writes so we sleep to exceed the 1
  840. %% millisecond interval specified by
  841. %% check_interval above
  842. timer:sleep(2),
  843. lager:error("Test message"),
  844. timer:sleep(10),
  845. ?assert(filelib:is_regular(TestLog0)),
  846. lager_util:delete_test_dir(TestDir)
  847. end}
  848. ]}.
  849. trace_files_test_() ->
  850. {foreach,
  851. fun() ->
  852. Dir = lager_util:create_test_dir(),
  853. Log = filename:join(Dir, "test.log"),
  854. Debug = filename:join(Dir, "debug.log"),
  855. Events = filename:join(Dir, "events.log"),
  856. error_logger:tty(false),
  857. application:load(lager),
  858. application:set_env(lager, handlers, [
  859. {lager_file_backend, [
  860. {file, Log},
  861. {level, error},
  862. {formatter, lager_default_formatter},
  863. {formatter_config, [message, "\n"]}
  864. ]}
  865. ]),
  866. application:set_env(lager, traces, [
  867. { % get default level of debug
  868. {lager_file_backend, Debug}, [{module, ?MODULE}]
  869. },
  870. { % Handler Filters Level
  871. {lager_file_backend, Events}, [{module, ?MODULE}], notice
  872. }
  873. ]),
  874. application:set_env(lager, async_threshold, undefined),
  875. lager:start(),
  876. {Dir, Log, Debug, Events}
  877. end,
  878. fun({Dir, _, _, _}) ->
  879. catch ets:delete(lager_config),
  880. application:unset_env(lager, traces),
  881. application:stop(lager),
  882. lager_util:delete_test_dir(Dir),
  883. error_logger:tty(true)
  884. end, [
  885. fun({_, Log, Debug, Events}) ->
  886. {"a trace using file backend set up in configuration should work",
  887. fun() ->
  888. lager:error("trace test error message"),
  889. lager:info("info message"),
  890. %% not eligible for trace
  891. lager:log(error, self(), "Not trace test message"),
  892. {ok, BinInfo} = file:read_file(Events),
  893. ?assertMatch([_, _, "[error]", _, "trace test error message\n"],
  894. re:split(BinInfo, " ", [{return, list}, {parts, 5}])),
  895. ?assert(filelib:is_regular(Log)),
  896. {ok, BinInfo2} = file:read_file(Log),
  897. ?assertMatch(["trace test error message", "Not trace test message\n"],
  898. re:split(BinInfo2, "\n", [{return, list}, {parts, 2}])),
  899. ?assert(filelib:is_regular(Debug)),
  900. %% XXX Aughhhh, wish I could force this to flush somehow...
  901. % should take about 1 second, try for 3 ...
  902. ?assertEqual(2, count_lines_until(2, add_secs(os:timestamp(), 3), Debug, 0))
  903. end}
  904. end
  905. ]}.
  906. count_lines_until(Lines, Timeout, File, Last) ->
  907. case timer:now_diff(Timeout, os:timestamp()) > 0 of
  908. true ->
  909. timer:sleep(333),
  910. {ok, Bin} = file:read_file(File),
  911. case erlang:length(re:split(Bin, "\n", [{return, list}, trim])) of
  912. Count when Count < Lines ->
  913. count_lines_until(Lines, Timeout, File, Count);
  914. Count ->
  915. Count
  916. end;
  917. _ ->
  918. Last
  919. end.
  920. formatting_test_() ->
  921. {foreach,
  922. fun() ->
  923. Dir = lager_util:create_test_dir(),
  924. Log1 = filename:join(Dir, "test.log"),
  925. Log2 = filename:join(Dir, "test2.log"),
  926. ?assertEqual(ok, file:write_file(Log1, [])),
  927. ?assertEqual(ok, file:write_file(Log2, [])),
  928. error_logger:tty(false),
  929. application:load(lager),
  930. application:set_env(lager, handlers, [{lager_test_backend, info}]),
  931. application:set_env(lager, error_logger_redirect, false),
  932. lager:start(),
  933. %% same race condition issue
  934. timer:sleep(5),
  935. {Dir, Log1, Log2}
  936. end,
  937. fun({Dir, _, _}) ->
  938. application:stop(lager),
  939. application:stop(goldrush),
  940. lager_util:delete_test_dir(Dir),
  941. error_logger:tty(true)
  942. end, [
  943. fun({_, Log1, Log2}) ->
  944. {"Should have two log files, the second prefixed with 2>",
  945. fun() ->
  946. gen_event:add_handler(lager_event, lager_file_backend,
  947. [{Log1, debug}, {lager_default_formatter, ["[",severity,"] ", message, "\n"]}]),
  948. gen_event:add_handler(lager_event, lager_file_backend,
  949. [{Log2, debug}, {lager_default_formatter, ["2> [",severity,"] ", message, "\n"]}]),
  950. lager:log(error, self(), "Test message"),
  951. ?assertMatch({ok, <<"[error] Test message\n">>},file:read_file(Log1)),
  952. ?assertMatch({ok, <<"2> [error] Test message\n">>},file:read_file(Log2))
  953. end}
  954. end
  955. ]}.
  956. config_validation_test_() ->
  957. [
  958. {"missing file",
  959. ?_assertEqual(false,
  960. validate_logfile_proplist([{level, info}, {size, 10}]))
  961. },
  962. {"bad level",
  963. ?_assertEqual(false,
  964. validate_logfile_proplist([{file, "test.log"}, {level, blah}, {size, 10}]))
  965. },
  966. {"bad size",
  967. ?_assertEqual(false,
  968. validate_logfile_proplist([{file, "test.log"}, {size, infinity}]))
  969. },
  970. {"bad count",
  971. ?_assertEqual(false,
  972. validate_logfile_proplist([{file, "test.log"}, {count, infinity}]))
  973. },
  974. {"bad high water mark",
  975. ?_assertEqual(false,
  976. validate_logfile_proplist([{file, "test.log"}, {high_water_mark, infinity}]))
  977. },
  978. {"bad date",
  979. ?_assertEqual(false,
  980. validate_logfile_proplist([{file, "test.log"}, {date, "midnight"}]))
  981. },
  982. {"blank date is ok",
  983. ?_assertMatch([_|_],
  984. validate_logfile_proplist([{file, "test.log"}, {date, ""}]))
  985. },
  986. {"bad sync_interval",
  987. ?_assertEqual(false,
  988. validate_logfile_proplist([{file, "test.log"}, {sync_interval, infinity}]))
  989. },
  990. {"bad sync_size",
  991. ?_assertEqual(false,
  992. validate_logfile_proplist([{file, "test.log"}, {sync_size, infinity}]))
  993. },
  994. {"bad check_interval",
  995. ?_assertEqual(false,
  996. validate_logfile_proplist([{file, "test.log"}, {check_interval, infinity}]))
  997. },
  998. {"bad sync_on level",
  999. ?_assertEqual(false,
  1000. validate_logfile_proplist([{file, "test.log"}, {sync_on, infinity}]))
  1001. },
  1002. {"bad formatter module",
  1003. ?_assertEqual(false,
  1004. validate_logfile_proplist([{file, "test.log"}, {formatter, "io:format"}]))
  1005. },
  1006. {"bad formatter config",
  1007. ?_assertEqual(false,
  1008. validate_logfile_proplist([{file, "test.log"}, {formatter_config, blah}]))
  1009. },
  1010. {"unknown option",
  1011. ?_assertEqual(false,
  1012. validate_logfile_proplist([{file, "test.log"}, {rhubarb, spicy}]))
  1013. }
  1014. ].
  1015. -endif.