rewrite from lager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

994 lines
37 KiB

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