rewrite from lager
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

966 líneas
38 KiB

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