/modules/mod_logging/mod_logging.erl

http://github.com/zotonic/zotonic · Erlang · 234 lines · 138 code · 40 blank · 56 comment · 0 complexity · 1664ada6b93dbaeb8595f44c3db8e36f MD5 · raw file

  1. %% @author Arjan Scherpenisse <arjan@scherpenisse.net>
  2. %% @copyright 2010 Arjan Scherpenisse
  3. %% @doc Simple database logging.
  4. %% Copyright 2010 Arjan Scherpenisse
  5. %%
  6. %% Licensed under the Apache License, Version 2.0 (the "License");
  7. %% you may not use this file except in compliance with the License.
  8. %% You may obtain a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing, software
  13. %% distributed under the License is distributed on an "AS IS" BASIS,
  14. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. %% See the License for the specific language governing permissions and
  16. %% limitations under the License.
  17. -module(mod_logging).
  18. -author("Arjan Scherpenisse <arjan@scherpenisse.net>").
  19. -behaviour(gen_server).
  20. -mod_title("Logging to the database").
  21. -mod_description("Logs debug/info/warning messages into the site's database.").
  22. -mod_prio(1000).
  23. %% gen_server exports
  24. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  25. -export([start_link/1]).
  26. -export([
  27. observe_search_query/2,
  28. pid_observe_zlog/3,
  29. observe_admin_menu/3
  30. ]).
  31. -include("zotonic.hrl").
  32. -include_lib("modules/mod_admin/include/admin_menu.hrl").
  33. -record(state, {site :: atom() | undefined, admin_log_pages=[] :: list()}).
  34. %% interface functions
  35. observe_search_query({search_query, Req, OffsetLimit}, Context) ->
  36. search(Req, OffsetLimit, Context).
  37. pid_observe_zlog(Pid, #zlog{props=#log_message{}=Msg}, Context) ->
  38. case Msg#log_message.user_id of
  39. undefined -> gen_server:cast(Pid, {log, Msg#log_message{user_id=z_acl:user(Context)}});
  40. _UserId -> gen_server:cast(Pid, {log, Msg})
  41. end;
  42. pid_observe_zlog(Pid, #zlog{props=#log_email{}=Msg}, _Context) ->
  43. gen_server:cast(Pid, {log, Msg});
  44. pid_observe_zlog(_Pid, #zlog{}, _Context) ->
  45. undefined.
  46. %%====================================================================
  47. %% API
  48. %%====================================================================
  49. %% @spec start_link(Args) -> {ok,Pid} | ignore | {error,Error}
  50. %% @doc Starts the server
  51. start_link(Args) when is_list(Args) ->
  52. gen_server:start_link(?MODULE, Args, []).
  53. %%====================================================================
  54. %% gen_server callbacks
  55. %%====================================================================
  56. %% @spec init(Args) -> {ok, State} |
  57. %% {ok, State, Timeout} |
  58. %% ignore |
  59. %% {stop, Reason}
  60. %% @doc Initiates the server.
  61. init(Args) ->
  62. {context, Context} = proplists:lookup(context, Args),
  63. Context1 = z_acl:sudo(z_context:new(Context)),
  64. install_check(Context1),
  65. Site = z_context:site(Context1),
  66. lager:md([
  67. {site, Site},
  68. {module, ?MODULE}
  69. ]),
  70. {ok, #state{site=Site}}.
  71. %% @spec handle_call(Request, From, State) -> {reply, Reply, State} |
  72. %% {reply, Reply, State, Timeout} |
  73. %% {noreply, State} |
  74. %% {noreply, State, Timeout} |
  75. %% {stop, Reason, Reply, State} |
  76. %% {stop, Reason, State}
  77. %% @doc Handling call messages
  78. handle_call(Message, _From, State) ->
  79. {stop, {unknown_call, Message}, State}.
  80. handle_cast({log, #log_message{} = Log}, State) ->
  81. handle_simple_log(Log, State),
  82. {noreply, State};
  83. handle_cast({log, OtherLog}, State) ->
  84. handle_other_log(OtherLog, State),
  85. {noreply, State};
  86. %% @doc Trap unknown casts
  87. handle_cast(Message, State) ->
  88. {stop, {unknown_cast, Message}, State}.
  89. %% @spec handle_info(Info, State) -> {noreply, State} |
  90. %% {noreply, State, Timeout} |
  91. %% {stop, Reason, State}
  92. %% @doc Handling all non call/cast messages
  93. handle_info(_Info, State) ->
  94. {noreply, State}.
  95. %% @spec terminate(Reason, State) -> void()
  96. %% @doc This function is called by a gen_server when it is about to
  97. %% terminate. It should be the opposite of Module:init/1 and do any necessary
  98. %% cleaning up. When it returns, the gen_server terminates with Reason.
  99. %% The return value is ignored.
  100. terminate(_Reason, _State) ->
  101. ok.
  102. %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
  103. %% @doc Convert process state when code is changed
  104. code_change(_OldVsn, State, _Extra) ->
  105. {ok, State}.
  106. %%====================================================================
  107. %% support functions
  108. %%====================================================================
  109. install_check(Context) ->
  110. m_log:install(Context),
  111. m_log_email:install(Context).
  112. search({log, []}, _OffsetLimit, _Context) ->
  113. #search_sql{
  114. select="l.id",
  115. from="log l",
  116. tables=[{log, "l"}],
  117. order="created DESC",
  118. args=[],
  119. assoc=false
  120. };
  121. search({log_email, Filter}, _OffsetLimit, Context) ->
  122. m_log_email:search(Filter, Context);
  123. search(_, _, _) ->
  124. undefined.
  125. %% @doc Insert a simple log entry. Send an update to all UA's displaying the log.
  126. handle_simple_log(#log_message{user_id=UserId, type=Type, message=Msg, props=Props}, State) ->
  127. Context = z_acl:sudo(z_context:new(State#state.site)),
  128. {ok, Id} = z_db:insert(log, [
  129. {user_id, UserId},
  130. {type, Type},
  131. {message, Msg}
  132. ] ++ Props, Context),
  133. mod_signal:emit({log_message, [{log_id, Id}, {user_id, UserId}, {type, Type}, {message, Msg}, {props, Props}]}, Context).
  134. % All non #log_message{} logs are sent to their own log table. If the severity of the log entry is high enough then
  135. % it is also sent to the main log.
  136. handle_other_log(Record, State) ->
  137. Context = z_acl:sudo(z_context:new(State#state.site)),
  138. LogType = element(1, Record),
  139. Fields = record_to_proplist(Record),
  140. case z_db:table_exists(LogType, Context) of
  141. true ->
  142. {ok, Id} = z_db:insert(LogType, Fields, Context),
  143. Log = record_to_log_message(Record, Fields, LogType, Id),
  144. case proplists:get_value(severity, Fields) of
  145. ?LOG_FATAL -> handle_simple_log(Log#log_message{type=fatal}, State);
  146. ?LOG_ERROR -> handle_simple_log(Log#log_message{type=error}, State);
  147. _Other -> nop
  148. end,
  149. mod_signal:emit({LogType, [{log_id, Id}|Fields]}, Context);
  150. false ->
  151. Log = #log_message{
  152. message=z_convert:to_binary(proplists:get_value(message, Fields, LogType)),
  153. props=[ {log_type, LogType} | Fields ]
  154. },
  155. handle_simple_log(Log, State)
  156. end.
  157. record_to_proplist(#log_email{} = Rec) ->
  158. lists:zip(record_info(fields, log_email), tl(tuple_to_list(Rec))).
  159. record_to_log_message(#log_email{} = R, _Fields, LogType, Id) ->
  160. #log_message{
  161. message=iolist_to_binary(["SMTP: ",
  162. z_convert:to_list(R#log_email.mailer_status), ": ", to_list(R#log_email.mailer_message), $\n,
  163. "To: ", z_convert:to_list(R#log_email.envelop_to), opt_user(R#log_email.to_id), $\n,
  164. "From: ", z_convert:to_list(R#log_email.envelop_from), opt_user(R#log_email.from_id)
  165. ]),
  166. props=[{log_type, LogType}, {log_id, Id}]
  167. };
  168. record_to_log_message(_, Fields, LogType, Id) ->
  169. #log_message{
  170. message=z_convert:to_binary(proplists:get_value(message, Fields, LogType)),
  171. props=[ {log_type, LogType}, {log_id, Id} | Fields ]
  172. }.
  173. to_list({error, timeout}) ->
  174. "timeout";
  175. to_list(R) when is_tuple(R) ->
  176. io_lib:format("~p", [R]);
  177. to_list(V) ->
  178. z_convert:to_list(V).
  179. opt_user(undefined) -> [];
  180. opt_user(Id) -> [" (", integer_to_list(Id), ")"].
  181. observe_admin_menu(admin_menu, Acc, Context) ->
  182. [
  183. #menu_item{id=admin_log,
  184. parent=admin_system,
  185. label=?__("Log", Context),
  186. url={admin_log},
  187. visiblecheck={acl, use, mod_logging}},
  188. #menu_item{id=admin_log_email,
  189. parent=admin_system,
  190. label=?__("Email log", Context),
  191. url={admin_log_email},
  192. visiblecheck={acl, use, mod_logging}},
  193. #menu_separator{parent=admin_system}
  194. |Acc].