25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1103 lines
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.