rewrite from lager
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

1065 rindas
46 KiB

pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
pirms 4 gadiem
  1. -module(rumBkdFile).
  2. %% @doc File backend for lager, with multiple file support.
  3. %% Multiple files are supported, each with the path and the loglevel being configurable.
  4. %% The configuration paramter for this backend is a list of key-value 2-tuples. See the init() function for the available options.
  5. %% This backend supports external and internal log rotation and will re-open handles to files if the inode changes.
  6. %% It will also rotate the files itself if the size of the file exceeds the `size' and keep `count' rotated files.
  7. %% `date' is an alternate rotation trigger, based on time. See the README for documentation.
  8. %% For performance, the file backend does delayed writes, although it will sync at specific log levels, configured via the `sync_on' option.
  9. %% By default the error level or above will trigger a sync.
  10. -include("eRum.hrl").
  11. -include_lib("kernel/include/file.hrl").
  12. -behaviour(gen_emm).
  13. -ifdef(TEST).
  14. -include_lib("eunit/include/eunit.hrl").
  15. -compile([{parse_transform, lager_transform}]).
  16. -endif.
  17. -export([config_to_id/1]).
  18. -export([
  19. init/1
  20. , handleCall/2
  21. , handleEvent/2
  22. , handleInfo/2
  23. , terminate/2
  24. , code_change/3
  25. ]).
  26. -record(state, {
  27. fileName :: string(),
  28. level :: {'mask', integer()},
  29. fd :: file:io_device() | undefined,
  30. inode :: integer() | undefined,
  31. ctime :: file:date_time() | undefined,
  32. flap = false :: boolean(),
  33. size = 0 :: integer(),
  34. date :: undefined | string(),
  35. count = 10 :: integer(),
  36. rotator = lager_util :: atom(),
  37. shaper :: rumShaper(),
  38. formatter :: atom(),
  39. formatterConfig :: any(),
  40. sync_on :: {'mask', integer()},
  41. checkInterval = ?RumDefCheckInterval :: non_neg_integer(),
  42. syncInterval = ?RumDefSyncInterval :: non_neg_integer(),
  43. sync_size = ?RumDefSyncSize :: non_neg_integer(),
  44. lastCheck = os:timestamp() :: erlang:timestamp(),
  45. osType :: atom()
  46. }).
  47. -type option() ::
  48. {file, string()} |
  49. {level, rumLevel()} |
  50. {size, non_neg_integer()} |
  51. {date, string()} |
  52. {count, non_neg_integer()} |
  53. {rotator, atom()} |
  54. {high_water_mark, non_neg_integer()} |
  55. {flush_queue, boolean()} |
  56. {flush_threshold, non_neg_integer()} |
  57. {sync_interval, non_neg_integer()} |
  58. {sync_size, non_neg_integer()} |
  59. {sync_on, rumLevel()} |
  60. {check_interval, non_neg_integer()} |
  61. {formatter, atom()} |
  62. {formatter_config, term()}.
  63. -spec init([option(), ...]) -> {ok, #state{}} | {error, {fatal, bad_config}}.
  64. init(Opts) ->
  65. true = checkOpts(Opts, false),
  66. RelName = rumUtil:get_opt(file, Opts, undefined),
  67. CfgLevel = rumUtil:get_opt(level, Opts, ?RumDefLogLevel),
  68. CfgDate = rumUtil:get_opt(date, Opts, ?RumDefRotateDate),
  69. Size = rumUtil:get_opt(size, Opts, ?RumDefRotateSize),
  70. Count = rumUtil:get_opt(count, Opts, ?RumDefRotateCnt),
  71. Rotator = rumUtil:get_opt(rotator, Opts, ?RumDefRotateMod),
  72. HighWaterMark = rumUtil:get_opt(high_water_mark, Opts, ?RumDefCheckHWM),
  73. Flush = rumUtil:get_opt(flush_queue, Opts, ?RumDefFlushQueue),
  74. FlushThr = rumUtil:get_opt(flush_threshold, Opts, ?RumDefFlushThreshold),
  75. SyncInterval = rumUtil:get_opt(sync_interval, Opts, ?RumDefSyncInterval),
  76. CfgCheckInterval = rumUtil:get_opt(check_interval, Opts, ?RumDefCheckInterval),
  77. SyncSize = rumUtil:get_opt(sync_size, Opts, ?RumDefSyncSize),
  78. CfgSyncOn = rumUtil:get_opt(sync_on, Opts, ?RumDefSyncLevel),
  79. Formatter = rumUtil:get_opt(formatter, Opts, ?RumDefFormatter),
  80. FormatterConfig = rumUtil:get_opt(formatter_config, Opts, ?RumDefFormatterCfg),
  81. %% 需要二次转换的配置在这里处理
  82. Level = validate_loglevel(CfgLevel),
  83. SyncOn = validate_loglevel(CfgSyncOn),
  84. CheckInterval = ?IIF(CfgCheckInterval == always, 0, CfgCheckInterval),
  85. {ok, Date} = rumUtil:parseRotateSpec(CfgDate),
  86. FileName = rumUtil:parsePath(RelName),
  87. scheduleRotation(Date, FileName),
  88. Shaper = rumUtil:maybe_flush(Flush, #rumShaper{hwm = HighWaterMark, flushThreshold = FlushThr, id = FileName}),
  89. TemState = #state{
  90. fileName = FileName, level = Level, size = Size, date = Date
  91. , count = Count, rotator = Rotator, shaper = Shaper
  92. , formatter = Formatter, formatterConfig = FormatterConfig
  93. , sync_on = SyncOn, syncInterval = SyncInterval
  94. , sync_size = SyncSize, checkInterval = CheckInterval
  95. },
  96. case Rotator:createLogfile(FileName, {SyncSize, SyncInterval}) of
  97. {ok, Fd, Inode, Ctime, _Size} ->
  98. {ok, TemState#state{fd = Fd, inode = Inode, ctime = Ctime}};
  99. {error, Reason} ->
  100. ?INT_LOG(error, "Failed to open log file ~ts with error ~s", [FileName, file:format_error(Reason)]),
  101. {ok, TemState#state{flap = true}}
  102. end.
  103. handleCall(mGetLogLevel, #state{level = Level} = State) ->
  104. {reply, Level, State};
  105. handleCall({mSetLogLevel, Level}, #state{fileName = Ident} = State) ->
  106. case validate_loglevel(Level) of
  107. false ->
  108. {reply, {error, bad_loglevel}, State};
  109. Levels ->
  110. ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]),
  111. {reply, ok, State#state{level = Levels}}
  112. end;
  113. handleCall({mSetLogHwm, Hwm}, #state{shaper = Shaper, fileName = FileName} = State) ->
  114. case checkOpts([{high_water_mark, Hwm}], true) of
  115. false ->
  116. {reply, {error, bad_log_hwm}, State};
  117. _ ->
  118. NewShaper = Shaper#rumShaper{hwm = Hwm},
  119. ?INT_LOG(notice, "Changed loghwm of ~ts to ~p", [FileName, Hwm]),
  120. {reply, {last_loghwm, Shaper#rumShaper.hwm}, State#state{shaper = NewShaper}}
  121. end;
  122. handleCall(mRotate, State = #state{fileName = File}) ->
  123. {ok, NewState} = handleInfo({mRotate, File}, State),
  124. {reply, ok, NewState};
  125. handleCall(_Msg, State) ->
  126. ?ERR("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  127. {reply, ok, State}.
  128. handleEvent({mWriteLog, Message}, #state{fileName = FileName, level = L, shaper = Shaper, formatter = Formatter, formatterConfig = FormatConfig} = State) ->
  129. case rumUtil:is_loggable(Message, L, {rumBkdFile, FileName}) of
  130. true ->
  131. case rumUtil:check_hwm(Shaper) of
  132. {true, Drop, #rumShaper{hwm = Hwm} = NewShaper} ->
  133. NewState =
  134. case Drop > 0 of
  135. true ->
  136. Report = io_lib:format("lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", [Drop, Hwm]),
  137. ReportMsg = rumMsg:new(Report, warning, [], []),
  138. write(State, rumMsg:timestamp(ReportMsg), rumMsg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig));
  139. _ ->
  140. State
  141. end,
  142. {ok, write(NewState#state{shaper = NewShaper},
  143. rumMsg:timestamp(Message), rumMsg:severity_as_int(Message),
  144. Formatter:format(Message, FormatConfig))};
  145. {false, _, #rumShaper{dropped = D} = NewShaper} ->
  146. {ok, State#state{shaper = NewShaper#rumShaper{dropped = D + 1}}}
  147. end;
  148. _ ->
  149. kpS
  150. end;
  151. handleEvent(_Msg, _State) ->
  152. ?ERR("~p event receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
  153. kpS.
  154. handleInfo({mRotate, File}, #state{fileName = File, count = Count, date = Date, rotator = Rotator} = State0) ->
  155. State1 = close_file(State0),
  156. _ = Rotator:rotateLogfile(File, Count),
  157. scheduleRotation(File, Date),
  158. {ok, State1};
  159. handleInfo({shaper_expired, Name}, #state{shaper = Shaper, fileName = Name, formatter = Formatter, formatterConfig = FormatConfig} = State) ->
  160. _ = case Shaper#rumShaper.dropped of
  161. 0 ->
  162. ok;
  163. Dropped ->
  164. Report = io_lib:format(
  165. "lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec",
  166. [Dropped, Shaper#rumShaper.hwm]),
  167. ReportMsg = rumMsg:new(Report, warning, [], []),
  168. write(State, rumMsg:timestamp(ReportMsg),
  169. rumMsg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig))
  170. end,
  171. {ok, State#state{shaper = Shaper#rumShaper{dropped = 0, mps = 0, lastTime = os:timestamp()}}};
  172. handleInfo(_Info, State) ->
  173. {ok, State}.
  174. terminate(_Reason, State) ->
  175. %% leaving this function call unmatched makes dialyzer cranky
  176. _ = close_file(State),
  177. ok.
  178. code_change(_OldVsn, State, _Extra) ->
  179. {ok, State}.
  180. %% Convert the config into a gen_event handler ID
  181. config_to_id({Name, _Severity}) when is_list(Name) ->
  182. {?MODULE, Name};
  183. config_to_id({Name, _Severity, _Size, _Rotation, _Count}) ->
  184. {?MODULE, Name};
  185. config_to_id([{Name, _Severity, _Size, _Rotation, _Count}, _Format]) ->
  186. {?MODULE, Name};
  187. config_to_id([{Name, _Severity}, _Format]) when is_list(Name) ->
  188. {?MODULE, Name};
  189. config_to_id(Config) ->
  190. case proplists:get_value(file, Config) of
  191. undefined ->
  192. erlang:error(no_file);
  193. File ->
  194. {?MODULE, File}
  195. end.
  196. write(#state{fileName = Name, fd = FD,
  197. inode = Inode, ctime = Ctime,
  198. flap = Flap, size = RotSize,
  199. count = Count, rotator = Rotator} = State0, Timestamp, Level, Msg) ->
  200. case write_should_check(State0, Timestamp) of
  201. true ->
  202. %% need to check for rotation
  203. Buffer = {State0#state.sync_size, State0#state.syncInterval},
  204. case Rotator:ensureLogfile(Name, FD, Inode, Ctime, Buffer) of
  205. {ok, {_FD, _Inode, _Ctime, Size}} when RotSize > 0, Size > RotSize ->
  206. State1 = close_file(State0),
  207. case Rotator:rotateLogfile(Name, Count) of
  208. ok ->
  209. %% go around the loop again, we'll do another rotation check and hit the next clause of ensureLogfile
  210. write(State1, Timestamp, Level, Msg);
  211. {error, Reason} ->
  212. case Flap of
  213. true ->
  214. State1;
  215. _ ->
  216. ?INT_LOG(error, "Failed to rotate log file ~ts with error ~s", [Name, file:format_error(Reason)]),
  217. State1#state{flap = true}
  218. end
  219. end;
  220. {ok, {NewFD, NewInode, NewCtime, _Size}} ->
  221. %% update our last check and try again
  222. State1 = State0#state{lastCheck = Timestamp, fd = NewFD, inode = NewInode, ctime = NewCtime},
  223. do_write(State1, Level, Msg);
  224. {error, Reason} ->
  225. case Flap of
  226. true ->
  227. State0;
  228. _ ->
  229. ?INT_LOG(error, "Failed to reopen log file ~ts with error ~s", [Name, file:format_error(Reason)]),
  230. State0#state{flap = true}
  231. end
  232. end;
  233. false ->
  234. do_write(State0, Level, Msg)
  235. end.
  236. write_should_check(#state{fd = undefined}, _Timestamp) ->
  237. true;
  238. write_should_check(#state{lastCheck = LastCheck0, checkInterval = CheckInterval,
  239. fileName = Name, inode = Inode0, ctime = Ctime0}, Timestamp) ->
  240. LastCheck1 = timer:now_diff(Timestamp, LastCheck0) div 1000,
  241. case LastCheck1 >= CheckInterval of
  242. true ->
  243. true;
  244. _ ->
  245. % We need to know if the file has changed "out from under lager" so we don't
  246. % write to an invalid FD
  247. {Result, _FInfo} = rumUtil:isFileChanged(Name, Inode0, Ctime0),
  248. Result
  249. end.
  250. do_write(#state{fd = FD, fileName = Name, flap = Flap} = State, Level, Msg) ->
  251. %% delayed_write doesn't report errors
  252. _ = file:write(FD, unicode:characters_to_binary(Msg)),
  253. {mask, SyncLevel} = State#state.sync_on,
  254. case (Level band SyncLevel) =/= 0 of
  255. true ->
  256. %% force a sync on any message that matches the 'sync_on' bitmask
  257. Flap2 = case file:datasync(FD) of
  258. {error, Reason2} when Flap == false ->
  259. ?INT_LOG(error, "Failed to write log message to file ~ts: ~s",
  260. [Name, file:format_error(Reason2)]),
  261. true;
  262. ok ->
  263. false;
  264. _ ->
  265. Flap
  266. end,
  267. State#state{flap = Flap2};
  268. _ ->
  269. State
  270. end.
  271. validate_loglevel(Level) ->
  272. try rumUtil:config_to_mask(Level) of
  273. Levels ->
  274. Levels
  275. catch
  276. _:_ ->
  277. false
  278. end.
  279. checkOpts([], IsFile) ->
  280. ?IIF(IsFile, true, {error, no_file_name});
  281. checkOpts([{file, _File} | Tail], _IsFile) ->
  282. checkOpts(Tail, true);
  283. checkOpts([{level, Level} | Tail], IsFile) ->
  284. ?IIF(validate_loglevel(Level), checkOpts(Tail, IsFile), ({error, {invalid_log_level, Level}}));
  285. checkOpts([{size, Size} | Tail], IsFile) when is_integer(Size), Size >= 0 ->
  286. checkOpts(Tail, IsFile);
  287. checkOpts([{count, Count} | Tail], IsFile) when is_integer(Count), Count >= 0 ->
  288. checkOpts(Tail, IsFile);
  289. checkOpts([{rotator, Rotator} | Tail], IsFile) when is_atom(Rotator) ->
  290. checkOpts(Tail, IsFile);
  291. checkOpts([{high_water_mark, HighWaterMark} | Tail], IsFile) when is_integer(HighWaterMark), HighWaterMark >= 0 ->
  292. checkOpts(Tail, IsFile);
  293. checkOpts([{date, _Date} | Tail], IsFile) ->
  294. checkOpts(Tail, IsFile);
  295. checkOpts([{sync_interval, SyncInt} | Tail], IsFile) when is_integer(SyncInt), SyncInt >= 0 ->
  296. checkOpts(Tail, IsFile);
  297. checkOpts([{sync_size, SyncSize} | Tail], IsFile) when is_integer(SyncSize), SyncSize >= 0 ->
  298. checkOpts(Tail, IsFile);
  299. checkOpts([{check_interval, CheckInt} | Tail], IsFile) when is_integer(CheckInt), CheckInt >= 0; CheckInt == always ->
  300. checkOpts(Tail, IsFile);
  301. checkOpts([{sync_on, _Level} | Tail], IsFile) ->
  302. checkOpts(Tail, IsFile);
  303. checkOpts([{formatter, Fmt} | Tail], IsFile) when is_atom(Fmt) ->
  304. checkOpts(Tail, IsFile);
  305. checkOpts([{formatter_config, FmtCfg} | Tail], IsFile) when is_list(FmtCfg) ->
  306. checkOpts(Tail, IsFile);
  307. checkOpts([{flush_queue, FlushCfg} | Tail], IsFile) when is_boolean(FlushCfg) ->
  308. checkOpts(Tail, IsFile);
  309. checkOpts([{flush_threshold, Thr} | Tail], IsFile) when is_integer(Thr), Thr >= 0 ->
  310. checkOpts(Tail, IsFile);
  311. checkOpts([Other | _Tail], _IsFile) ->
  312. {error, {invalid_opt, Other}}.
  313. scheduleRotation(undefined, _FileName) ->
  314. ok;
  315. scheduleRotation(Date, Name) ->
  316. erlang:send_after(rumUtil:calcNextRotateMs(Date), self(), {mRotate, Name}),
  317. ok.
  318. close_file(#state{fd = undefined} = State) ->
  319. State;
  320. close_file(#state{fd = FD} = State) ->
  321. %% Flush and close any file handles.
  322. %% delayed write can cause file:close not to do a close
  323. _ = file:datasync(FD),
  324. _ = file:close(FD),
  325. _ = file:close(FD),
  326. State#state{fd = undefined}.
  327. -ifdef(TEST).
  328. get_loglevel_test() ->
  329. {ok, Level, _} = handleCall(mGetLogLevel,
  330. #state{fileName = "bar", level = rumUtil:config_to_mask(info), fd = 0, inode = 0, ctime = undefined}),
  331. ?assertEqual(Level, rumUtil:config_to_mask(info)),
  332. {ok, Level2, _} = handleCall(mGetLogLevel,
  333. #state{fileName = "foo", level = rumUtil:config_to_mask(warning), fd = 0, inode = 0, ctime = undefined}),
  334. ?assertEqual(Level2, rumUtil:config_to_mask(warning)).
  335. rotation_test_() ->
  336. {foreach,
  337. fun() ->
  338. SyncLevel = validate_loglevel(?RumDefSyncLevel),
  339. SyncSize = ?RumDefSyncSize,
  340. SyncInterval = ?RumDefSyncInterval,
  341. Rotator = ?RumDefRotateMod,
  342. CheckInterval = 0, %% hard to test delayed mode
  343. {ok, TestDir} = rumUtil:create_test_dir(),
  344. TestLog = filename:join(TestDir, "test.log"),
  345. {OsType, _} = os:type(),
  346. #state{fileName = TestLog,
  347. level = ?DEBUG,
  348. sync_on = SyncLevel,
  349. sync_size = SyncSize,
  350. syncInterval = SyncInterval,
  351. checkInterval = CheckInterval,
  352. rotator = Rotator,
  353. osType = OsType}
  354. end,
  355. fun(#state{}) ->
  356. ok = rumUtil:delete_test_dir()
  357. end, [
  358. fun(DefaultState = #state{fileName = TestLog, osType = OsType, sync_size = SyncSize, syncInterval = SyncInterval, rotator = Rotator}) ->
  359. {"External rotation should work",
  360. fun() ->
  361. case OsType of
  362. win32 ->
  363. % Note: test is skipped on win32 due to the fact that a file can't be deleted or renamed
  364. % while a process has an open file handle referencing it
  365. ok;
  366. _ ->
  367. {ok, FD, Inode, Ctime, _Size} = Rotator:openLogfile(TestLog, {SyncSize, SyncInterval}),
  368. State0 = DefaultState#state{fd = FD, inode = Inode, ctime = Ctime},
  369. State1 = write(State0, os:timestamp(), ?DEBUG, "hello world"),
  370. ?assertMatch(#state{fileName = TestLog, level = ?DEBUG, fd = FD, inode = Inode, ctime = Ctime}, State1),
  371. ?assertEqual(ok, file:delete(TestLog)),
  372. State2 = write(State0, os:timestamp(), ?DEBUG, "hello world"),
  373. %% assert file has changed
  374. ExpState1 = #state{fileName = TestLog, level = ?DEBUG, fd = FD, inode = Inode, ctime = Ctime},
  375. ?assertNotEqual(ExpState1, State2),
  376. ?assertMatch(#state{fileName = TestLog, level = ?DEBUG}, State2),
  377. ?assertEqual(ok, file:rename(TestLog, TestLog ++ ".1")),
  378. State3 = write(State2, os:timestamp(), ?DEBUG, "hello world"),
  379. %% assert file has changed
  380. ?assertNotEqual(State3, State2),
  381. ?assertMatch(#state{fileName = TestLog, level = ?DEBUG}, State3),
  382. ok
  383. end
  384. end}
  385. end,
  386. fun(DefaultState = #state{fileName = TestLog, sync_size = SyncSize, syncInterval = SyncInterval, rotator = Rotator}) ->
  387. {"Internal rotation and delayed write",
  388. fun() ->
  389. TestLog0 = TestLog ++ ".0",
  390. CheckInterval = 3000, % 3 sec
  391. RotationSize = 15,
  392. PreviousCheck = os:timestamp(),
  393. {ok, FD, Inode, Ctime, _Size} = Rotator:openLogfile(TestLog, {SyncSize, SyncInterval}),
  394. State0 = DefaultState#state{
  395. fd = FD, inode = Inode, ctime = Ctime, size = RotationSize,
  396. checkInterval = CheckInterval, lastCheck = PreviousCheck},
  397. %% new message within check interval with sync_on level
  398. Msg1Timestamp = add_secs(PreviousCheck, 1),
  399. State1 = write(State0, Msg1Timestamp, ?ERROR, "big big message 1"),
  400. ?assertEqual(State0, State1),
  401. %% new message within check interval under sync_on level
  402. %% not written to disk yet
  403. Msg2Timestamp = add_secs(PreviousCheck, 2),
  404. State2 = write(State1, Msg2Timestamp, ?DEBUG, "buffered message 2"),
  405. ?assertEqual(State0, State2),
  406. % Note: we must ensure at least one second (DEFAULT_SYNC_INTERVAL) has passed
  407. % for message 1 and 2 to be written to disk
  408. ElapsedMs = timer:now_diff(os:timestamp(), PreviousCheck) div 1000,
  409. case ElapsedMs > SyncInterval of
  410. true ->
  411. ok;
  412. _ ->
  413. S = SyncInterval - ElapsedMs,
  414. timer:sleep(S)
  415. end,
  416. %% although file size is big enough...
  417. {ok, FInfo} = file:read_file_info(TestLog, [raw]),
  418. ?assert(RotationSize < FInfo#file_info.size),
  419. %% ...no rotation yet
  420. ?assertEqual(PreviousCheck, State2#state.lastCheck),
  421. ?assertNot(filelib:is_regular(TestLog0)),
  422. %% new message after check interval
  423. Msg3Timestamp = add_secs(PreviousCheck, 4),
  424. _State3 = write(State2, Msg3Timestamp, ?DEBUG, "message 3"),
  425. %% rotation happened
  426. ?assert(filelib:is_regular(TestLog0)),
  427. {ok, Bin1} = file:read_file(TestLog0),
  428. {ok, Bin2} = file:read_file(TestLog),
  429. %% message 1-2 written to file
  430. ?assertEqual(<<"big big message 1buffered message 2">>, Bin1),
  431. %% message 3 buffered, not yet written to file
  432. ?assertEqual(<<"">>, Bin2),
  433. ok
  434. end}
  435. end
  436. ]}.
  437. add_secs({Mega, Secs, Micro}, Add) ->
  438. NewSecs = Secs + Add,
  439. {Mega + NewSecs div 10000000, NewSecs rem 10000000, Micro}.
  440. filesystem_test_() ->
  441. {foreach,
  442. fun() ->
  443. ok = error_logger:tty(false),
  444. ok = rumUtil:safe_application_load(lager),
  445. ok = application:set_env(lager, handlers, [{lager_test_backend, info}]),
  446. ok = application:set_env(lager, errLoggerRedirect, false),
  447. ok = application:set_env(lager, asyncThreshold, undefined),
  448. {ok, _TestDir} = rumUtil:create_test_dir(),
  449. ok = eRum:start(),
  450. %% race condition where lager logs its own start up
  451. %% makes several tests fail. See test/lager_test_backend
  452. %% around line 800 for more information.
  453. ok = timer:sleep(5),
  454. ok = lager_test_backend:flush()
  455. end,
  456. fun(_) ->
  457. ok = application:stop(lager),
  458. ok = application:stop(goldrush),
  459. ok = error_logger:tty(true),
  460. ok = rumUtil:delete_test_dir()
  461. end, [
  462. {"under normal circumstances, file should be opened",
  463. fun() ->
  464. {ok, TestDir} = rumUtil:get_test_dir(),
  465. TestLog = filename:join(TestDir, "test.log"),
  466. gen_event:add_handler(rumEvent, lager_file_backend,
  467. [{TestLog, info}, {lager_default_formatter}]),
  468. eRum:log(error, self(), "Test message"),
  469. {ok, Bin} = file:read_file(TestLog),
  470. Pid = pid_to_list(self()),
  471. ?assertMatch([_, _, "[error]", Pid, "Test message\n"],
  472. re:split(Bin, " ", [{return, list}, {parts, 5}]))
  473. end},
  474. {"don't choke on unicode",
  475. fun() ->
  476. {ok, TestDir} = rumUtil:get_test_dir(),
  477. TestLog = filename:join(TestDir, "test.log"),
  478. gen_event:add_handler(rumEvent, lager_file_backend,
  479. [{TestLog, info}, {lager_default_formatter}]),
  480. eRum:log(error, self(), "~ts", [[20013, 25991, 27979, 35797]]),
  481. {ok, Bin} = file:read_file(TestLog),
  482. Pid = pid_to_list(self()),
  483. ?assertMatch([_, _, "[error]", Pid,
  484. [228, 184, 173, 230, 150, 135, 230, 181, 139, 232, 175, 149, $\n]],
  485. re:split(Bin, " ", [{return, list}, {parts, 5}]))
  486. end},
  487. {"don't choke on latin-1",
  488. fun() ->
  489. {ok, TestDir} = rumUtil:get_test_dir(),
  490. TestLog = filename:join(TestDir, "test.log"),
  491. %% XXX if this test fails, check that this file is encoded latin-1, not utf-8!
  492. gen_event:add_handler(rumEvent, lager_file_backend,
  493. [{TestLog, info}, {lager_default_formatter}]),
  494. eRum:log(error, self(), "~ts", [[76, 198, 221, 206, 78, $-, 239]]),
  495. {ok, Bin} = file:read_file(TestLog),
  496. Pid = pid_to_list(self()),
  497. Res = re:split(Bin, " ", [{return, list}, {parts, 5}]),
  498. ?assertMatch([_, _, "[error]", Pid,
  499. [76, 195, 134, 195, 157, 195, 142, 78, 45, 195, 175, $\n]], Res)
  500. end},
  501. {"file can't be opened on startup triggers an error message",
  502. fun() ->
  503. {ok, TestDir} = rumUtil:get_test_dir(),
  504. TestLog = filename:join(TestDir, "test.log"),
  505. ?assertEqual(ok, rumUtil:safe_write_file(TestLog, [])),
  506. {ok, FInfo0} = file:read_file_info(TestLog, [raw]),
  507. FInfo1 = FInfo0#file_info{mode = 0},
  508. ?assertEqual(ok, file:write_file_info(TestLog, FInfo1)),
  509. gen_event:add_handler(rumEvent, lager_file_backend,
  510. {TestLog, info, 10 * 1024 * 1024, "$D0", 5}),
  511. % Note: required on win32, do this early to prevent subsequent failures
  512. % from preventing cleanup
  513. ?assertEqual(ok, file:write_file_info(TestLog, FInfo0)),
  514. ?assertEqual(1, lager_test_backend:count()),
  515. {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
  516. MessageFlat = lists:flatten(Message),
  517. ?assertEqual(
  518. "Failed to open log file " ++ TestLog ++ " with error permission denied",
  519. MessageFlat)
  520. end},
  521. {"file that becomes unavailable at runtime should trigger an error message",
  522. fun() ->
  523. case os:type() of
  524. {win32, _} ->
  525. % Note: test is skipped on win32 due to the fact that a file can't be
  526. % deleted or renamed while a process has an open file handle referencing it
  527. ok;
  528. _ ->
  529. {ok, TestDir} = rumUtil:get_test_dir(),
  530. TestLog = filename:join(TestDir, "test.log"),
  531. gen_event:add_handler(rumEvent, lager_file_backend,
  532. [{file, TestLog}, {level, info}, {check_interval, 0}]),
  533. ?assertEqual(0, lager_test_backend:count()),
  534. eRum:log(error, self(), "Test message"),
  535. ?assertEqual(1, lager_test_backend:count()),
  536. ?assertEqual(ok, file:delete(TestLog)),
  537. ?assertEqual(ok, rumUtil:safe_write_file(TestLog, "")),
  538. {ok, FInfo0} = file:read_file_info(TestLog, [raw]),
  539. FInfo1 = FInfo0#file_info{mode = 0},
  540. ?assertEqual(ok, file:write_file_info(TestLog, FInfo1)),
  541. eRum:log(error, self(), "Test message"),
  542. lager_test_backend:pop(),
  543. lager_test_backend:pop(),
  544. {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
  545. ?assertEqual(
  546. "Failed to reopen log file " ++ TestLog ++ " with error permission denied",
  547. lists:flatten(Message))
  548. end
  549. end},
  550. {"unavailable files that are fixed at runtime should start having log messages written",
  551. fun() ->
  552. {ok, TestDir} = rumUtil:get_test_dir(),
  553. TestLog = filename:join(TestDir, "test.log"),
  554. ?assertEqual(ok, rumUtil:safe_write_file(TestLog, [])),
  555. {ok, FInfo} = file:read_file_info(TestLog, [raw]),
  556. OldPerms = FInfo#file_info.mode,
  557. ?assertEqual(ok, file:write_file_info(TestLog, FInfo#file_info{mode = 0})),
  558. gen_event:add_handler(rumEvent, lager_file_backend,
  559. [{file, TestLog}, {check_interval, 0}]),
  560. ?assertEqual(1, lager_test_backend:count()),
  561. {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(),
  562. ?assertEqual(
  563. "Failed to open log file " ++ TestLog ++ " with error permission denied",
  564. lists:flatten(Message)),
  565. ?assertEqual(ok, file:write_file_info(TestLog, FInfo#file_info{mode = OldPerms})),
  566. eRum:log(error, self(), "Test message"),
  567. {ok, Bin} = file:read_file(TestLog),
  568. Pid = pid_to_list(self()),
  569. ?assertMatch([_, _, "[error]", Pid, "Test message\n"],
  570. re:split(Bin, " ", [{return, list}, {parts, 5}]))
  571. end},
  572. {"external logfile rotation/deletion should be handled",
  573. fun() ->
  574. case os:type() of
  575. {win32, _} ->
  576. % Note: test is skipped on win32 due to the fact that a file can't be deleted or renamed
  577. % while a process has an open file handle referencing it
  578. ok;
  579. _ ->
  580. {ok, TestDir} = rumUtil:get_test_dir(),
  581. TestLog = filename:join(TestDir, "test.log"),
  582. TestLog0 = TestLog ++ ".0",
  583. gen_event:add_handler(rumEvent, lager_file_backend,
  584. [{file, TestLog}, {level, info}, {check_interval, 0}]),
  585. ?assertEqual(0, lager_test_backend:count()),
  586. eRum:log(error, self(), "Test message1"),
  587. ?assertEqual(1, lager_test_backend:count()),
  588. ?assertEqual(ok, file:delete(TestLog)),
  589. ?assertEqual(ok, rumUtil:safe_write_file(TestLog, "")),
  590. eRum:log(error, self(), "Test message2"),
  591. ?assertEqual(2, lager_test_backend:count()),
  592. {ok, Bin} = file:read_file(TestLog),
  593. Pid = pid_to_list(self()),
  594. ?assertMatch([_, _, "[error]", Pid, "Test message2\n"],
  595. re:split(Bin, " ", [{return, list}, {parts, 5}])),
  596. ?assertEqual(ok, file:rename(TestLog, TestLog0)),
  597. eRum:log(error, self(), "Test message3"),
  598. ?assertEqual(3, lager_test_backend:count()),
  599. {ok, Bin2} = file:read_file(TestLog),
  600. ?assertMatch([_, _, "[error]", Pid, "Test message3\n"],
  601. re:split(Bin2, " ", [{return, list}, {parts, 5}]))
  602. end
  603. end},
  604. {"internal size rotation should work",
  605. fun() ->
  606. {ok, TestDir} = rumUtil:get_test_dir(),
  607. TestLog = filename:join(TestDir, "test.log"),
  608. TestLog0 = TestLog ++ ".0",
  609. gen_event:add_handler(rumEvent, lager_file_backend,
  610. [{file, TestLog}, {level, info}, {check_interval, 0}, {size, 10}]),
  611. eRum:log(error, self(), "Test message1"),
  612. eRum:log(error, self(), "Test message1"),
  613. ?assert(filelib:is_regular(TestLog0))
  614. end},
  615. {"internal time rotation should work",
  616. fun() ->
  617. {ok, TestDir} = rumUtil:get_test_dir(),
  618. TestLog = filename:join(TestDir, "test.log"),
  619. TestLog0 = TestLog ++ ".0",
  620. gen_event:add_handler(rumEvent, lager_file_backend,
  621. [{file, TestLog}, {level, info}, {check_interval, 1000}]),
  622. eRum:log(error, self(), "Test message1"),
  623. eRum:log(error, self(), "Test message1"),
  624. whereis(rumEvent) ! {mRotate, TestLog},
  625. eRum:log(error, self(), "Test message1"),
  626. ?assert(filelib:is_regular(TestLog0))
  627. end},
  628. {"rotation call should work",
  629. fun() ->
  630. {ok, TestDir} = rumUtil:get_test_dir(),
  631. TestLog = filename:join(TestDir, "test.log"),
  632. TestLog0 = TestLog ++ ".0",
  633. gen_event:add_handler(rumEvent, {lager_file_backend, TestLog},
  634. [{file, TestLog}, {level, info}, {check_interval, 1000}]),
  635. eRum:log(error, self(), "Test message1"),
  636. eRum:log(error, self(), "Test message1"),
  637. gen_event:call(rumEvent, {lager_file_backend, TestLog}, mRotate, infinity),
  638. eRum:log(error, self(), "Test message1"),
  639. ?assert(filelib:is_regular(TestLog0))
  640. end},
  641. {"sync_on option should work",
  642. fun() ->
  643. {ok, TestDir} = rumUtil:get_test_dir(),
  644. TestLog = filename:join(TestDir, "test.log"),
  645. gen_event:add_handler(rumEvent, lager_file_backend, [{file, TestLog},
  646. {level, info}, {sync_on, "=info"}, {check_interval, 5000}, {sync_interval, 5000}]),
  647. eRum:log(error, self(), "Test message1"),
  648. eRum:log(error, self(), "Test message1"),
  649. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  650. eRum:log(info, self(), "Test message1"),
  651. {ok, Bin} = file:read_file(TestLog),
  652. ?assert(<<>> /= Bin)
  653. end},
  654. {"sync_on none option should work (also tests sync_interval)",
  655. fun() ->
  656. {ok, TestDir} = rumUtil:get_test_dir(),
  657. TestLog = filename:join(TestDir, "test.log"),
  658. gen_event:add_handler(rumEvent, lager_file_backend, [{file, TestLog},
  659. {level, info}, {sync_on, "none"}, {check_interval, 5000}, {sync_interval, 1000}]),
  660. eRum:log(error, self(), "Test message1"),
  661. eRum:log(error, self(), "Test message1"),
  662. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  663. eRum:log(info, self(), "Test message1"),
  664. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  665. timer:sleep(2000),
  666. {ok, Bin} = file:read_file(TestLog),
  667. ?assert(<<>> /= Bin)
  668. end},
  669. {"sync_size option should work",
  670. fun() ->
  671. {ok, TestDir} = rumUtil:get_test_dir(),
  672. TestLog = filename:join(TestDir, "test.log"),
  673. gen_event:add_handler(rumEvent, lager_file_backend, [{file, TestLog}, {level, info},
  674. {sync_on, "none"}, {check_interval, 5001}, {sync_size, 640}, {sync_interval, 5001}]),
  675. eRum:log(error, self(), "Test messageis64bytes"),
  676. eRum:log(error, self(), "Test messageis64bytes"),
  677. eRum:log(error, self(), "Test messageis64bytes"),
  678. eRum:log(error, self(), "Test messageis64bytes"),
  679. eRum:log(error, self(), "Test messageis64bytes"),
  680. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  681. eRum:log(error, self(), "Test messageis64bytes"),
  682. eRum:log(error, self(), "Test messageis64bytes"),
  683. eRum:log(error, self(), "Test messageis64bytes"),
  684. eRum:log(error, self(), "Test messageis64bytes"),
  685. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  686. %% now we've written enough bytes
  687. eRum:log(error, self(), "Test messageis64bytes"),
  688. {ok, Bin} = file:read_file(TestLog),
  689. ?assert(<<>> /= Bin)
  690. end},
  691. {"runtime level changes",
  692. fun() ->
  693. {ok, TestDir} = rumUtil:get_test_dir(),
  694. TestLog = filename:join(TestDir, "test.log"),
  695. gen_event:add_handler(rumEvent, {lager_file_backend, TestLog}, {TestLog, info}),
  696. ?assertEqual(0, lager_test_backend:count()),
  697. eRum:log(info, self(), "Test message1"),
  698. eRum:log(error, self(), "Test message2"),
  699. {ok, Bin} = file:read_file(TestLog),
  700. Lines = length(re:split(Bin, "\n", [{return, list}, trim])),
  701. ?assertEqual(Lines, 2),
  702. ?assertEqual(ok, eRum:set_loglevel(lager_file_backend, TestLog, warning)),
  703. eRum:log(info, self(), "Test message3"), %% this won't get logged
  704. eRum:log(error, self(), "Test message4"),
  705. {ok, Bin2} = file:read_file(TestLog),
  706. Lines2 = length(re:split(Bin2, "\n", [{return, list}, trim])),
  707. ?assertEqual(Lines2, 3)
  708. end},
  709. {"invalid runtime level changes",
  710. fun() ->
  711. {ok, TestDir} = rumUtil:get_test_dir(),
  712. TestLog = filename:join(TestDir, "test.log"),
  713. TestLog3 = filename:join(TestDir, "test3.log"),
  714. gen_event:add_handler(rumEvent, lager_file_backend,
  715. [{TestLog, info, 10 * 1024 * 1024, "$D0", 5}, {lager_default_formatter}]),
  716. gen_event:add_handler(rumEvent, lager_file_backend, {TestLog3, info}),
  717. ?assertEqual({error, bad_module}, eRum:set_loglevel(lager_file_backend, TestLog, warning))
  718. end},
  719. {"tracing should work",
  720. fun() ->
  721. {ok, TestDir} = rumUtil:get_test_dir(),
  722. TestLog = filename:join(TestDir, "test.log"),
  723. gen_event:add_handler(rumEvent, lager_file_backend, {TestLog, critical}),
  724. eRum:error("Test message"),
  725. ?assertEqual({ok, <<>>}, file:read_file(TestLog)),
  726. {Level, _} = rumConfig:get({rumEvent, loglevel}),
  727. rumConfig:set({rumEvent, loglevel}, {Level,
  728. [{[{module, ?MODULE}], ?DEBUG, {lager_file_backend, TestLog}}]}),
  729. eRum:error("Test message"),
  730. timer:sleep(1000),
  731. {ok, Bin} = file:read_file(TestLog),
  732. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  733. re:split(Bin, " ", [{return, list}, {parts, 5}]))
  734. end},
  735. {"tracing should not duplicate messages",
  736. fun() ->
  737. case os:type() of
  738. {win32, _} ->
  739. % Note: test is skipped on win32 due to the fact that a file can't be
  740. % deleted or renamed while a process has an open file handle referencing it
  741. ok;
  742. _ ->
  743. {ok, TestDir} = rumUtil:get_test_dir(),
  744. TestLog = filename:join(TestDir, "test.log"),
  745. gen_event:add_handler(rumEvent, lager_file_backend,
  746. [{file, TestLog}, {level, critical}, {check_interval, always}]),
  747. timer:sleep(500),
  748. eRum:critical("Test message"),
  749. {ok, Bin1} = file:read_file(TestLog),
  750. ?assertMatch([_, _, "[critical]", _, "Test message\n"],
  751. re:split(Bin1, " ", [{return, list}, {parts, 5}])),
  752. ?assertEqual(ok, file:delete(TestLog)),
  753. {Level, _} = rumConfig:get({rumEvent, loglevel}),
  754. rumConfig:set({rumEvent, loglevel},
  755. {Level, [{[{module, ?MODULE}], ?DEBUG, {lager_file_backend, TestLog}}]}),
  756. eRum:critical("Test message"),
  757. {ok, Bin2} = file:read_file(TestLog),
  758. ?assertMatch([_, _, "[critical]", _, "Test message\n"],
  759. re:split(Bin2, " ", [{return, list}, {parts, 5}])),
  760. ?assertEqual(ok, file:delete(TestLog)),
  761. eRum:error("Test message"),
  762. {ok, Bin3} = file:read_file(TestLog),
  763. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  764. re:split(Bin3, " ", [{return, list}, {parts, 5}]))
  765. end
  766. end},
  767. {"tracing to a dedicated file should work",
  768. fun() ->
  769. {ok, TestDir} = rumUtil:get_test_dir(),
  770. TestLog = filename:join(TestDir, "foo.log"),
  771. {ok, _} = eRum:trace_file(TestLog, [{module, ?MODULE}]),
  772. eRum:error("Test message"),
  773. %% not eligible for trace
  774. eRum:log(error, self(), "Test message"),
  775. {ok, Bin3} = file:read_file(TestLog),
  776. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  777. re:split(Bin3, " ", [{return, list}, {parts, 5}]))
  778. end},
  779. {"tracing to a dedicated file should work even if root_log is set",
  780. fun() ->
  781. {ok, TestDir} = rumUtil:get_test_dir(),
  782. LogName = "foo.log",
  783. LogPath = filename:join(TestDir, LogName),
  784. application:set_env(lager, logRoot, TestDir),
  785. {ok, _} = eRum:trace_file(LogName, [{module, ?MODULE}]),
  786. eRum:error("Test message"),
  787. %% not eligible for trace
  788. eRum:log(error, self(), "Test message"),
  789. {ok, Bin3} = file:read_file(LogPath),
  790. application:unset_env(lager, logRoot),
  791. ?assertMatch([_, _, "[error]", _, "Test message\n"],
  792. re:split(Bin3, " ", [{return, list}, {parts, 5}]))
  793. end},
  794. {"tracing with options should work",
  795. fun() ->
  796. {ok, TestDir} = rumUtil:get_test_dir(),
  797. TestLog = filename:join(TestDir, "foo.log"),
  798. TestLog0 = TestLog ++ ".0",
  799. {ok, _} = eRum:trace_file(TestLog, [{module, ?MODULE}],
  800. [{size, 20}, {check_interval, 1}]),
  801. eRum:error("Test message"),
  802. ?assertNot(filelib:is_regular(TestLog0)),
  803. %% rotation is sensitive to intervals between
  804. %% writes so we sleep to exceed the 1
  805. %% millisecond interval specified by
  806. %% check_interval above
  807. timer:sleep(2),
  808. eRum:error("Test message"),
  809. timer:sleep(10),
  810. ?assert(filelib:is_regular(TestLog0))
  811. end},
  812. {"no silent hwm drops",
  813. fun() ->
  814. MsgCount = 15,
  815. {ok, TestDir} = rumUtil:get_test_dir(),
  816. TestLog = filename:join(TestDir, "test.log"),
  817. gen_event:add_handler(rumEvent, lager_file_backend, [{file, TestLog}, {level, info},
  818. {high_water_mark, 5}, {flush_queue, false}, {sync_on, "=warning"}]),
  819. {_, _, MS} = os:timestamp(),
  820. % start close to the beginning of a new second
  821. ?assertEqual(ok, timer:sleep((1000000 - MS) div 1000 + 1)),
  822. [eRum:log(info, self(), "Foo ~p", [K]) || K <- lists:seq(1, MsgCount)],
  823. ?assertEqual(MsgCount, lager_test_backend:count()),
  824. % Note: bumped from 1000 to 1250 to ensure delayed write flushes to disk
  825. ?assertEqual(ok, timer:sleep(1250)),
  826. {ok, Bin} = file:read_file(TestLog),
  827. Last = lists:last(re:split(Bin, "\n", [{return, list}, trim])),
  828. ?assertMatch([_, _, _, _, "lager_file_backend dropped 10 messages in the last second that exceeded the limit of 5 messages/sec"],
  829. re:split(Last, " ", [{return, list}, {parts, 5}]))
  830. end}
  831. ]}.
  832. trace_files_test_() ->
  833. {foreach,
  834. fun() ->
  835. {ok, TestDir} = rumUtil:get_test_dir(),
  836. Log = filename:join(TestDir, "test.log"),
  837. Debug = filename:join(TestDir, "debug.log"),
  838. Events = filename:join(TestDir, "events.log"),
  839. ok = error_logger:tty(false),
  840. ok = rumUtil:safe_application_load(lager),
  841. ok = application:set_env(lager, handlers, [
  842. {lager_file_backend, [
  843. {file, Log},
  844. {level, error},
  845. {formatter, lager_default_formatter},
  846. {formatter_config, [message, "\n"]}
  847. ]}
  848. ]),
  849. ok = application:set_env(lager, traces, [
  850. { % get default level of debug
  851. {lager_file_backend, Debug}, [{module, ?MODULE}]
  852. },
  853. { % Handler Filters Level
  854. {lager_file_backend, Events}, [{module, ?MODULE}], notice
  855. }
  856. ]),
  857. ok = application:set_env(lager, asyncThreshold, undefined),
  858. ok = eRum:start(),
  859. {Log, Debug, Events}
  860. end,
  861. fun({_, _, _}) ->
  862. catch ets:delete(lager_config),
  863. ok = application:unset_env(lager, traces),
  864. ok = application:stop(lager),
  865. ok = rumUtil:delete_test_dir(),
  866. ok = error_logger:tty(true)
  867. end, [
  868. fun({Log, Debug, Events}) ->
  869. {"a trace using file backend set up in configuration should work",
  870. fun() ->
  871. eRum:error("trace test error message"),
  872. eRum:info("info message"),
  873. %% not eligible for trace
  874. eRum:log(error, self(), "Not trace test message"),
  875. {ok, BinInfo} = file:read_file(Events),
  876. ?assertMatch([_, _, "[error]", _, "trace test error message\n"],
  877. re:split(BinInfo, " ", [{return, list}, {parts, 5}])),
  878. ?assert(filelib:is_regular(Log)),
  879. {ok, BinInfo2} = file:read_file(Log),
  880. ?assertMatch(["trace test error message", "Not trace test message\n"],
  881. re:split(BinInfo2, "\n", [{return, list}, {parts, 2}])),
  882. ?assert(filelib:is_regular(Debug)),
  883. %% XXX Aughhhh, wish I could force this to flush somehow...
  884. % should take about 1 second, try for 3 ...
  885. ?assertEqual(2, count_lines_until(2, add_secs(os:timestamp(), 3), Debug, 0))
  886. end}
  887. end
  888. ]}.
  889. count_lines_until(Lines, Timeout, File, Last) ->
  890. case timer:now_diff(Timeout, os:timestamp()) > 0 of
  891. true ->
  892. timer:sleep(333),
  893. {ok, Bin} = file:read_file(File),
  894. case erlang:length(re:split(Bin, "\n", [{return, list}, trim])) of
  895. Count when Count < Lines ->
  896. count_lines_until(Lines, Timeout, File, Count);
  897. Count ->
  898. Count
  899. end;
  900. _ ->
  901. Last
  902. end.
  903. formatting_test_() ->
  904. {foreach,
  905. fun() ->
  906. {ok, TestDir} = rumUtil:get_test_dir(),
  907. Log1 = filename:join(TestDir, "test.log"),
  908. Log2 = filename:join(TestDir, "test2.log"),
  909. ?assertEqual(ok, rumUtil:safe_write_file(Log1, [])),
  910. ?assertEqual(ok, rumUtil:safe_write_file(Log2, [])),
  911. ok = error_logger:tty(false),
  912. ok = rumUtil:safe_application_load(lager),
  913. ok = application:set_env(lager, handlers, [{lager_test_backend, info}]),
  914. ok = application:set_env(lager, errLoggerRedirect, false),
  915. ok = eRum:start(),
  916. %% same race condition issue
  917. ok = timer:sleep(5),
  918. {ok, Log1, Log2}
  919. end,
  920. fun({ok, _, _}) ->
  921. ok = application:stop(lager),
  922. ok = application:stop(goldrush),
  923. ok = rumUtil:delete_test_dir(),
  924. ok = error_logger:tty(true)
  925. end, [
  926. fun({ok, Log1, Log2}) ->
  927. {"Should have two log files, the second prefixed with 2>",
  928. fun() ->
  929. gen_event:add_handler(rumEvent, lager_file_backend,
  930. [{Log1, debug}, {lager_default_formatter, ["[", severity, "] ", message, "\n"]}]),
  931. gen_event:add_handler(rumEvent, lager_file_backend,
  932. [{Log2, debug}, {lager_default_formatter, ["2> [", severity, "] ", message, "\n"]}]),
  933. eRum:log(error, self(), "Test message"),
  934. ?assertMatch({ok, <<"[error] Test message\n">>}, file:read_file(Log1)),
  935. ?assertMatch({ok, <<"2> [error] Test message\n">>}, file:read_file(Log2))
  936. end}
  937. end
  938. ]}.
  939. config_validation_test_() ->
  940. [
  941. {"missing file",
  942. ?_assertEqual(false,
  943. checkOpts([{level, info}, {size, 10}]))
  944. },
  945. {"bad level",
  946. ?_assertEqual(false,
  947. checkOpts([{file, "test.log"}, {level, blah}, {size, 10}]))
  948. },
  949. {"bad size",
  950. ?_assertEqual(false,
  951. checkOpts([{file, "test.log"}, {size, infinity}]))
  952. },
  953. {"bad count",
  954. ?_assertEqual(false,
  955. checkOpts([{file, "test.log"}, {count, infinity}]))
  956. },
  957. {"bad high water mark",
  958. ?_assertEqual(false,
  959. checkOpts([{file, "test.log"}, {high_water_mark, infinity}]))
  960. },
  961. {"bad date",
  962. ?_assertEqual(false,
  963. checkOpts([{file, "test.log"}, {date, "midnight"}]))
  964. },
  965. {"blank date is ok",
  966. ?_assertMatch([_ | _],
  967. checkOpts([{file, "test.log"}, {date, ""}]))
  968. },
  969. {"bad sync_interval",
  970. ?_assertEqual(false,
  971. checkOpts([{file, "test.log"}, {sync_interval, infinity}]))
  972. },
  973. {"bad sync_size",
  974. ?_assertEqual(false,
  975. checkOpts([{file, "test.log"}, {sync_size, infinity}]))
  976. },
  977. {"bad check_interval",
  978. ?_assertEqual(false,
  979. checkOpts([{file, "test.log"}, {check_interval, infinity}]))
  980. },
  981. {"bad sync_on level",
  982. ?_assertEqual(false,
  983. checkOpts([{file, "test.log"}, {sync_on, infinity}]))
  984. },
  985. {"bad formatter module",
  986. ?_assertEqual(false,
  987. checkOpts([{file, "test.log"}, {formatter, "io:format"}]))
  988. },
  989. {"bad formatter config",
  990. ?_assertEqual(false,
  991. checkOpts([{file, "test.log"}, {formatter_config, blah}]))
  992. },
  993. {"unknown option",
  994. ?_assertEqual(false,
  995. checkOpts([{file, "test.log"}, {rhubarb, spicy}]))
  996. }
  997. ].
  998. -endif.