您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

736 行
30 KiB

  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.