rewrite from lager
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

553 行
19 KiB

4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
4年前
  1. -module(rumFormatter).
  2. -include("eRum.hrl").
  3. -ifdef(TEST).
  4. -include_lib("eunit/include/eunit.hrl").
  5. -endif.
  6. %%
  7. %% Exported Functions
  8. %%
  9. -export([format/2, format/3]).
  10. %%
  11. %% API Functions
  12. %%
  13. %% @doc Provides a generic, default formatting for log messages using a semi-iolist as configuration. Any iolist allowed
  14. %% elements in the configuration are printed verbatim. Atoms in the configuration are treated as metadata properties
  15. %% and extracted from the log message. Optionally, a tuple of {atom(),semi-iolist()} can be used. The atom will look
  16. %% up the property, but if not found it will use the semi-iolist() instead. These fallbacks can be similarly nested
  17. %% or refer to other properties, if desired. You can also use a {atom, semi-iolist(), semi-iolist()} formatter, which
  18. %% acts like a ternary operator's true/false branches.
  19. %%
  20. %% The metadata properties date,time, message, severity, and sev will always exist.
  21. %% The properties pid, file, line, module, and function will always exist if the parser transform is used.
  22. %%
  23. %% Example:
  24. %%
  25. %% `["Foo"]' -> "Foo", regardless of message content.
  26. %%
  27. %% `[message]' -> The content of the logged message, alone.
  28. %%
  29. %% `[{pid,"Unknown Pid"}]' -> "?.?.?" if pid is in the metadata, "Unknown Pid" if not.
  30. %%
  31. %% `[{pid, ["My pid is ", pid], ["Unknown Pid"]}]' -> if pid is in the metada print "My pid is ?.?.?", otherwise print "Unknown Pid"
  32. %% @end
  33. -spec format(rumMsg:rumMsg(), list(), list()) -> any().
  34. format(Msg, [], Colors) ->
  35. format(Msg, [{eol, "\n"}], Colors);
  36. format(Msg, [{eol, EOL}], Colors) ->
  37. Config = case application:get_env(lager, metadata_whitelist) of
  38. undefined -> config(EOL, []);
  39. {ok, Whitelist} -> config(EOL, Whitelist)
  40. end,
  41. format(Msg, Config, Colors);
  42. format(Message, Config, Colors) ->
  43. [case V of
  44. color -> output_color(Message, Colors);
  45. _ -> output(V, Message)
  46. end || V <- Config].
  47. -spec format(rumMsg:rumMsg(), list()) -> any().
  48. format(Msg, Config) ->
  49. format(Msg, Config, []).
  50. -spec output(term(), rumMsg:rumMsg()) -> iolist().
  51. output(message, Msg) -> rumMsg:message(Msg);
  52. output(date, Msg) ->
  53. {D, _T} = rumMsg:datetime(Msg),
  54. D;
  55. output(time, Msg) ->
  56. {_D, T} = rumMsg:datetime(Msg),
  57. T;
  58. output(severity, Msg) ->
  59. atom_to_list(rumMsg:severity(Msg));
  60. output(severity_upper, Msg) ->
  61. uppercase_severity(rumMsg:severity(Msg));
  62. output(blank, _Msg) ->
  63. output({blank, " "}, _Msg);
  64. output(node, _Msg) ->
  65. output({node, atom_to_list(node())}, _Msg);
  66. output({blank, Fill}, _Msg) ->
  67. Fill;
  68. output(sev, Msg) ->
  69. %% Write brief acronym for the severity level (e.g. debug -> $D)
  70. [rumUtil:levelToChr(rumMsg:severity(Msg))];
  71. output(metadata, Msg) ->
  72. output({metadata, "=", " "}, Msg);
  73. output({metadata, IntSep, FieldSep}, Msg) ->
  74. MD = lists:keysort(1, rumMsg:metadata(Msg)),
  75. string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep);
  76. output({pterm, Key}, Msg) ->
  77. output({pterm, Key, ""}, Msg);
  78. output({pterm, Key, Default}, _Msg) ->
  79. make_printable(maybe_get_persistent_term(Key, Default));
  80. output(Prop, Msg) when is_atom(Prop) ->
  81. Metadata = rumMsg:metadata(Msg),
  82. make_printable(get_metadata(Prop, Metadata, <<"Undefined">>));
  83. output({Prop, Default}, Msg) when is_atom(Prop) ->
  84. Metadata = rumMsg:metadata(Msg),
  85. make_printable(get_metadata(Prop, Metadata, output(Default, Msg)));
  86. output({Prop, Present, Absent}, Msg) when is_atom(Prop) ->
  87. %% sort of like a poor man's ternary operator
  88. Metadata = rumMsg:metadata(Msg),
  89. case get_metadata(Prop, Metadata) of
  90. undefined ->
  91. [output(V, Msg) || V <- Absent];
  92. _ ->
  93. [output(V, Msg) || V <- Present]
  94. end;
  95. output({Prop, Present, Absent, Width}, Msg) when is_atom(Prop) ->
  96. %% sort of like a poor man's ternary operator
  97. Metadata = rumMsg:metadata(Msg),
  98. case get_metadata(Prop, Metadata) of
  99. undefined ->
  100. [output(V, Msg, Width) || V <- Absent];
  101. _ ->
  102. [output(V, Msg, Width) || V <- Present]
  103. end;
  104. output(Other, _) -> make_printable(Other).
  105. output(message, Msg, _Width) -> rumMsg:message(Msg);
  106. output(date, Msg, _Width) ->
  107. {D, _T} = rumMsg:datetime(Msg),
  108. D;
  109. output(time, Msg, _Width) ->
  110. {_D, T} = rumMsg:datetime(Msg),
  111. T;
  112. output(severity, Msg, Width) ->
  113. make_printable(atom_to_list(rumMsg:severity(Msg)), Width);
  114. output(sev, Msg, _Width) ->
  115. %% Write brief acronym for the severity level (e.g. debug -> $D)
  116. [rumUtil:levelToChr(rumMsg:severity(Msg))];
  117. output(node, Msg, _Width) ->
  118. output({node, atom_to_list(node())}, Msg, _Width);
  119. output(blank, _Msg, _Width) ->
  120. output({blank, " "}, _Msg, _Width);
  121. output({blank, Fill}, _Msg, _Width) ->
  122. Fill;
  123. output(metadata, Msg, _Width) ->
  124. output({metadata, "=", " "}, Msg, _Width);
  125. output({metadata, IntSep, FieldSep}, Msg, _Width) ->
  126. MD = lists:keysort(1, rumMsg:metadata(Msg)),
  127. [string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep)];
  128. output({pterm, Key}, Msg, Width) ->
  129. output({pterm, Key, ""}, Msg, Width);
  130. output({pterm, Key, Default}, _Msg, _Width) ->
  131. make_printable(maybe_get_persistent_term(Key, Default));
  132. output(Prop, Msg, Width) when is_atom(Prop) ->
  133. Metadata = rumMsg:metadata(Msg),
  134. make_printable(get_metadata(Prop, Metadata, <<"Undefined">>), Width);
  135. output({Prop, Default}, Msg, Width) when is_atom(Prop) ->
  136. Metadata = rumMsg:metadata(Msg),
  137. make_printable(get_metadata(Prop, Metadata, output(Default, Msg)), Width);
  138. output(Other, _, Width) -> make_printable(Other, Width).
  139. output_color(_Msg, []) -> [];
  140. output_color(Msg, Colors) ->
  141. Level = rumMsg:severity(Msg),
  142. case lists:keyfind(Level, 1, Colors) of
  143. {_, Color} -> Color;
  144. _ -> []
  145. end.
  146. -spec make_printable(any()) -> iolist().
  147. make_printable(A) when is_atom(A) -> atom_to_list(A);
  148. make_printable(P) when is_pid(P) -> pid_to_list(P);
  149. make_printable(L) when is_list(L) orelse is_binary(L) -> L;
  150. make_printable(Other) -> io_lib:format("~p", [Other]).
  151. make_printable(A, W) when is_integer(W) -> string:left(make_printable(A), W);
  152. make_printable(A, {Align, W}) when is_integer(W) ->
  153. case Align of
  154. left ->
  155. string:left(make_printable(A), W);
  156. centre ->
  157. string:centre(make_printable(A), W);
  158. right ->
  159. string:right(make_printable(A), W);
  160. _ ->
  161. string:left(make_printable(A), W)
  162. end;
  163. make_printable(A, _W) -> make_printable(A).
  164. %% persistent term was introduced in OTP 21.2, so
  165. %% if we're running on an older OTP, just return the
  166. %% default value.
  167. -ifdef(OTP_RELEASE).
  168. maybe_get_persistent_term(Key, Default) ->
  169. try
  170. persistent_term:get(Key, Default)
  171. catch
  172. _:undef -> Default
  173. end.
  174. -else.
  175. maybe_get_persistent_term(_Key, Default) -> Default.
  176. -endif.
  177. run_function(Function, Default) ->
  178. try Function() of
  179. Result ->
  180. Result
  181. catch
  182. _:_ ->
  183. Default
  184. end.
  185. get_metadata(Key, Metadata) ->
  186. get_metadata(Key, Metadata, undefined).
  187. get_metadata(Key, Metadata, Default) ->
  188. case lists:keyfind(Key, 1, Metadata) of
  189. false ->
  190. Default;
  191. {Key, Value} when is_function(Value) ->
  192. run_function(Value, Default);
  193. {Key, Value} ->
  194. Value
  195. end.
  196. config(EOL, []) ->
  197. [
  198. date, " ", time, " ", color, "[", severity, "] ",
  199. {pid, ""},
  200. {module, [
  201. {pid, ["@"], ""},
  202. module,
  203. {function, [":", function], ""},
  204. {line, [":", line], ""}], ""},
  205. " ", message, EOL
  206. ];
  207. config(EOL, MetaWhitelist) ->
  208. [
  209. date, " ", time, " ", color, "[", severity, "] ",
  210. {pid, ""},
  211. {module, [
  212. {pid, ["@"], ""},
  213. module,
  214. {function, [":", function], ""},
  215. {line, [":", line], ""}], ""},
  216. " "
  217. ] ++
  218. [{M, [atom_to_list(M), "=", M, " "], ""} || M <- MetaWhitelist] ++
  219. [message, EOL].
  220. uppercase_severity(debug) -> "DEBUG";
  221. uppercase_severity(info) -> "INFO";
  222. uppercase_severity(notice) -> "NOTICE";
  223. uppercase_severity(warning) -> "WARNING";
  224. uppercase_severity(error) -> "ERROR";
  225. uppercase_severity(critical) -> "CRITICAL";
  226. uppercase_severity(alert) -> "ALERT";
  227. uppercase_severity(emergency) -> "EMERGENCY".
  228. -ifdef(TEST).
  229. date_time_now() ->
  230. Now = os:timestamp(),
  231. {Date, Time} = rumUtil:format_time(rumUtil:maybe_utc(rumUtil:localtime_ms(Now))),
  232. {Date, Time, Now}.
  233. basic_test_() ->
  234. {Date, Time, Now} = date_time_now(),
  235. [{"Default formatting test",
  236. ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]),
  237. iolist_to_binary(format(rumMsg:new("Message",
  238. Now,
  239. error,
  240. [{pid, self()}],
  241. []),
  242. [])))
  243. },
  244. {"Basic Formatting",
  245. ?_assertEqual(<<"Simplist Format">>,
  246. iolist_to_binary(format(rumMsg:new("Message",
  247. Now,
  248. error,
  249. [{pid, self()}],
  250. []),
  251. ["Simplist Format"])))
  252. },
  253. {"Default equivalent formatting test",
  254. ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]),
  255. iolist_to_binary(format(rumMsg:new("Message",
  256. Now,
  257. error,
  258. [{pid, self()}],
  259. []),
  260. [date, " ", time, " [", severity, "] ", pid, " ", message, "\n"]
  261. )))
  262. },
  263. {"Non existent metadata can default to string",
  264. ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]),
  265. iolist_to_binary(format(rumMsg:new("Message",
  266. Now,
  267. error,
  268. [{pid, self()}],
  269. []),
  270. [date, " ", time, " [", severity, "] ", {does_not_exist, "Fallback"}, " ", message, "\n"]
  271. )))
  272. },
  273. {"Non existent metadata can default to other metadata",
  274. ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]),
  275. iolist_to_binary(format(rumMsg:new("Message",
  276. Now,
  277. error,
  278. [{pid, "Fallback"}],
  279. []),
  280. [date, " ", time, " [", severity, "] ", {does_not_exist, pid}, " ", message, "\n"]
  281. )))
  282. },
  283. {"Non existent metadata can default to a string2",
  284. ?_assertEqual(iolist_to_binary(["Unknown Pid"]),
  285. iolist_to_binary(format(rumMsg:new("Message",
  286. Now,
  287. error,
  288. [],
  289. []),
  290. [{pid, ["My pid is ", pid], ["Unknown Pid"]}]
  291. )))
  292. },
  293. {"Metadata can have extra formatting",
  294. ?_assertEqual(iolist_to_binary(["My pid is hello"]),
  295. iolist_to_binary(format(rumMsg:new("Message",
  296. Now,
  297. error,
  298. [{pid, hello}],
  299. []),
  300. [{pid, ["My pid is ", pid], ["Unknown Pid"]}]
  301. )))
  302. },
  303. {"Metadata can have extra formatting1",
  304. ?_assertEqual(iolist_to_binary(["servername"]),
  305. iolist_to_binary(format(rumMsg:new("Message",
  306. Now,
  307. error,
  308. [{pid, hello}, {server, servername}],
  309. []),
  310. [{server, {pid, ["(", pid, ")"], ["(Unknown Server)"]}}]
  311. )))
  312. },
  313. {"Metadata can have extra formatting2",
  314. ?_assertEqual(iolist_to_binary(["(hello)"]),
  315. iolist_to_binary(format(rumMsg:new("Message",
  316. Now,
  317. error,
  318. [{pid, hello}],
  319. []),
  320. [{server, {pid, ["(", pid, ")"], ["(Unknown Server)"]}}]
  321. )))
  322. },
  323. {"Metadata can have extra formatting3",
  324. ?_assertEqual(iolist_to_binary(["(Unknown Server)"]),
  325. iolist_to_binary(format(rumMsg:new("Message",
  326. Now,
  327. error,
  328. [],
  329. []),
  330. [{server, {pid, ["(", pid, ")"], ["(Unknown Server)"]}}]
  331. )))
  332. },
  333. {"Metadata can be printed in its enterity",
  334. ?_assertEqual(iolist_to_binary(["bar=2 baz=3 foo=1"]),
  335. iolist_to_binary(format(rumMsg:new("Message",
  336. Now,
  337. error,
  338. [{foo, 1}, {bar, 2}, {baz, 3}],
  339. []),
  340. [metadata]
  341. )))
  342. },
  343. {"Metadata can be printed in its enterity with custom seperators",
  344. ?_assertEqual(iolist_to_binary(["bar->2, baz->3, foo->1"]),
  345. iolist_to_binary(format(rumMsg:new("Message",
  346. Now,
  347. error,
  348. [{foo, 1}, {bar, 2}, {baz, 3}],
  349. []),
  350. [{metadata, "->", ", "}]
  351. )))
  352. },
  353. {"Metadata can have extra formatting with width 1",
  354. ?_assertEqual(iolist_to_binary(["(hello )(hello )(hello)(hello)(hello)"]),
  355. iolist_to_binary(format(rumMsg:new("Message",
  356. Now,
  357. error,
  358. [{pid, hello}],
  359. []),
  360. ["(", {pid, [pid], "", 10}, ")",
  361. "(", {pid, [pid], "", {bad_align, 10}}, ")",
  362. "(", {pid, [pid], "", bad10}, ")",
  363. "(", {pid, [pid], "", {right, bad20}}, ")",
  364. "(", {pid, [pid], "", {bad_align, bad20}}, ")"]
  365. )))
  366. },
  367. {"Metadata can have extra formatting with width 2",
  368. ?_assertEqual(iolist_to_binary(["(hello )"]),
  369. iolist_to_binary(format(rumMsg:new("Message",
  370. Now,
  371. error,
  372. [{pid, hello}],
  373. []),
  374. ["(", {pid, [pid], "", {left, 10}}, ")"]
  375. )))
  376. },
  377. {"Metadata can have extra formatting with width 3",
  378. ?_assertEqual(iolist_to_binary(["( hello)"]),
  379. iolist_to_binary(format(rumMsg:new("Message",
  380. Now,
  381. error,
  382. [{pid, hello}],
  383. []),
  384. ["(", {pid, [pid], "", {right, 10}}, ")"]
  385. )))
  386. },
  387. {"Metadata can have extra formatting with width 4",
  388. ?_assertEqual(iolist_to_binary(["( hello )"]),
  389. iolist_to_binary(format(rumMsg:new("Message",
  390. Now,
  391. error,
  392. [{pid, hello}],
  393. []),
  394. ["(", {pid, [pid], "", {centre, 10}}, ")"]
  395. )))
  396. },
  397. {"Metadata can have extra formatting with width 5",
  398. ?_assertEqual(iolist_to_binary(["error |hello ! ( hello )"]),
  399. iolist_to_binary(format(rumMsg:new("Message",
  400. Now,
  401. error,
  402. [{pid, hello}],
  403. []),
  404. [{x, "", [severity, {blank, "|"}, pid], 10}, "!", blank, "(", {pid, [pid], "", {centre, 10}}, ")"]
  405. )))
  406. },
  407. {"Metadata can have extra formatting with width 6",
  408. ?_assertEqual(iolist_to_binary([Time, Date, " bar=2 baz=3 foo=1 pid=hello EMessage"]),
  409. iolist_to_binary(format(rumMsg:new("Message",
  410. Now,
  411. error,
  412. [{pid, hello}, {foo, 1}, {bar, 2}, {baz, 3}],
  413. []),
  414. [{x, "", [time]}, {x, "", [date], 20}, blank, {x, "", [metadata], 30}, blank, {x, "", [sev], 10}, message, {message, message, "", {right, 20}}]
  415. )))
  416. },
  417. {"Uppercase Severity Formatting - DEBUG",
  418. ?_assertEqual(<<"DEBUG Simplist Format">>,
  419. iolist_to_binary(format(rumMsg:new("Message",
  420. Now,
  421. debug,
  422. [{pid, self()}],
  423. []),
  424. [severity_upper, " Simplist Format"])))
  425. },
  426. {"Uppercase Severity Formatting - INFO",
  427. ?_assertEqual(<<"INFO Simplist Format">>,
  428. iolist_to_binary(format(rumMsg:new("Message",
  429. Now,
  430. info,
  431. [{pid, self()}],
  432. []),
  433. [severity_upper, " Simplist Format"])))
  434. },
  435. {"Uppercase Severity Formatting - NOTICE",
  436. ?_assertEqual(<<"NOTICE Simplist Format">>,
  437. iolist_to_binary(format(rumMsg:new("Message",
  438. Now,
  439. notice,
  440. [{pid, self()}],
  441. []),
  442. [severity_upper, " Simplist Format"])))
  443. },
  444. {"Uppercase Severity Formatting - WARNING",
  445. ?_assertEqual(<<"WARNING Simplist Format">>,
  446. iolist_to_binary(format(rumMsg:new("Message",
  447. Now,
  448. warning,
  449. [{pid, self()}],
  450. []),
  451. [severity_upper, " Simplist Format"])))
  452. },
  453. {"Uppercase Severity Formatting - ERROR",
  454. ?_assertEqual(<<"ERROR Simplist Format">>,
  455. iolist_to_binary(format(rumMsg:new("Message",
  456. Now,
  457. error,
  458. [{pid, self()}],
  459. []),
  460. [severity_upper, " Simplist Format"])))
  461. },
  462. {"Uppercase Severity Formatting - CRITICAL",
  463. ?_assertEqual(<<"CRITICAL Simplist Format">>,
  464. iolist_to_binary(format(rumMsg:new("Message",
  465. Now,
  466. critical,
  467. [{pid, self()}],
  468. []),
  469. [severity_upper, " Simplist Format"])))
  470. },
  471. {"Uppercase Severity Formatting - ALERT",
  472. ?_assertEqual(<<"ALERT Simplist Format">>,
  473. iolist_to_binary(format(rumMsg:new("Message",
  474. Now,
  475. alert,
  476. [{pid, self()}],
  477. []),
  478. [severity_upper, " Simplist Format"])))
  479. },
  480. {"Uppercase Severity Formatting - EMERGENCY",
  481. ?_assertEqual(<<"EMERGENCY Simplist Format">>,
  482. iolist_to_binary(format(rumMsg:new("Message",
  483. Now,
  484. emergency,
  485. [{pid, self()}],
  486. []),
  487. [severity_upper, " Simplist Format"])))
  488. },
  489. {"pterm presence test",
  490. %% skip test on OTP < 21
  491. case list_to_integer(erlang:system_info(otp_release)) >= 21 of
  492. true ->
  493. ?_assertEqual(<<"Pterm is: something">>,
  494. begin
  495. persistent_term:put(thing, something),
  496. Ret = iolist_to_binary(format(rumMsg:new("Message",
  497. Now,
  498. emergency,
  499. [{pid, self()}],
  500. []),
  501. ["Pterm is: ", {pterm, thing}])),
  502. persistent_term:erase(thing),
  503. Ret
  504. end);
  505. false -> ?_assert(true)
  506. end
  507. },
  508. {"pterm absence test",
  509. ?_assertEqual(<<"Pterm is: nothing">>,
  510. iolist_to_binary(format(rumMsg:new("Message",
  511. Now,
  512. emergency,
  513. [{pid, self()}],
  514. []),
  515. ["Pterm is: ", {pterm, thing, "nothing"}])))
  516. },
  517. {"node formatting basic",
  518. begin
  519. [N, "foo"] = format(rumMsg:new("Message",
  520. Now,
  521. info,
  522. [{pid, self()}],
  523. []),
  524. [node, "foo"]),
  525. ?_assertNotMatch(nomatch, re:run(N, <<"@">>))
  526. end
  527. }
  528. ].
  529. -endif.