PageRenderTime 27ms CodeModel.GetById 11ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

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