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.

271 lines
9.9 KiB

14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
  1. %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
  2. %%
  3. %% This file is provided to you under the Apache License,
  4. %% Version 2.0 (the "License"); you may not use this file
  5. %% except in compliance with the License. You may obtain
  6. %% a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing,
  11. %% software distributed under the License is distributed on an
  12. %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. %% KIND, either express or implied. See the License for the
  14. %% specific language governing permissions and limitations
  15. %% under the License.
  16. %% @doc The lager logging framework.
  17. -module(lager).
  18. -include("lager.hrl").
  19. -include_lib("eunit/include/eunit.hrl").
  20. %% API
  21. -export([start/0,
  22. log/3, log/4,
  23. trace_file/2, trace_file/3, trace_console/1, trace_console/2,
  24. clear_all_traces/0, stop_trace/1, status/0,
  25. get_loglevel/1, set_loglevel/2, set_loglevel/3, get_loglevels/0,
  26. minimum_loglevel/1, posix_error/1,
  27. safe_format/3, safe_format_chop/3,dispatch_log/5]).
  28. -type log_level() :: debug | info | notice | warning | error | critical | alert | emergency.
  29. -type log_level_number() :: 0..7.
  30. -export_type([log_level/0, log_level_number/0]).
  31. %% API
  32. %% @doc Start the application. Mainly useful for using `-s lager' as a command
  33. %% line switch to the VM to make lager start on boot.
  34. start() -> start(lager).
  35. start(App) ->
  36. start_ok(App, application:start(App, permanent)).
  37. start_ok(_App, ok) -> ok;
  38. start_ok(_App, {error, {already_started, _App}}) -> ok;
  39. start_ok(App, {error, {not_started, Dep}}) ->
  40. ok = start(Dep),
  41. start(App);
  42. start_ok(App, {error, Reason}) ->
  43. erlang:error({app_start_failed, App, Reason}).
  44. -spec dispatch_log(log_level(), list(), string(), list() | none, pos_integer()) -> ok | {error, lager_not_running}.
  45. dispatch_log(Severity, Metadata, Format, Args, Size) when is_atom(Severity)->
  46. case whereis(lager_event) of
  47. undefined ->
  48. %% lager isn't running
  49. {error, lager_not_running};
  50. Pid ->
  51. {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}),
  52. SeverityAsInt=lager_util:level_to_num(Severity),
  53. Destinations = case TraceFilters of
  54. [] -> [];
  55. _ ->
  56. lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[])
  57. end,
  58. case (LevelThreshold >= SeverityAsInt orelse Destinations =/= []) of
  59. true ->
  60. Timestamp = lager_util:format_time(),
  61. Msg=case Args of
  62. A when is_list(A) ->safe_format_chop(Format,Args,Size);
  63. _ -> Format
  64. end,
  65. gen_event:sync_notify(Pid, #lager_log_message{destinations=Destinations,
  66. metadata=Metadata,
  67. severity_as_int=SeverityAsInt,
  68. timestamp=Timestamp,
  69. message=Msg});
  70. _ ->
  71. ok
  72. end
  73. end.
  74. %% @doc Manually log a message into lager without using the parse transform.
  75. -spec log(log_level(), pid() | [tuple(),...], list()) -> ok | {error, lager_not_running}.
  76. log(Level, Pid, Message) when is_pid(Pid) ->
  77. dispatch_log(Level, [{pid,Pid}], Message, [], 4096);
  78. log(Level, Metadata, Message) when is_list(Metadata) ->
  79. dispatch_log(Level, Metadata, Message, [], 4096).
  80. %% @doc Manually log a message into lager without using the parse transform.
  81. -spec log(log_level(), pid() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}.
  82. log(Level, Pid, Format, Args) when is_pid(Pid) ->
  83. dispatch_log(Level, [{pid,Pid}], Format, Args, 4096);
  84. log(Level, Metadata, Format, Args) when is_list(Metadata) ->
  85. dispatch_log(Level, Metadata, Format, Args, 4096).
  86. trace_file(File, Filter) ->
  87. trace_file(File, Filter, debug).
  88. trace_file(File, Filter, Level) ->
  89. Trace0 = {Filter, Level, {lager_file_backend, File}},
  90. case lager_util:validate_trace(Trace0) of
  91. {ok, Trace} ->
  92. Handlers = gen_event:which_handlers(lager_event),
  93. %% check if this file backend is already installed
  94. Res = case lists:member({lager_file_backend, File}, Handlers) of
  95. false ->
  96. %% install the handler
  97. supervisor:start_child(lager_handler_watcher_sup,
  98. [lager_event, {lager_file_backend, File}, {File, none}]);
  99. _ ->
  100. {ok, exists}
  101. end,
  102. case Res of
  103. {ok, _} ->
  104. %% install the trace.
  105. {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
  106. case lists:member(Trace, Traces) of
  107. false ->
  108. lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
  109. _ ->
  110. ok
  111. end,
  112. {ok, Trace};
  113. {error, _} = E ->
  114. E
  115. end;
  116. Error ->
  117. Error
  118. end.
  119. trace_console(Filter) ->
  120. trace_console(Filter, debug).
  121. trace_console(Filter, Level) ->
  122. Trace0 = {Filter, Level, lager_console_backend},
  123. case lager_util:validate_trace(Trace0) of
  124. {ok, Trace} ->
  125. {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
  126. case lists:member(Trace, Traces) of
  127. false ->
  128. lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
  129. _ -> ok
  130. end,
  131. {ok, Trace};
  132. Error ->
  133. Error
  134. end.
  135. stop_trace({_Filter, _Level, Target} = Trace) ->
  136. {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
  137. NewTraces = lists:delete(Trace, Traces),
  138. lager_mochiglobal:put(loglevel, {MinLevel, NewTraces}),
  139. case get_loglevel(Target) of
  140. none ->
  141. %% check no other traces point here
  142. case lists:keyfind(Target, 3, NewTraces) of
  143. false ->
  144. gen_event:delete_handler(lager_event, Target, []);
  145. _ ->
  146. ok
  147. end;
  148. _ ->
  149. ok
  150. end,
  151. ok.
  152. clear_all_traces() ->
  153. {MinLevel, _Traces} = lager_mochiglobal:get(loglevel),
  154. lager_mochiglobal:put(loglevel, {MinLevel, []}),
  155. lists:foreach(fun(Handler) ->
  156. case get_loglevel(Handler) of
  157. none ->
  158. gen_event:delete_handler(lager_event, Handler, []);
  159. _ ->
  160. ok
  161. end
  162. end, gen_event:which_handlers(lager_event)).
  163. status() ->
  164. Handlers = gen_event:which_handlers(lager_event),
  165. Status = ["Lager status:\n",
  166. [begin
  167. Level = get_loglevel(Handler),
  168. case Handler of
  169. {lager_file_backend, File} ->
  170. io_lib:format("File ~s at level ~p\n", [File, Level]);
  171. lager_console_backend ->
  172. io_lib:format("Console at level ~p\n", [Level]);
  173. _ ->
  174. []
  175. end
  176. end || Handler <- Handlers],
  177. "Active Traces:\n",
  178. [begin
  179. io_lib:format("Tracing messages matching ~p at level ~p to ~p\n",
  180. [Filter, lager_util:num_to_level(Level), Destination])
  181. end || {Filter, Level, Destination} <- element(2, lager_mochiglobal:get(loglevel))]],
  182. io:put_chars(Status).
  183. %% @doc Set the loglevel for a particular backend.
  184. set_loglevel(Handler, Level) when is_atom(Level) ->
  185. Reply = gen_event:call(lager_event, Handler, {set_loglevel, Level}, infinity),
  186. %% recalculate min log level
  187. MinLog = minimum_loglevel(get_loglevels()),
  188. {_, Traces} = lager_mochiglobal:get(loglevel),
  189. lager_mochiglobal:put(loglevel, {MinLog, Traces}),
  190. Reply.
  191. %% @doc Set the loglevel for a particular backend that has multiple identifiers
  192. %% (eg. the file backend).
  193. set_loglevel(Handler, Ident, Level) when is_atom(Level) ->
  194. io:format("handler: ~p~n", [{Handler, Ident}]),
  195. Reply = gen_event:call(lager_event, {Handler, Ident}, {set_loglevel, Level}, infinity),
  196. %% recalculate min log level
  197. MinLog = minimum_loglevel(get_loglevels()),
  198. {_, Traces} = lager_mochiglobal:get(loglevel),
  199. lager_mochiglobal:put(loglevel, {MinLog, Traces}),
  200. Reply.
  201. %% @doc Get the loglevel for a particular backend. In the case that the backend
  202. %% has multiple identifiers, the lowest is returned
  203. get_loglevel(Handler) ->
  204. case gen_event:call(lager_event, Handler, get_loglevel, infinity) of
  205. X when is_integer(X) ->
  206. lager_util:num_to_level(X);
  207. Y -> Y
  208. end.
  209. %% @doc Try to convert an atom to a posix error, but fall back on printing the
  210. %% term if its not a valid posix error code.
  211. posix_error(Error) when is_atom(Error) ->
  212. case erl_posix_msg:message(Error) of
  213. "unknown POSIX error" -> atom_to_list(Error);
  214. Message -> Message
  215. end;
  216. posix_error(Error) ->
  217. safe_format_chop("~p", [Error], 4096).
  218. %% @private
  219. get_loglevels() ->
  220. [gen_event:call(lager_event, Handler, get_loglevel, infinity) ||
  221. Handler <- gen_event:which_handlers(lager_event)].
  222. %% @private
  223. minimum_loglevel([]) ->
  224. -1; %% lower than any log level, logging off
  225. minimum_loglevel(Levels) ->
  226. erlang:hd(lists:reverse(lists:sort(Levels))).
  227. %% @doc Print the format string `Fmt' with `Args' safely with a size
  228. %% limit of `Limit'. If the format string is invalid, or not enough
  229. %% arguments are supplied 'FORMAT ERROR' is printed with the offending
  230. %% arguments. The caller is NOT crashed.
  231. safe_format(Fmt, Args, Limit) ->
  232. safe_format(Fmt, Args, Limit, []).
  233. safe_format(Fmt, Args, Limit, Options) ->
  234. try lager_trunc_io:format(Fmt, Args, Limit, Options)
  235. catch
  236. _:_ -> lager_trunc_io:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit)
  237. end.
  238. %% @private
  239. safe_format_chop(Fmt, Args, Limit) ->
  240. safe_format(Fmt, Args, Limit, [{chomp, true}]).