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.

736 lines
30 KiB

пре 12 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 13 година
пре 11 година
пре 11 година
пре 13 година
пре 12 година
пре 12 година
пре 12 година
пре 12 година
  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. -module(lager_util).
  17. -include_lib("kernel/include/file.hrl").
  18. -export([levels/0, level_to_num/1, num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1,
  19. open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
  20. localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1,
  21. calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3,
  22. trace_filter/1, trace_filter/2, expand_path/1]).
  23. -ifdef(TEST).
  24. -include_lib("eunit/include/eunit.hrl").
  25. -endif.
  26. -include("lager.hrl").
  27. levels() ->
  28. [debug, info, notice, warning, error, critical, alert, emergency, none].
  29. level_to_num(debug) -> ?DEBUG;
  30. level_to_num(info) -> ?INFO;
  31. level_to_num(notice) -> ?NOTICE;
  32. level_to_num(warning) -> ?WARNING;
  33. level_to_num(error) -> ?ERROR;
  34. level_to_num(critical) -> ?CRITICAL;
  35. level_to_num(alert) -> ?ALERT;
  36. level_to_num(emergency) -> ?EMERGENCY;
  37. level_to_num(none) -> ?LOG_NONE.
  38. num_to_level(?DEBUG) -> debug;
  39. num_to_level(?INFO) -> info;
  40. num_to_level(?NOTICE) -> notice;
  41. num_to_level(?WARNING) -> warning;
  42. num_to_level(?ERROR) -> error;
  43. num_to_level(?CRITICAL) -> critical;
  44. num_to_level(?ALERT) -> alert;
  45. num_to_level(?EMERGENCY) -> emergency;
  46. num_to_level(?LOG_NONE) -> none.
  47. -spec config_to_mask(atom()|string()) -> {'mask', integer()}.
  48. config_to_mask(Conf) ->
  49. Levels = config_to_levels(Conf),
  50. {mask, lists:foldl(fun(Level, Acc) ->
  51. level_to_num(Level) bor Acc
  52. end, 0, Levels)}.
  53. -spec mask_to_levels(non_neg_integer()) -> [lager:log_level()].
  54. mask_to_levels(Mask) ->
  55. mask_to_levels(Mask, levels(), []).
  56. mask_to_levels(_Mask, [], Acc) ->
  57. lists:reverse(Acc);
  58. mask_to_levels(Mask, [Level|Levels], Acc) ->
  59. NewAcc = case (level_to_num(Level) band Mask) /= 0 of
  60. true ->
  61. [Level|Acc];
  62. false ->
  63. Acc
  64. end,
  65. mask_to_levels(Mask, Levels, NewAcc).
  66. -spec config_to_levels(atom()|string()) -> [lager:log_level()].
  67. config_to_levels(Conf) when is_atom(Conf) ->
  68. config_to_levels(atom_to_list(Conf));
  69. config_to_levels([$! | Rest]) ->
  70. levels() -- config_to_levels(Rest);
  71. config_to_levels([$=, $< | Rest]) ->
  72. [_|Levels] = config_to_levels_int(Rest),
  73. lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
  74. config_to_levels([$<, $= | Rest]) ->
  75. [_|Levels] = config_to_levels_int(Rest),
  76. lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
  77. config_to_levels([$>, $= | Rest]) ->
  78. config_to_levels_int(Rest);
  79. config_to_levels([$=, $> | Rest]) ->
  80. config_to_levels_int(Rest);
  81. config_to_levels([$= | Rest]) ->
  82. [level_to_atom(Rest)];
  83. config_to_levels([$< | Rest]) ->
  84. Levels = config_to_levels_int(Rest),
  85. lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
  86. config_to_levels([$> | Rest]) ->
  87. [_|Levels] = config_to_levels_int(Rest),
  88. lists:filter(fun(E) -> lists:member(E, Levels) end, levels());
  89. config_to_levels(Conf) ->
  90. config_to_levels_int(Conf).
  91. %% internal function to break the recursion loop
  92. config_to_levels_int(Conf) ->
  93. Level = level_to_atom(Conf),
  94. lists:dropwhile(fun(E) -> E /= Level end, levels()).
  95. level_to_atom(String) ->
  96. Levels = levels(),
  97. try list_to_existing_atom(String) of
  98. Atom ->
  99. case lists:member(Atom, Levels) of
  100. true ->
  101. Atom;
  102. false ->
  103. erlang:error(badarg)
  104. end
  105. catch
  106. _:_ ->
  107. erlang:error(badarg)
  108. end.
  109. open_logfile(Name, Buffer) ->
  110. case filelib:ensure_dir(Name) of
  111. ok ->
  112. Options = [append, raw] ++
  113. case Buffer of
  114. {Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
  115. [{delayed_write, Size, Interval}];
  116. _ -> []
  117. end,
  118. case file:open(Name, Options) of
  119. {ok, FD} ->
  120. case file:read_file_info(Name) of
  121. {ok, FInfo} ->
  122. Inode = FInfo#file_info.inode,
  123. {ok, {FD, Inode, FInfo#file_info.size}};
  124. X -> X
  125. end;
  126. Y -> Y
  127. end;
  128. Z -> Z
  129. end.
  130. ensure_logfile(Name, FD, Inode, Buffer) ->
  131. case file:read_file_info(Name) of
  132. {ok, FInfo} ->
  133. Inode2 = FInfo#file_info.inode,
  134. case Inode == Inode2 of
  135. true ->
  136. {ok, {FD, Inode, FInfo#file_info.size}};
  137. false ->
  138. %% delayed write can cause file:close not to do a close
  139. _ = file:close(FD),
  140. _ = file:close(FD),
  141. case open_logfile(Name, Buffer) of
  142. {ok, {FD2, Inode3, Size}} ->
  143. %% inode changed, file was probably moved and
  144. %% recreated
  145. {ok, {FD2, Inode3, Size}};
  146. Error ->
  147. Error
  148. end
  149. end;
  150. _ ->
  151. %% delayed write can cause file:close not to do a close
  152. _ = file:close(FD),
  153. _ = file:close(FD),
  154. case open_logfile(Name, Buffer) of
  155. {ok, {FD2, Inode3, Size}} ->
  156. %% file was removed
  157. {ok, {FD2, Inode3, Size}};
  158. Error ->
  159. Error
  160. end
  161. end.
  162. %% returns localtime with milliseconds included
  163. localtime_ms() ->
  164. Now = os:timestamp(),
  165. localtime_ms(Now).
  166. localtime_ms(Now) ->
  167. {_, _, Micro} = Now,
  168. {Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now),
  169. {Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}.
  170. maybe_utc({Date, {H, M, S, Ms}}) ->
  171. case lager_stdlib:maybe_utc({Date, {H, M, S}}) of
  172. {utc, {Date1, {H1, M1, S1}}} ->
  173. {utc, {Date1, {H1, M1, S1, Ms}}};
  174. {Date1, {H1, M1, S1}} ->
  175. {Date1, {H1, M1, S1, Ms}}
  176. end.
  177. %% renames failing are OK
  178. rotate_logfile(File, 0) ->
  179. file:delete(File);
  180. rotate_logfile(File, 1) ->
  181. case file:rename(File, File++".0") of
  182. ok ->
  183. ok;
  184. _ ->
  185. rotate_logfile(File, 0)
  186. end;
  187. rotate_logfile(File, Count) ->
  188. _ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)),
  189. rotate_logfile(File, Count - 1).
  190. format_time() ->
  191. format_time(maybe_utc(localtime_ms())).
  192. format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) ->
  193. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  194. [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms), $ , $U, $T, $C]};
  195. format_time({{Y, M, D}, {H, Mi, S, Ms}}) ->
  196. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  197. [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms)]};
  198. format_time({utc, {{Y, M, D}, {H, Mi, S}}}) ->
  199. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  200. [i2l(H), $:, i2l(Mi), $:, i2l(S), $ , $U, $T, $C]};
  201. format_time({{Y, M, D}, {H, Mi, S}}) ->
  202. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  203. [i2l(H), $:, i2l(Mi), $:, i2l(S)]}.
  204. parse_rotation_day_spec([], Res) ->
  205. {ok, Res ++ [{hour, 0}]};
  206. parse_rotation_day_spec([$D, D1, D2], Res) ->
  207. case list_to_integer([D1, D2]) of
  208. X when X >= 0, X =< 23 ->
  209. {ok, Res ++ [{hour, X}]};
  210. _ ->
  211. {error, invalid_date_spec}
  212. end;
  213. parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 ->
  214. {ok, Res ++ [{hour, D - 48}]};
  215. parse_rotation_day_spec(_, _) ->
  216. {error, invalid_date_spec}.
  217. parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 ->
  218. Week = W - 48,
  219. parse_rotation_day_spec(T, [{day, Week}]);
  220. parse_rotation_date_spec([$$, $M, L|T]) when L == $L; L == $l ->
  221. %% last day in month.
  222. parse_rotation_day_spec(T, [{date, last}]);
  223. parse_rotation_date_spec([$$, $M, M1, M2|[$D|_]=T]) ->
  224. case list_to_integer([M1, M2]) of
  225. X when X >= 1, X =< 31 ->
  226. parse_rotation_day_spec(T, [{date, X}]);
  227. _ ->
  228. {error, invalid_date_spec}
  229. end;
  230. parse_rotation_date_spec([$$, $M, M|[$D|_]=T]) ->
  231. parse_rotation_day_spec(T, [{date, M - 48}]);
  232. parse_rotation_date_spec([$$, $M, M1, M2]) ->
  233. case list_to_integer([M1, M2]) of
  234. X when X >= 1, X =< 31 ->
  235. {ok, [{date, X}, {hour, 0}]};
  236. _ ->
  237. {error, invalid_date_spec}
  238. end;
  239. parse_rotation_date_spec([$$, $M, M]) ->
  240. {ok, [{date, M - 48}, {hour, 0}]};
  241. parse_rotation_date_spec([$$|X]) when X /= [] ->
  242. parse_rotation_day_spec(X, []);
  243. parse_rotation_date_spec(_) ->
  244. {error, invalid_date_spec}.
  245. calculate_next_rotation(Spec) ->
  246. Now = calendar:local_time(),
  247. Later = calculate_next_rotation(Spec, Now),
  248. calendar:datetime_to_gregorian_seconds(Later) -
  249. calendar:datetime_to_gregorian_seconds(Now).
  250. calculate_next_rotation([], Now) ->
  251. Now;
  252. calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X ->
  253. %% rotation is today, sometime
  254. NewNow = setelement(2, Now, {X, 0, 0}),
  255. calculate_next_rotation(T, NewNow);
  256. calculate_next_rotation([{hour, X}|T], {{_, _, _}, _} = Now) ->
  257. %% rotation is not today
  258. Seconds = calendar:datetime_to_gregorian_seconds(Now) + 86400,
  259. DateTime = calendar:gregorian_seconds_to_datetime(Seconds),
  260. NewNow = setelement(2, DateTime, {X, 0, 0}),
  261. calculate_next_rotation(T, NewNow);
  262. calculate_next_rotation([{day, Day}|T], {Date, _Time} = Now) ->
  263. DoW = calendar:day_of_the_week(Date),
  264. AdjustedDay = case Day of
  265. 0 -> 7;
  266. X -> X
  267. end,
  268. case AdjustedDay of
  269. DoW -> %% rotation is today
  270. OldDate = element(1, Now),
  271. case calculate_next_rotation(T, Now) of
  272. {OldDate, _} = NewNow -> NewNow;
  273. {NewDate, _} ->
  274. %% rotation *isn't* today! rerun the calculation
  275. NewNow = {NewDate, {0, 0, 0}},
  276. calculate_next_rotation([{day, Day}|T], NewNow)
  277. end;
  278. Y when Y > DoW -> %% rotation is later this week
  279. PlusDays = Y - DoW,
  280. Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
  281. {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
  282. NewNow = {NewDate, {0, 0, 0}},
  283. calculate_next_rotation(T, NewNow);
  284. Y when Y < DoW -> %% rotation is next week
  285. PlusDays = ((7 - DoW) + Y),
  286. Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
  287. {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
  288. NewNow = {NewDate, {0, 0, 0}},
  289. calculate_next_rotation(T, NewNow)
  290. end;
  291. calculate_next_rotation([{date, last}|T], {{Year, Month, Day}, _} = Now) ->
  292. Last = calendar:last_day_of_the_month(Year, Month),
  293. case Last == Day of
  294. true -> %% doing rotation today
  295. OldDate = element(1, Now),
  296. case calculate_next_rotation(T, Now) of
  297. {OldDate, _} = NewNow -> NewNow;
  298. {NewDate, _} ->
  299. %% rotation *isn't* today! rerun the calculation
  300. NewNow = {NewDate, {0, 0, 0}},
  301. calculate_next_rotation([{date, last}|T], NewNow)
  302. end;
  303. false ->
  304. NewNow = setelement(1, Now, {Year, Month, Last}),
  305. calculate_next_rotation(T, NewNow)
  306. end;
  307. calculate_next_rotation([{date, Date}|T], {{_, _, Date}, _} = Now) ->
  308. %% rotation is today
  309. OldDate = element(1, Now),
  310. case calculate_next_rotation(T, Now) of
  311. {OldDate, _} = NewNow -> NewNow;
  312. {NewDate, _} ->
  313. %% rotation *isn't* today! rerun the calculation
  314. NewNow = setelement(1, Now, NewDate),
  315. calculate_next_rotation([{date, Date}|T], NewNow)
  316. end;
  317. calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
  318. PlusDays = case Date of
  319. X when X < Day -> %% rotation is next month
  320. Last = calendar:last_day_of_the_month(Year, Month),
  321. (Last - Day);
  322. X when X > Day -> %% rotation is later this month
  323. X - Day
  324. end,
  325. Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
  326. NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
  327. calculate_next_rotation(T, NewNow).
  328. -spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}.
  329. trace_filter(Query) ->
  330. trace_filter(?DEFAULT_TRACER, Query).
  331. %% TODO: Support multiple trace modules
  332. %-spec trace_filter(Module :: atom(), Query :: 'none' | [tuple()]) -> {ok, any()}.
  333. trace_filter(Module, Query) when Query == none; Query == [] ->
  334. {ok, _} = glc:compile(Module, glc:null(false));
  335. trace_filter(Module, Query) when is_list(Query) ->
  336. {ok, _} = glc:compile(Module, glc_lib:reduce(trace_any(Query))).
  337. validate_trace({Filter, Level, {Destination, ID}}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
  338. case validate_trace({Filter, Level, Destination}) of
  339. {ok, {F, L, D}} ->
  340. {ok, {F, L, {D, ID}}};
  341. Error ->
  342. Error
  343. end;
  344. validate_trace({Filter, Level, Destination}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
  345. ValidFilter = validate_trace_filter(Filter),
  346. try config_to_mask(Level) of
  347. _ when not ValidFilter ->
  348. {error, invalid_trace};
  349. L when is_list(Filter) ->
  350. {ok, {trace_all(Filter), L, Destination}};
  351. L ->
  352. {ok, {Filter, L, Destination}}
  353. catch
  354. _:_ ->
  355. {error, invalid_level}
  356. end;
  357. validate_trace(_) ->
  358. {error, invalid_trace}.
  359. validate_trace_filter(Filter) when is_tuple(Filter), is_atom(element(1, Filter)) =:= false ->
  360. false;
  361. validate_trace_filter(Filter) ->
  362. case lists:all(fun({Key, '*'}) when is_atom(Key) -> true;
  363. ({Key, '!'}) when is_atom(Key) -> true;
  364. ({Key, _Value}) when is_atom(Key) -> true;
  365. ({Key, '=', _Value}) when is_atom(Key) -> true;
  366. ({Key, '<', _Value}) when is_atom(Key) -> true;
  367. ({Key, '>', _Value}) when is_atom(Key) -> true;
  368. (_) -> false end, Filter) of
  369. true ->
  370. true;
  371. _ ->
  372. false
  373. end.
  374. trace_all(Query) ->
  375. glc:all(trace_acc(Query)).
  376. trace_any(Query) ->
  377. glc:any(Query).
  378. trace_acc(Query) ->
  379. trace_acc(Query, []).
  380. trace_acc([], Acc) ->
  381. lists:reverse(Acc);
  382. trace_acc([{Key, '*'}|T], Acc) ->
  383. trace_acc(T, [glc:wc(Key)|Acc]);
  384. trace_acc([{Key, '!'}|T], Acc) ->
  385. trace_acc(T, [glc:nf(Key)|Acc]);
  386. trace_acc([{Key, Val}|T], Acc) ->
  387. trace_acc(T, [glc:eq(Key, Val)|Acc]);
  388. trace_acc([{Key, '=', Val}|T], Acc) ->
  389. trace_acc(T, [glc:eq(Key, Val)|Acc]);
  390. trace_acc([{Key, '>', Val}|T], Acc) ->
  391. trace_acc(T, [glc:gt(Key, Val)|Acc]);
  392. trace_acc([{Key, '<', Val}|T], Acc) ->
  393. trace_acc(T, [glc:lt(Key, Val)|Acc]).
  394. check_traces(_, _, [], Acc) ->
  395. lists:flatten(Acc);
  396. check_traces(Attrs, Level, [{_, {mask, FilterLevel}, _}|Flows], Acc) when (Level band FilterLevel) == 0 ->
  397. check_traces(Attrs, Level, Flows, Acc);
  398. check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) ->
  399. check_traces(Attrs, Level, Flows, Acc);
  400. check_traces(Attrs, Level, [Flow|Flows], Acc) ->
  401. check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]).
  402. check_trace(Attrs, {Filter, _Level, Dest}) when is_list(Filter) ->
  403. check_trace(Attrs, {trace_all(Filter), _Level, Dest});
  404. check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) ->
  405. Made = gre:make(Attrs, [list]),
  406. glc:handle(?DEFAULT_TRACER, Made),
  407. Match = glc_lib:matches(Filter, Made),
  408. case Match of
  409. true ->
  410. Dest;
  411. false ->
  412. []
  413. end.
  414. -spec is_loggable(lager_msg:lager_msg(), non_neg_integer()|{'mask', non_neg_integer()}, term()) -> boolean().
  415. is_loggable(Msg, {mask, Mask}, MyName) ->
  416. %% using syslog style comparison flags
  417. %S = lager_msg:severity_as_int(Msg),
  418. %?debugFmt("comparing masks ~.2B and ~.2B -> ~p~n", [S, Mask, S band Mask]),
  419. (lager_msg:severity_as_int(Msg) band Mask) /= 0 orelse
  420. lists:member(MyName, lager_msg:destinations(Msg));
  421. is_loggable(Msg ,SeverityThreshold,MyName) ->
  422. lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse
  423. lists:member(MyName, lager_msg:destinations(Msg)).
  424. i2l(I) when I < 10 -> [$0, $0+I];
  425. i2l(I) -> integer_to_list(I).
  426. i3l(I) when I < 100 -> [$0 | i2l(I)];
  427. i3l(I) -> integer_to_list(I).
  428. %% When log_root option is provided, get the real path to a file
  429. expand_path(RelPath) ->
  430. case application:get_env(lager, log_root) of
  431. {ok, LogRoot} when is_list(LogRoot) -> % Join relative path
  432. filename:join(LogRoot, RelPath);
  433. undefined -> % No log_root given, keep relative path
  434. RelPath
  435. end.
  436. -ifdef(TEST).
  437. parse_test() ->
  438. ?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")),
  439. ?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")),
  440. ?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")),
  441. ?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")),
  442. ?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")),
  443. ?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")),
  444. ?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")),
  445. ?assertEqual({ok, [{date, 31}, {hour, 0}]}, parse_rotation_date_spec("$M31")),
  446. ?assertEqual({ok, [{date, 31}, {hour, 1}]}, parse_rotation_date_spec("$M31D1")),
  447. ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$ML")),
  448. ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$Ml")),
  449. ?assertEqual({ok, [{day, 5}, {hour, 0}]}, parse_rotation_date_spec("$W5")),
  450. ok.
  451. parse_fail_test() ->
  452. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")),
  453. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")),
  454. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")),
  455. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7D1")),
  456. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32")),
  457. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32D1")),
  458. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$")),
  459. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("")),
  460. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D15M5")),
  461. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M5W5")),
  462. ok.
  463. rotation_calculation_test() ->
  464. ?assertMatch({{2000, 1, 2}, {0, 0, 0}},
  465. calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})),
  466. ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
  467. calculate_next_rotation([{hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  468. ?assertMatch({{2000, 1, 2}, {12, 0, 0}},
  469. calculate_next_rotation([{hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
  470. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  471. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
  472. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  473. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 15}, {12, 34, 43}})),
  474. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  475. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 2}, {12, 34, 43}})),
  476. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  477. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 31}, {12, 34, 43}})),
  478. ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
  479. calculate_next_rotation([{date, 1}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  480. ?assertMatch({{2000, 1, 15}, {16, 0, 0}},
  481. calculate_next_rotation([{date, 15}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  482. ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
  483. calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  484. ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
  485. calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {12, 34, 43}})),
  486. ?assertMatch({{2000, 2, 29}, {16, 0, 0}},
  487. calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {17, 34, 43}})),
  488. ?assertMatch({{2001, 2, 28}, {16, 0, 0}},
  489. calculate_next_rotation([{date, last}, {hour, 16}], {{2001, 1, 31}, {17, 34, 43}})),
  490. ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
  491. calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  492. ?assertMatch({{2000, 1, 8}, {16, 0, 0}},
  493. calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  494. ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
  495. calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  496. ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
  497. calculate_next_rotation([{day, 1}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  498. ?assertMatch({{2000, 1, 2}, {16, 0, 0}},
  499. calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  500. ?assertMatch({{2000, 1, 9}, {16, 0, 0}},
  501. calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 2}, {17, 34, 43}})),
  502. ?assertMatch({{2000, 2, 3}, {16, 0, 0}},
  503. calculate_next_rotation([{day, 4}, {hour, 16}], {{2000, 1, 29}, {17, 34, 43}})),
  504. ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
  505. calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 3}, {17, 34, 43}})),
  506. ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
  507. calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})),
  508. ok.
  509. rotate_file_test() ->
  510. file:delete("rotation.log"),
  511. [file:delete(["rotation.log.", integer_to_list(N)]) || N <- lists:seq(0, 9)],
  512. [begin
  513. file:write_file("rotation.log", integer_to_list(N)),
  514. Count = case N > 10 of
  515. true -> 10;
  516. _ -> N
  517. end,
  518. [begin
  519. FileName = ["rotation.log.", integer_to_list(M)],
  520. ?assert(filelib:is_regular(FileName)),
  521. %% check the expected value is in the file
  522. Number = list_to_binary(integer_to_list(N - M - 1)),
  523. ?assertEqual({ok, Number}, file:read_file(FileName))
  524. end
  525. || M <- lists:seq(0, Count-1)],
  526. rotate_logfile("rotation.log", 10)
  527. end || N <- lists:seq(0, 20)].
  528. rotate_file_fail_test() ->
  529. %% make sure the directory exists
  530. ?assertEqual(ok, filelib:ensure_dir("rotation/rotation.log")),
  531. %% fix the permissions on it
  532. os:cmd("chown -R u+rwx rotation"),
  533. %% delete any old files
  534. [ok = file:delete(F) || F <- filelib:wildcard("rotation/*")],
  535. %% write a file
  536. file:write_file("rotation/rotation.log", "hello"),
  537. %% hose up the permissions
  538. os:cmd("chown u-w rotation"),
  539. ?assertMatch({error, _}, rotate_logfile("rotation.log", 10)),
  540. ?assert(filelib:is_regular("rotation/rotation.log")),
  541. os:cmd("chown u+w rotation"),
  542. ?assertMatch(ok, rotate_logfile("rotation/rotation.log", 10)),
  543. ?assert(filelib:is_regular("rotation/rotation.log.0")),
  544. ?assertEqual(false, filelib:is_regular("rotation/rotation.log")),
  545. ok.
  546. check_trace_test() ->
  547. lager:start(),
  548. trace_filter(none),
  549. %% match by module
  550. ?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  551. {[{module, ?MODULE}], config_to_mask(emergency), foo},
  552. {[{module, test}], config_to_mask(emergency), bar}], [])),
  553. %% match by module, but other unsatisfyable attribute
  554. ?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  555. {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
  556. {[{module, test}], config_to_mask(emergency), bar}], [])),
  557. %% match by wildcard module
  558. ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  559. {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
  560. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  561. %% wildcard module, one trace with unsatisfyable attribute
  562. ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  563. {[{module, '*'}, {foo, bar}], config_to_mask(emergency), foo},
  564. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  565. %% wildcard but not present custom trace attribute
  566. ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  567. {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
  568. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  569. %% wildcarding a custom attribute works when it is present
  570. ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [
  571. {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
  572. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  573. %% denied by level
  574. ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
  575. {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
  576. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  577. %% allowed by level
  578. ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
  579. {[{module, '*'}, {foo, '*'}], config_to_mask(debug), foo},
  580. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  581. ?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [
  582. {[{module, '*'}], config_to_mask('=debug'), debugonly},
  583. {[{module, '*'}], config_to_mask('=info'), infoonly},
  584. {[{module, '*'}], config_to_mask('<=info'), infoandbelow},
  585. {[{module, '*'}], config_to_mask('!=info'), anythingbutinfo},
  586. {[{module, '*'}], config_to_mask('!=notice'), anythingbutnotice}
  587. ], [])),
  588. application:stop(lager),
  589. application:stop(goldrush),
  590. ok.
  591. is_loggable_test_() ->
  592. [
  593. {"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("", alert, [], []),2,me))},
  594. {"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("", critical, [], []),1,me))},
  595. {"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("", alert, [], [you]),2,me))},
  596. {"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("", critical, [], [you]),1,me))},
  597. {"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("", critical, [], [me]),1,me))}
  598. ].
  599. format_time_test_() ->
  600. [
  601. ?_assertEqual("2012-10-04 11:16:23.002",
  602. begin
  603. {D, T} = format_time({{2012,10,04},{11,16,23,2}}),
  604. lists:flatten([D,$ ,T])
  605. end),
  606. ?_assertEqual("2012-10-04 11:16:23.999",
  607. begin
  608. {D, T} = format_time({{2012,10,04},{11,16,23,999}}),
  609. lists:flatten([D,$ ,T])
  610. end),
  611. ?_assertEqual("2012-10-04 11:16:23",
  612. begin
  613. {D, T} = format_time({{2012,10,04},{11,16,23}}),
  614. lists:flatten([D,$ ,T])
  615. end),
  616. ?_assertEqual("2012-10-04 00:16:23.092 UTC",
  617. begin
  618. {D, T} = format_time({utc, {{2012,10,04},{0,16,23,92}}}),
  619. lists:flatten([D,$ ,T])
  620. end),
  621. ?_assertEqual("2012-10-04 11:16:23 UTC",
  622. begin
  623. {D, T} = format_time({utc, {{2012,10,04},{11,16,23}}}),
  624. lists:flatten([D,$ ,T])
  625. end)
  626. ].
  627. config_to_levels_test() ->
  628. ?assertEqual([none], config_to_levels('none')),
  629. ?assertEqual({mask, 0}, config_to_mask('none')),
  630. ?assertEqual([debug], config_to_levels('=debug')),
  631. ?assertEqual([debug], config_to_levels('<info')),
  632. ?assertEqual(levels() -- [debug], config_to_levels('!=debug')),
  633. ?assertEqual(levels() -- [debug], config_to_levels('>debug')),
  634. ?assertEqual(levels() -- [debug], config_to_levels('>=info')),
  635. ?assertEqual(levels() -- [debug], config_to_levels('=>info')),
  636. ?assertEqual([debug, info, notice], config_to_levels('<=notice')),
  637. ?assertEqual([debug, info, notice], config_to_levels('=<notice')),
  638. ?assertEqual([debug], config_to_levels('<info')),
  639. ?assertEqual([debug], config_to_levels('!info')),
  640. ?assertError(badarg, config_to_levels(ok)),
  641. ?assertError(badarg, config_to_levels('<=>info')),
  642. ?assertError(badarg, config_to_levels('=<=info')),
  643. ?assertError(badarg, config_to_levels('<==>=<=>info')),
  644. %% double negatives DO work, however
  645. ?assertEqual([debug], config_to_levels('!!=debug')),
  646. ?assertEqual(levels() -- [debug], config_to_levels('!!!=debug')),
  647. ok.
  648. config_to_mask_test() ->
  649. ?assertEqual({mask, 0}, config_to_mask('none')),
  650. ?assertEqual({mask, ?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('debug')),
  651. ?assertEqual({mask, ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('warning')),
  652. ?assertEqual({mask, ?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('!=info')),
  653. ok.
  654. mask_to_levels_test() ->
  655. ?assertEqual([], mask_to_levels(0)),
  656. ?assertEqual([debug], mask_to_levels(2#10000000)),
  657. ?assertEqual([debug, info], mask_to_levels(2#11000000)),
  658. ?assertEqual([debug, info, emergency], mask_to_levels(2#11000001)),
  659. ?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)),
  660. ok.
  661. expand_path_test() ->
  662. OldRootVal = application:get_env(lager, log_root),
  663. ok = application:unset_env(lager, log_root),
  664. ?assertEqual("/foo/bar", expand_path("/foo/bar")),
  665. ?assertEqual("foo/bar", expand_path("foo/bar")),
  666. ok = application:set_env(lager, log_root, "log/dir"),
  667. ?assertEqual("/foo/bar", expand_path("/foo/bar")), % Absolute path should not be changed
  668. ?assertEqual("log/dir/foo/bar", expand_path("foo/bar")),
  669. case OldRootVal of
  670. undefined -> application:unset_env(lager, log_root);
  671. {ok, Root} -> application:set_env(lager, log_root, Root)
  672. end,
  673. ok.
  674. -endif.