Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

895 righe
36 KiB

14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
Fix path expansion for traces when log_root is set Previously the following configuration didn't work properly: {log_root, "logs"}, {handlers, [{lager_file_backend, [{file, "access.log"}, {level, none}, ...]}]}, {traces, [{{lager_file_backend, "access.log"}, [{tag, access}], info}]} When lager:trace_file was trying to find a file among the existing handlers, it was searching for the expanded file name ("logs/access.log"), but the handlers store the file names without expansion ("access.log"), so it didn't find it. The result was that lager:trace_file started another "access.log" backend, which of course didn't use e.g. the log rotation settings of the first handler. When the log_root was relative, then each log message appeared twice in the log file (both backends thought that they should log it, because when calculating the Destination, "logs/access.log" was used in both cases). Now the code performs path expansion on the file name inside the handler too. Also, now it calculates the absolute paths of both files, so that if one file is specified with an absolute path and the other with a relative path, lager will still know that they are the same. Test ---- Using the old code (tag 3.2.4 was tested) the new test usually fails the following way: module 'lager_trace_test' lager_trace_test: trace_test_ (Trace combined with log_root)...*failed* in function lager_trace_test:'-trace_test_/0-fun-6-'/0 (test/lager_trace_test.erl, line 71) **throw:{too_many_entries,{ok,<<"Test message\n2017-03-26 23:04:23.935 [info] <0.5898.0>@lager"...>>}} output:<<"">> The reason for failing only usually is that it might happen that the duplicate log entry is added to file only after we called time:sleep(1000) and checked that the log entry is not there.
8 anni fa
  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. -module(lager_util).
  21. -include_lib("kernel/include/file.hrl").
  22. -export([
  23. levels/0, level_to_num/1, level_to_chr/1,
  24. num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1,
  25. open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
  26. localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1,
  27. calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3,
  28. trace_filter/1, trace_filter/2, expand_path/1, find_file/2, check_hwm/1, check_hwm/2,
  29. make_internal_sink_name/1, otp_version/0
  30. ]).
  31. -ifdef(TEST).
  32. -export([create_test_dir/0, delete_test_dir/1]).
  33. -include_lib("eunit/include/eunit.hrl").
  34. -endif.
  35. -include("lager.hrl").
  36. levels() ->
  37. [debug, info, notice, warning, error, critical, alert, emergency, none].
  38. level_to_num(debug) -> ?DEBUG;
  39. level_to_num(info) -> ?INFO;
  40. level_to_num(notice) -> ?NOTICE;
  41. level_to_num(warning) -> ?WARNING;
  42. level_to_num(error) -> ?ERROR;
  43. level_to_num(critical) -> ?CRITICAL;
  44. level_to_num(alert) -> ?ALERT;
  45. level_to_num(emergency) -> ?EMERGENCY;
  46. level_to_num(none) -> ?LOG_NONE.
  47. level_to_chr(debug) -> $D;
  48. level_to_chr(info) -> $I;
  49. level_to_chr(notice) -> $N;
  50. level_to_chr(warning) -> $W;
  51. level_to_chr(error) -> $E;
  52. level_to_chr(critical) -> $C;
  53. level_to_chr(alert) -> $A;
  54. level_to_chr(emergency) -> $M;
  55. level_to_chr(none) -> $ .
  56. num_to_level(?DEBUG) -> debug;
  57. num_to_level(?INFO) -> info;
  58. num_to_level(?NOTICE) -> notice;
  59. num_to_level(?WARNING) -> warning;
  60. num_to_level(?ERROR) -> error;
  61. num_to_level(?CRITICAL) -> critical;
  62. num_to_level(?ALERT) -> alert;
  63. num_to_level(?EMERGENCY) -> emergency;
  64. num_to_level(?LOG_NONE) -> none.
  65. -spec config_to_mask(atom()|string()) -> {'mask', integer()}.
  66. config_to_mask(Conf) ->
  67. Levels = config_to_levels(Conf),
  68. {mask, lists:foldl(fun(Level, Acc) ->
  69. level_to_num(Level) bor Acc
  70. end, 0, Levels)}.
  71. -spec mask_to_levels(non_neg_integer()) -> [lager:log_level()].
  72. mask_to_levels(Mask) ->
  73. mask_to_levels(Mask, levels(), []).
  74. mask_to_levels(_Mask, [], Acc) ->
  75. lists:reverse(Acc);
  76. mask_to_levels(Mask, [Level|Levels], Acc) ->
  77. NewAcc = case (level_to_num(Level) band Mask) /= 0 of
  78. true ->
  79. [Level|Acc];
  80. false ->
  81. Acc
  82. end,
  83. mask_to_levels(Mask, Levels, NewAcc).
  84. -spec config_to_levels(atom()|string()) -> [lager:log_level()].
  85. config_to_levels(Conf) when is_atom(Conf) ->
  86. config_to_levels(atom_to_list(Conf));
  87. config_to_levels([$! | Rest]) ->
  88. levels() -- config_to_levels(Rest);
  89. config_to_levels([$=, $< | Rest]) ->
  90. [_|Levels] = config_to_levels_int(Rest),
  91. lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
  92. config_to_levels([$<, $= | Rest]) ->
  93. [_|Levels] = config_to_levels_int(Rest),
  94. lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
  95. config_to_levels([$>, $= | Rest]) ->
  96. config_to_levels_int(Rest);
  97. config_to_levels([$=, $> | Rest]) ->
  98. config_to_levels_int(Rest);
  99. config_to_levels([$= | Rest]) ->
  100. [level_to_atom(Rest)];
  101. config_to_levels([$< | Rest]) ->
  102. Levels = config_to_levels_int(Rest),
  103. lists:filter(fun(E) -> not lists:member(E, Levels) end, levels());
  104. config_to_levels([$> | Rest]) ->
  105. [_|Levels] = config_to_levels_int(Rest),
  106. lists:filter(fun(E) -> lists:member(E, Levels) end, levels());
  107. config_to_levels(Conf) ->
  108. config_to_levels_int(Conf).
  109. %% internal function to break the recursion loop
  110. config_to_levels_int(Conf) ->
  111. Level = level_to_atom(Conf),
  112. lists:dropwhile(fun(E) -> E /= Level end, levels()).
  113. level_to_atom(String) ->
  114. Levels = levels(),
  115. try list_to_existing_atom(String) of
  116. Atom ->
  117. case lists:member(Atom, Levels) of
  118. true ->
  119. Atom;
  120. false ->
  121. erlang:error(badarg)
  122. end
  123. catch
  124. _:_ ->
  125. erlang:error(badarg)
  126. end.
  127. open_logfile(Name, Buffer) ->
  128. case filelib:ensure_dir(Name) of
  129. ok ->
  130. Options = [append, raw] ++
  131. case Buffer of
  132. {Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
  133. [{delayed_write, Size, Interval}];
  134. _ -> []
  135. end,
  136. case file:open(Name, Options) of
  137. {ok, FD} ->
  138. case file:read_file_info(Name) of
  139. {ok, FInfo} ->
  140. Inode = FInfo#file_info.inode,
  141. {ok, {FD, Inode, FInfo#file_info.size}};
  142. X -> X
  143. end;
  144. Y -> Y
  145. end;
  146. Z -> Z
  147. end.
  148. ensure_logfile(Name, FD, Inode, Buffer) ->
  149. case file:read_file_info(Name) of
  150. {ok, FInfo} ->
  151. Inode2 = FInfo#file_info.inode,
  152. case Inode == Inode2 of
  153. true ->
  154. {ok, {FD, Inode, FInfo#file_info.size}};
  155. false ->
  156. %% delayed write can cause file:close not to do a close
  157. _ = file:close(FD),
  158. _ = file:close(FD),
  159. case open_logfile(Name, Buffer) of
  160. {ok, {FD2, Inode3, Size}} ->
  161. %% inode changed, file was probably moved and
  162. %% recreated
  163. {ok, {FD2, Inode3, Size}};
  164. Error ->
  165. Error
  166. end
  167. end;
  168. _ ->
  169. %% delayed write can cause file:close not to do a close
  170. _ = file:close(FD),
  171. _ = file:close(FD),
  172. case open_logfile(Name, Buffer) of
  173. {ok, {FD2, Inode3, Size}} ->
  174. %% file was removed
  175. {ok, {FD2, Inode3, Size}};
  176. Error ->
  177. Error
  178. end
  179. end.
  180. %% returns localtime with milliseconds included
  181. localtime_ms() ->
  182. Now = os:timestamp(),
  183. localtime_ms(Now).
  184. localtime_ms(Now) ->
  185. {_, _, Micro} = Now,
  186. {Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now),
  187. {Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}.
  188. maybe_utc({Date, {H, M, S, Ms}}) ->
  189. case lager_stdlib:maybe_utc({Date, {H, M, S}}) of
  190. {utc, {Date1, {H1, M1, S1}}} ->
  191. {utc, {Date1, {H1, M1, S1, Ms}}};
  192. {Date1, {H1, M1, S1}} ->
  193. {Date1, {H1, M1, S1, Ms}}
  194. end.
  195. %% renames failing are OK
  196. rotate_logfile(File, 0) ->
  197. file:delete(File);
  198. rotate_logfile(File, 1) ->
  199. case file:rename(File, File++".0") of
  200. ok ->
  201. ok;
  202. _ ->
  203. rotate_logfile(File, 0)
  204. end;
  205. rotate_logfile(File, Count) ->
  206. _ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)),
  207. rotate_logfile(File, Count - 1).
  208. format_time() ->
  209. format_time(maybe_utc(localtime_ms())).
  210. format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) ->
  211. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  212. [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms), $ , $U, $T, $C]};
  213. format_time({{Y, M, D}, {H, Mi, S, Ms}}) ->
  214. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  215. [i2l(H), $:, i2l(Mi), $:, i2l(S), $., i3l(Ms)]};
  216. format_time({utc, {{Y, M, D}, {H, Mi, S}}}) ->
  217. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  218. [i2l(H), $:, i2l(Mi), $:, i2l(S), $ , $U, $T, $C]};
  219. format_time({{Y, M, D}, {H, Mi, S}}) ->
  220. {[integer_to_list(Y), $-, i2l(M), $-, i2l(D)],
  221. [i2l(H), $:, i2l(Mi), $:, i2l(S)]}.
  222. parse_rotation_day_spec([], Res) ->
  223. {ok, Res ++ [{hour, 0}]};
  224. parse_rotation_day_spec([$D, D1, D2], Res) ->
  225. case list_to_integer([D1, D2]) of
  226. X when X >= 0, X =< 23 ->
  227. {ok, Res ++ [{hour, X}]};
  228. _ ->
  229. {error, invalid_date_spec}
  230. end;
  231. parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 ->
  232. {ok, Res ++ [{hour, D - 48}]};
  233. parse_rotation_day_spec(_, _) ->
  234. {error, invalid_date_spec}.
  235. parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 ->
  236. Week = W - 48,
  237. parse_rotation_day_spec(T, [{day, Week}]);
  238. parse_rotation_date_spec([$$, $M, L|T]) when L == $L; L == $l ->
  239. %% last day in month.
  240. parse_rotation_day_spec(T, [{date, last}]);
  241. parse_rotation_date_spec([$$, $M, M1, M2|[$D|_]=T]) ->
  242. case list_to_integer([M1, M2]) of
  243. X when X >= 1, X =< 31 ->
  244. parse_rotation_day_spec(T, [{date, X}]);
  245. _ ->
  246. {error, invalid_date_spec}
  247. end;
  248. parse_rotation_date_spec([$$, $M, M|[$D|_]=T]) ->
  249. parse_rotation_day_spec(T, [{date, M - 48}]);
  250. parse_rotation_date_spec([$$, $M, M1, M2]) ->
  251. case list_to_integer([M1, M2]) of
  252. X when X >= 1, X =< 31 ->
  253. {ok, [{date, X}, {hour, 0}]};
  254. _ ->
  255. {error, invalid_date_spec}
  256. end;
  257. parse_rotation_date_spec([$$, $M, M]) ->
  258. {ok, [{date, M - 48}, {hour, 0}]};
  259. parse_rotation_date_spec([$$|X]) when X /= [] ->
  260. parse_rotation_day_spec(X, []);
  261. parse_rotation_date_spec(_) ->
  262. {error, invalid_date_spec}.
  263. calculate_next_rotation(Spec) ->
  264. Now = calendar:local_time(),
  265. Later = calculate_next_rotation(Spec, Now),
  266. calendar:datetime_to_gregorian_seconds(Later) -
  267. calendar:datetime_to_gregorian_seconds(Now).
  268. calculate_next_rotation([], Now) ->
  269. Now;
  270. calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X ->
  271. %% rotation is today, sometime
  272. NewNow = setelement(2, Now, {X, 0, 0}),
  273. calculate_next_rotation(T, NewNow);
  274. calculate_next_rotation([{hour, X}|T], {{_, _, _}, _} = Now) ->
  275. %% rotation is not today
  276. Seconds = calendar:datetime_to_gregorian_seconds(Now) + 86400,
  277. DateTime = calendar:gregorian_seconds_to_datetime(Seconds),
  278. NewNow = setelement(2, DateTime, {X, 0, 0}),
  279. calculate_next_rotation(T, NewNow);
  280. calculate_next_rotation([{day, Day}|T], {Date, _Time} = Now) ->
  281. DoW = calendar:day_of_the_week(Date),
  282. AdjustedDay = case Day of
  283. 0 -> 7;
  284. X -> X
  285. end,
  286. case AdjustedDay of
  287. DoW -> %% rotation is today
  288. OldDate = element(1, Now),
  289. case calculate_next_rotation(T, Now) of
  290. {OldDate, _} = NewNow -> NewNow;
  291. {NewDate, _} ->
  292. %% rotation *isn't* today! rerun the calculation
  293. NewNow = {NewDate, {0, 0, 0}},
  294. calculate_next_rotation([{day, Day}|T], NewNow)
  295. end;
  296. Y when Y > DoW -> %% rotation is later this week
  297. PlusDays = Y - DoW,
  298. Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
  299. {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
  300. NewNow = {NewDate, {0, 0, 0}},
  301. calculate_next_rotation(T, NewNow);
  302. Y when Y < DoW -> %% rotation is next week
  303. PlusDays = ((7 - DoW) + Y),
  304. Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
  305. {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
  306. NewNow = {NewDate, {0, 0, 0}},
  307. calculate_next_rotation(T, NewNow)
  308. end;
  309. calculate_next_rotation([{date, last}|T], {{Year, Month, Day}, _} = Now) ->
  310. Last = calendar:last_day_of_the_month(Year, Month),
  311. case Last == Day of
  312. true -> %% doing rotation today
  313. OldDate = element(1, Now),
  314. case calculate_next_rotation(T, Now) of
  315. {OldDate, _} = NewNow -> NewNow;
  316. {NewDate, _} ->
  317. %% rotation *isn't* today! rerun the calculation
  318. NewNow = {NewDate, {0, 0, 0}},
  319. calculate_next_rotation([{date, last}|T], NewNow)
  320. end;
  321. false ->
  322. NewNow = setelement(1, Now, {Year, Month, Last}),
  323. calculate_next_rotation(T, NewNow)
  324. end;
  325. calculate_next_rotation([{date, Date}|T], {{_, _, Date}, _} = Now) ->
  326. %% rotation is today
  327. OldDate = element(1, Now),
  328. case calculate_next_rotation(T, Now) of
  329. {OldDate, _} = NewNow -> NewNow;
  330. {NewDate, _} ->
  331. %% rotation *isn't* today! rerun the calculation
  332. NewNow = setelement(1, Now, NewDate),
  333. calculate_next_rotation([{date, Date}|T], NewNow)
  334. end;
  335. calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
  336. PlusDays = case Date of
  337. X when X < Day -> %% rotation is next month
  338. Last = calendar:last_day_of_the_month(Year, Month),
  339. (Last - Day);
  340. X when X > Day -> %% rotation is later this month
  341. X - Day
  342. end,
  343. Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
  344. NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
  345. calculate_next_rotation(T, NewNow).
  346. -spec trace_filter(Query :: 'none' | [tuple()]) -> {ok, any()}.
  347. trace_filter(Query) ->
  348. trace_filter(?DEFAULT_TRACER, Query).
  349. %% TODO: Support multiple trace modules
  350. %-spec trace_filter(Module :: atom(), Query :: 'none' | [tuple()]) -> {ok, any()}.
  351. trace_filter(Module, Query) when Query == none; Query == [] ->
  352. {ok, _} = glc:compile(Module, glc:null(false));
  353. trace_filter(Module, Query) when is_list(Query) ->
  354. {ok, _} = glc:compile(Module, glc_lib:reduce(trace_any(Query))).
  355. validate_trace({Filter, Level, {Destination, ID}}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
  356. case validate_trace({Filter, Level, Destination}) of
  357. {ok, {F, L, D}} ->
  358. {ok, {F, L, {D, ID}}};
  359. Error ->
  360. Error
  361. end;
  362. validate_trace({Filter, Level, Destination}) when is_tuple(Filter); is_list(Filter), is_atom(Level), is_atom(Destination) ->
  363. ValidFilter = validate_trace_filter(Filter),
  364. try config_to_mask(Level) of
  365. _ when not ValidFilter ->
  366. {error, invalid_trace};
  367. L when is_list(Filter) ->
  368. {ok, {trace_all(Filter), L, Destination}};
  369. L ->
  370. {ok, {Filter, L, Destination}}
  371. catch
  372. _:_ ->
  373. {error, invalid_level}
  374. end;
  375. validate_trace(_) ->
  376. {error, invalid_trace}.
  377. validate_trace_filter(Filter) when is_tuple(Filter), is_atom(element(1, Filter)) =:= false ->
  378. false;
  379. validate_trace_filter(Filter) when is_list(Filter) ->
  380. lists:all(fun validate_trace_filter/1, Filter);
  381. validate_trace_filter({Key, '*'}) when is_atom(Key) -> true;
  382. validate_trace_filter({any, L}) when is_list(L) -> lists:all(fun validate_trace_filter/1, L);
  383. validate_trace_filter({all, L}) when is_list(L) -> lists:all(fun validate_trace_filter/1, L);
  384. validate_trace_filter({null, Bool}) when is_boolean(Bool) -> true;
  385. validate_trace_filter({Key, _Value}) when is_atom(Key) -> true;
  386. validate_trace_filter({Key, '=', _Value}) when is_atom(Key) -> true;
  387. validate_trace_filter({Key, '!=', _Value}) when is_atom(Key) -> true;
  388. validate_trace_filter({Key, '<', _Value}) when is_atom(Key) -> true;
  389. validate_trace_filter({Key, '=<', _Value}) when is_atom(Key) -> true;
  390. validate_trace_filter({Key, '>', _Value}) when is_atom(Key) -> true;
  391. validate_trace_filter({Key, '>=', _Value}) when is_atom(Key) -> true;
  392. validate_trace_filter(_) -> false.
  393. trace_all(Query) ->
  394. glc:all(trace_acc(Query)).
  395. trace_any(Query) ->
  396. glc:any(Query).
  397. trace_acc(Query) ->
  398. trace_acc(Query, []).
  399. trace_acc([], Acc) ->
  400. lists:reverse(Acc);
  401. trace_acc([{any, L}|T], Acc) ->
  402. trace_acc(T, [glc:any(L)|Acc]);
  403. trace_acc([{all, L}|T], Acc) ->
  404. trace_acc(T, [glc:all(L)|Acc]);
  405. trace_acc([{null, Bool}|T], Acc) ->
  406. trace_acc(T, [glc:null(Bool)|Acc]);
  407. trace_acc([{Key, '*'}|T], Acc) ->
  408. trace_acc(T, [glc:wc(Key)|Acc]);
  409. trace_acc([{Key, '!'}|T], Acc) ->
  410. trace_acc(T, [glc:nf(Key)|Acc]);
  411. trace_acc([{Key, Val}|T], Acc) ->
  412. trace_acc(T, [glc:eq(Key, Val)|Acc]);
  413. trace_acc([{Key, '=', Val}|T], Acc) ->
  414. trace_acc(T, [glc:eq(Key, Val)|Acc]);
  415. trace_acc([{Key, '!=', Val}|T], Acc) ->
  416. trace_acc(T, [glc:neq(Key, Val)|Acc]);
  417. trace_acc([{Key, '>', Val}|T], Acc) ->
  418. trace_acc(T, [glc:gt(Key, Val)|Acc]);
  419. trace_acc([{Key, '>=', Val}|T], Acc) ->
  420. trace_acc(T, [glc:gte(Key, Val)|Acc]);
  421. trace_acc([{Key, '=<', Val}|T], Acc) ->
  422. trace_acc(T, [glc:lte(Key, Val)|Acc]);
  423. trace_acc([{Key, '<', Val}|T], Acc) ->
  424. trace_acc(T, [glc:lt(Key, Val)|Acc]).
  425. check_traces(_, _, [], Acc) ->
  426. lists:flatten(Acc);
  427. check_traces(Attrs, Level, [{_, {mask, FilterLevel}, _}|Flows], Acc) when (Level band FilterLevel) == 0 ->
  428. check_traces(Attrs, Level, Flows, Acc);
  429. check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) ->
  430. check_traces(Attrs, Level, Flows, Acc);
  431. check_traces(Attrs, Level, [Flow|Flows], Acc) ->
  432. check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]).
  433. check_trace(Attrs, {Filter, _Level, Dest}) when is_list(Filter) ->
  434. check_trace(Attrs, {trace_all(Filter), _Level, Dest});
  435. check_trace(Attrs, {Filter, _Level, Dest}) when is_tuple(Filter) ->
  436. Made = gre:make(Attrs, [list]),
  437. glc:handle(?DEFAULT_TRACER, Made),
  438. Match = glc_lib:matches(Filter, Made),
  439. case Match of
  440. true ->
  441. Dest;
  442. false ->
  443. []
  444. end.
  445. -spec is_loggable(lager_msg:lager_msg(), non_neg_integer()|{'mask', non_neg_integer()}, term()) -> boolean().
  446. is_loggable(Msg, {mask, Mask}, MyName) ->
  447. %% using syslog style comparison flags
  448. %S = lager_msg:severity_as_int(Msg),
  449. %?debugFmt("comparing masks ~.2B and ~.2B -> ~p~n", [S, Mask, S band Mask]),
  450. (lager_msg:severity_as_int(Msg) band Mask) /= 0 orelse
  451. lists:member(MyName, lager_msg:destinations(Msg));
  452. is_loggable(Msg ,SeverityThreshold,MyName) ->
  453. lager_msg:severity_as_int(Msg) =< SeverityThreshold orelse
  454. lists:member(MyName, lager_msg:destinations(Msg)).
  455. i2l(I) when I < 10 -> [$0, $0+I];
  456. i2l(I) -> integer_to_list(I).
  457. i3l(I) when I < 100 -> [$0 | i2l(I)];
  458. i3l(I) -> integer_to_list(I).
  459. %% When log_root option is provided, get the real path to a file
  460. expand_path(RelPath) ->
  461. case application:get_env(lager, log_root) of
  462. {ok, LogRoot} when is_list(LogRoot) -> % Join relative path
  463. %% check if the given RelPath contains LogRoot, if so, do not add
  464. %% it again; see gh #304
  465. case string:str(filename:dirname(RelPath), LogRoot) of
  466. X when X > 0 ->
  467. RelPath;
  468. _Zero ->
  469. filename:join(LogRoot, RelPath)
  470. end;
  471. undefined -> % No log_root given, keep relative path
  472. RelPath
  473. end.
  474. %% Find a file among the already installed handlers.
  475. %%
  476. %% The file is already expanded (i.e. lager_util:expand_path already added the
  477. %% "log_root"), but the file paths inside Handlers are not.
  478. find_file(_File1, _Handlers = []) ->
  479. false;
  480. find_file(File1, [{{lager_file_backend, File2}, _Handler, _Sink} = HandlerInfo | Handlers]) ->
  481. File1Abs = filename:absname(File1),
  482. File2Abs = filename:absname(lager_util:expand_path(File2)),
  483. case File1Abs =:= File2Abs of
  484. true ->
  485. % The file inside HandlerInfo is the same as the file we are looking
  486. % for, so we are done.
  487. HandlerInfo;
  488. false ->
  489. find_file(File1, Handlers)
  490. end;
  491. find_file(File1, [_HandlerInfo | Handlers]) ->
  492. find_file(File1, Handlers).
  493. %% conditionally check the HWM if the event would not have been filtered
  494. check_hwm(Shaper = #lager_shaper{filter = Filter}, Event) ->
  495. case Filter(Event) of
  496. true ->
  497. {true, 0, Shaper};
  498. false ->
  499. check_hwm(Shaper)
  500. end.
  501. %% Log rate limit, i.e. high water mark for incoming messages
  502. check_hwm(Shaper = #lager_shaper{hwm = undefined}) ->
  503. {true, 0, Shaper};
  504. check_hwm(Shaper = #lager_shaper{mps = Mps, hwm = Hwm}) when Mps < Hwm ->
  505. %% haven't hit high water mark yet, just log it
  506. {true, 0, Shaper#lager_shaper{mps=Mps+1}};
  507. check_hwm(Shaper = #lager_shaper{lasttime = Last, dropped = Drop}) ->
  508. %% are we still in the same second?
  509. {M, S, _} = Now = os:timestamp(),
  510. case Last of
  511. {M, S, N} ->
  512. %% still in same second, but have exceeded the high water mark
  513. Timer = case erlang:read_timer(Shaper#lager_shaper.timer) of
  514. false ->
  515. erlang:send_after(trunc((1000000 - N)/1000), self(), {shaper_expired, Shaper#lager_shaper.id});
  516. _ ->
  517. Shaper#lager_shaper.timer
  518. end,
  519. {false, 0, Shaper#lager_shaper{dropped=Drop+1, timer=Timer}};
  520. _ ->
  521. erlang:cancel_timer(Shaper#lager_shaper.timer),
  522. %% different second, reset all counters and allow it
  523. {true, Drop, Shaper#lager_shaper{dropped = 0, mps=1, lasttime = Now}}
  524. end.
  525. %% @private Build an atom for the gen_event process based on a sink name.
  526. %% For historical reasons, the default gen_event process for lager itself is named
  527. %% `lager_event'. For all other sinks, it is SinkName++`_lager_event'
  528. make_internal_sink_name(lager) ->
  529. ?DEFAULT_SINK;
  530. make_internal_sink_name(Sink) ->
  531. list_to_atom(atom_to_list(Sink) ++ "_lager_event").
  532. -spec otp_version() -> pos_integer().
  533. %% @doc Return the major version of the current Erlang/OTP runtime as an integer.
  534. otp_version() ->
  535. {Vsn, _} = string:to_integer(
  536. case erlang:system_info(otp_release) of
  537. [$R | Rel] ->
  538. Rel;
  539. Rel ->
  540. Rel
  541. end),
  542. Vsn.
  543. -ifdef(TEST).
  544. parse_test() ->
  545. ?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")),
  546. ?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")),
  547. ?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")),
  548. ?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")),
  549. ?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")),
  550. ?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")),
  551. ?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")),
  552. ?assertEqual({ok, [{date, 31}, {hour, 0}]}, parse_rotation_date_spec("$M31")),
  553. ?assertEqual({ok, [{date, 31}, {hour, 1}]}, parse_rotation_date_spec("$M31D1")),
  554. ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$ML")),
  555. ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$Ml")),
  556. ?assertEqual({ok, [{day, 5}, {hour, 0}]}, parse_rotation_date_spec("$W5")),
  557. ok.
  558. parse_fail_test() ->
  559. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")),
  560. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")),
  561. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")),
  562. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7D1")),
  563. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32")),
  564. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32D1")),
  565. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$")),
  566. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("")),
  567. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D15M5")),
  568. ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M5W5")),
  569. ok.
  570. rotation_calculation_test() ->
  571. ?assertMatch({{2000, 1, 2}, {0, 0, 0}},
  572. calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})),
  573. ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
  574. calculate_next_rotation([{hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  575. ?assertMatch({{2000, 1, 2}, {12, 0, 0}},
  576. calculate_next_rotation([{hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
  577. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  578. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
  579. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  580. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 15}, {12, 34, 43}})),
  581. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  582. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 2}, {12, 34, 43}})),
  583. ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
  584. calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 31}, {12, 34, 43}})),
  585. ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
  586. calculate_next_rotation([{date, 1}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  587. ?assertMatch({{2000, 1, 15}, {16, 0, 0}},
  588. calculate_next_rotation([{date, 15}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  589. ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
  590. calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  591. ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
  592. calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {12, 34, 43}})),
  593. ?assertMatch({{2000, 2, 29}, {16, 0, 0}},
  594. calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {17, 34, 43}})),
  595. ?assertMatch({{2001, 2, 28}, {16, 0, 0}},
  596. calculate_next_rotation([{date, last}, {hour, 16}], {{2001, 1, 31}, {17, 34, 43}})),
  597. ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
  598. calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
  599. ?assertMatch({{2000, 1, 8}, {16, 0, 0}},
  600. calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  601. ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
  602. calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  603. ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
  604. calculate_next_rotation([{day, 1}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  605. ?assertMatch({{2000, 1, 2}, {16, 0, 0}},
  606. calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
  607. ?assertMatch({{2000, 1, 9}, {16, 0, 0}},
  608. calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 2}, {17, 34, 43}})),
  609. ?assertMatch({{2000, 2, 3}, {16, 0, 0}},
  610. calculate_next_rotation([{day, 4}, {hour, 16}], {{2000, 1, 29}, {17, 34, 43}})),
  611. ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
  612. calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 3}, {17, 34, 43}})),
  613. ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
  614. calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})),
  615. ok.
  616. rotate_file_test() ->
  617. RotCount = 10,
  618. TestDir = create_test_dir(),
  619. TestLog = filename:join(TestDir, "rotation.log"),
  620. Outer = fun(N) ->
  621. ?assertEqual(ok, file:write_file(TestLog, erlang:integer_to_list(N))),
  622. Inner = fun(M) ->
  623. File = lists:flatten([TestLog, $., erlang:integer_to_list(M)]),
  624. ?assert(filelib:is_regular(File)),
  625. %% check the expected value is in the file
  626. Number = erlang:list_to_binary(integer_to_list(N - M - 1)),
  627. ?assertEqual({ok, Number}, file:read_file(File))
  628. end,
  629. Count = erlang:min(N, RotCount),
  630. % The first time through, Count == 0, so the sequence is empty,
  631. % effectively skipping the inner loop so a rotation can occur that
  632. % creates the file that Inner looks for.
  633. % Don't shoot the messenger, it was worse before this refactoring.
  634. lists:foreach(Inner, lists:seq(0, Count-1)),
  635. rotate_logfile(TestLog, RotCount)
  636. end,
  637. lists:foreach(Outer, lists:seq(0, (RotCount * 2))),
  638. delete_test_dir(TestDir).
  639. rotate_file_fail_test() ->
  640. TestDir = create_test_dir(),
  641. TestLog = filename:join(TestDir, "rotation.log"),
  642. %% set known permissions on it
  643. os:cmd("chmod -R u+rwx " ++ TestDir),
  644. %% write a file
  645. file:write_file(TestLog, "hello"),
  646. %% hose up the permissions
  647. os:cmd("chmod u-w " ++ TestDir),
  648. ?assertMatch({error, _}, rotate_logfile(TestLog, 10)),
  649. ?assert(filelib:is_regular(TestLog)),
  650. %% fix the permissions
  651. os:cmd("chmod u+w " ++ TestDir),
  652. ?assertMatch(ok, rotate_logfile(TestLog, 10)),
  653. ?assert(filelib:is_regular(TestLog ++ ".0")),
  654. ?assertEqual(false, filelib:is_regular(TestLog)),
  655. delete_test_dir(TestDir).
  656. check_trace_test() ->
  657. lager:start(),
  658. trace_filter(none),
  659. %% match by module
  660. ?assertEqual([foo], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  661. {[{module, ?MODULE}], config_to_mask(emergency), foo},
  662. {[{module, test}], config_to_mask(emergency), bar}], [])),
  663. %% match by module, but other unsatisfyable attribute
  664. ?assertEqual([], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  665. {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
  666. {[{module, test}], config_to_mask(emergency), bar}], [])),
  667. %% match by wildcard module
  668. ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  669. {[{module, ?MODULE}, {foo, bar}], config_to_mask(emergency), foo},
  670. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  671. %% wildcard module, one trace with unsatisfyable attribute
  672. ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  673. {[{module, '*'}, {foo, bar}], config_to_mask(emergency), foo},
  674. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  675. %% wildcard but not present custom trace attribute
  676. ?assertEqual([bar], check_traces([{module, ?MODULE}], ?EMERGENCY, [
  677. {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
  678. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  679. %% wildcarding a custom attribute works when it is present
  680. ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], ?EMERGENCY, [
  681. {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
  682. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  683. %% denied by level
  684. ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
  685. {[{module, '*'}, {foo, '*'}], config_to_mask(emergency), foo},
  686. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  687. %% allowed by level
  688. ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], ?INFO, [
  689. {[{module, '*'}, {foo, '*'}], config_to_mask(debug), foo},
  690. {[{module, '*'}], config_to_mask(emergency), bar}], [])),
  691. ?assertEqual([anythingbutnotice, infoandbelow, infoonly], check_traces([{module, ?MODULE}], ?INFO, [
  692. {[{module, '*'}], config_to_mask('=debug'), debugonly},
  693. {[{module, '*'}], config_to_mask('=info'), infoonly},
  694. {[{module, '*'}], config_to_mask('<=info'), infoandbelow},
  695. {[{module, '*'}], config_to_mask('!=info'), anythingbutinfo},
  696. {[{module, '*'}], config_to_mask('!=notice'), anythingbutnotice}
  697. ], [])),
  698. application:stop(lager),
  699. application:stop(goldrush),
  700. ok.
  701. is_loggable_test_() ->
  702. [
  703. {"Loggable by severity only", ?_assert(is_loggable(lager_msg:new("", alert, [], []),2,me))},
  704. {"Not loggable by severity only", ?_assertNot(is_loggable(lager_msg:new("", critical, [], []),1,me))},
  705. {"Loggable by severity with destination", ?_assert(is_loggable(lager_msg:new("", alert, [], [you]),2,me))},
  706. {"Not loggable by severity with destination", ?_assertNot(is_loggable(lager_msg:new("", critical, [], [you]),1,me))},
  707. {"Loggable by destination overriding severity", ?_assert(is_loggable(lager_msg:new("", critical, [], [me]),1,me))}
  708. ].
  709. format_time_test_() ->
  710. [
  711. ?_assertEqual("2012-10-04 11:16:23.002",
  712. begin
  713. {D, T} = format_time({{2012,10,04},{11,16,23,2}}),
  714. lists:flatten([D,$ ,T])
  715. end),
  716. ?_assertEqual("2012-10-04 11:16:23.999",
  717. begin
  718. {D, T} = format_time({{2012,10,04},{11,16,23,999}}),
  719. lists:flatten([D,$ ,T])
  720. end),
  721. ?_assertEqual("2012-10-04 11:16:23",
  722. begin
  723. {D, T} = format_time({{2012,10,04},{11,16,23}}),
  724. lists:flatten([D,$ ,T])
  725. end),
  726. ?_assertEqual("2012-10-04 00:16:23.092 UTC",
  727. begin
  728. {D, T} = format_time({utc, {{2012,10,04},{0,16,23,92}}}),
  729. lists:flatten([D,$ ,T])
  730. end),
  731. ?_assertEqual("2012-10-04 11:16:23 UTC",
  732. begin
  733. {D, T} = format_time({utc, {{2012,10,04},{11,16,23}}}),
  734. lists:flatten([D,$ ,T])
  735. end)
  736. ].
  737. config_to_levels_test() ->
  738. ?assertEqual([none], config_to_levels('none')),
  739. ?assertEqual({mask, 0}, config_to_mask('none')),
  740. ?assertEqual([debug], config_to_levels('=debug')),
  741. ?assertEqual([debug], config_to_levels('<info')),
  742. ?assertEqual(levels() -- [debug], config_to_levels('!=debug')),
  743. ?assertEqual(levels() -- [debug], config_to_levels('>debug')),
  744. ?assertEqual(levels() -- [debug], config_to_levels('>=info')),
  745. ?assertEqual(levels() -- [debug], config_to_levels('=>info')),
  746. ?assertEqual([debug, info, notice], config_to_levels('<=notice')),
  747. ?assertEqual([debug, info, notice], config_to_levels('=<notice')),
  748. ?assertEqual([debug], config_to_levels('<info')),
  749. ?assertEqual([debug], config_to_levels('!info')),
  750. ?assertError(badarg, config_to_levels(ok)),
  751. ?assertError(badarg, config_to_levels('<=>info')),
  752. ?assertError(badarg, config_to_levels('=<=info')),
  753. ?assertError(badarg, config_to_levels('<==>=<=>info')),
  754. %% double negatives DO work, however
  755. ?assertEqual([debug], config_to_levels('!!=debug')),
  756. ?assertEqual(levels() -- [debug], config_to_levels('!!!=debug')),
  757. ok.
  758. config_to_mask_test() ->
  759. ?assertEqual({mask, 0}, config_to_mask('none')),
  760. ?assertEqual({mask, ?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('debug')),
  761. ?assertEqual({mask, ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('warning')),
  762. ?assertEqual({mask, ?DEBUG bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY}, config_to_mask('!=info')),
  763. ok.
  764. mask_to_levels_test() ->
  765. ?assertEqual([], mask_to_levels(0)),
  766. ?assertEqual([debug], mask_to_levels(2#10000000)),
  767. ?assertEqual([debug, info], mask_to_levels(2#11000000)),
  768. ?assertEqual([debug, info, emergency], mask_to_levels(2#11000001)),
  769. ?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)),
  770. ok.
  771. expand_path_test() ->
  772. OldRootVal = application:get_env(lager, log_root),
  773. ok = application:unset_env(lager, log_root),
  774. ?assertEqual("/foo/bar", expand_path("/foo/bar")),
  775. ?assertEqual("foo/bar", expand_path("foo/bar")),
  776. ok = application:set_env(lager, log_root, "log/dir"),
  777. ?assertEqual("/foo/bar", expand_path("/foo/bar")), % Absolute path should not be changed
  778. ?assertEqual("log/dir/foo/bar", expand_path("foo/bar")),
  779. ?assertEqual("log/dir/foo/bar", expand_path("log/dir/foo/bar")), %% gh #304
  780. case OldRootVal of
  781. undefined -> application:unset_env(lager, log_root);
  782. {ok, Root} -> application:set_env(lager, log_root, Root)
  783. end,
  784. ok.
  785. sink_name_test_() ->
  786. [
  787. ?_assertEqual(lager_event, make_internal_sink_name(lager)),
  788. ?_assertEqual(audit_lager_event, make_internal_sink_name(audit))
  789. ].
  790. create_test_dir() ->
  791. Dir = filename:join(["/tmp", "lager_test",
  792. erlang:integer_to_list(erlang:phash2(os:timestamp()))]),
  793. ?assertEqual(ok, filelib:ensure_dir(Dir)),
  794. case file:make_dir(Dir) of
  795. ok ->
  796. Dir;
  797. Err ->
  798. ?assertEqual({error, eexist}, Err),
  799. create_test_dir()
  800. end.
  801. delete_test_dir(Dir) ->
  802. case otp_version() of
  803. 15 ->
  804. os:cmd("rm -rf " ++ Dir);
  805. _ ->
  806. do_delete_test_dir(Dir)
  807. end.
  808. do_delete_test_dir(Dir) ->
  809. ListRet = file:list_dir_all(Dir),
  810. ?assertMatch({ok, _}, ListRet),
  811. {_, Entries} = ListRet,
  812. lists:foreach(
  813. fun(Entry) ->
  814. FsElem = filename:join(Dir, Entry),
  815. case filelib:is_dir(FsElem) of
  816. true ->
  817. delete_test_dir(FsElem);
  818. _ ->
  819. ?assertEqual(ok, file:delete(FsElem))
  820. end
  821. end, Entries),
  822. ?assertEqual(ok, file:del_dir(Dir)).
  823. -endif.