log_monitor/apps/log_monitor/src/config.erl

384 lines
14 KiB
Erlang

%%%-------------------------------------------------------------------
%%% @author Fabio Salvini <fs@fabiosalvini.com>
%%% @copyright (C) 2017, Fabio Salvini
%%% @doc
%%%
%%% @end
%%% Created : 2 Jul 2017 by Fabio Salvini <fs@fabiosalvini.com>
%%%-------------------------------------------------------------------
-module(config).
-behaviour(gen_server).
-include_lib("mnesia_tables.hrl").
%% API
-export([start_link/0]).
-export([status/0, reload/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {statuses}).
%%%===================================================================
%%% API
%%%===================================================================
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link(?MODULE, [], []).
%%--------------------------------------------------------------------
%% @doc
%% Get the status of the log files
%%
%% @spec status() -> [{Logfile, Status}]
%% @end
%%--------------------------------------------------------------------
status() ->
gen_server:call(config, {get_statuses}).
%%--------------------------------------------------------------------
%% @doc
%% Reload the configuration
%%
%% @spec reload() -> ok
%% @end
%%--------------------------------------------------------------------
reload() ->
gen_server:call(config, {reload}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
register(config, self()),
Statuses = ets:new(log_statuses, []),
timer:apply_after(1000, ?MODULE, reload, []),
{ok, #state{statuses = Statuses}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @spec handle_call(Request, From, State) ->
%% {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call({reload}, _From, State = #state{statuses = Statuses}) ->
{ok, File} = application:get_env(log_monitor, logfiles_config),
{ok, Terms} = file:consult(File),
MonitoredLogs = proplists:get_value(monitored_logs, Terms),
Groups = [#log_monitor_group{name = Name, email_receivers = EmailReceivers, email_subject = EmailSubject, default_error_regex = DefaultErrorRegex}
|| {{group, Name}, {email_receivers, EmailReceivers}, {email_subject, EmailSubject}, {default_error_regex, DefaultErrorRegex}, _} <- MonitoredLogs],
Logfiles = utils:flatten([[#log_monitor_file{file = F, error_regex = ErrorRegex, group = Name} || {{file, F}, {error_regex, ErrorRegex}} <- Logfiles]
|| {{group, Name}, {email_receivers, _}, {email_subject, _}, {default_error_regex, _}, {logfiles, Logfiles}} <- MonitoredLogs]),
GroupsPartitions = compare_groups(Groups),
manage_deleted_groups(proplists:get_value(deleted, GroupsPartitions)),
manage_existing_groups(proplists:get_value(existing, GroupsPartitions)),
manage_new_groups(proplists:get_value(new, GroupsPartitions)),
LogfilesPartitions = compare_logfiles(Logfiles),
manage_deleted_logfiles(proplists:get_value(deleted, LogfilesPartitions), Statuses),
manage_existing_logfiles(proplists:get_value(existing, LogfilesPartitions), Statuses),
manage_new_logfiles(proplists:get_value(new, LogfilesPartitions), Statuses),
{reply, ok, State};
handle_call({get_statuses}, _From, State = #state{statuses = Statuses}) ->
{reply, ets:tab2list(Statuses), State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info({watcher_init, File}, State = #state{statuses = Statuses}) ->
case ets:lookup(Statuses, File) of
%% If the File has been removed, do nothing
[] -> ok;
_ -> ets:insert(Statuses, {File, active})
end,
{noreply, State};
handle_info({watcher_terminate, File}, State = #state{statuses = Statuses}) ->
case ets:lookup(Statuses, File) of
%% If the File has been removed, do nothing
[] -> ok;
_ -> ets:insert(Statuses, {File, inactive})
end,
{noreply, State};
handle_info(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
shutdown.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Compare the old and new groups.
%%
%% @spec compare_groups(Logfiles) ->
%% [{deleted, D}, {existing, E}, {new, N}]
%% @end
%%--------------------------------------------------------------------
compare_groups(Groups) ->
Old = list_groups(),
Deleted = lists:filter(fun(Name) -> not lists:any(fun(Group) -> Group#log_monitor_group.name == Name end, Groups) end, Old),
Existing = lists:filter(fun(Group) -> lists:member(Group#log_monitor_group.name, Old) end, Groups),
New = lists:filter(fun(Group) -> not lists:member(Group#log_monitor_group.name, Old) end, Groups),
[
{deleted, Deleted},
{existing, Existing},
{new, New}
].
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Manage the deleted groups.
%% Each group is removed from the database..
%%
%% @spec manage_deleted_groups(Groups) -> void()
%% @end
%%--------------------------------------------------------------------
manage_deleted_groups(Groups) ->
mnesia:activity(
transaction,
fun() ->
lists:foldl(fun(Name, _Acc) ->
mnesia:delete({log_monitor_group, Name})
end, [], Groups)
end).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Manage the existing groups.
%% Each group in the database is updated.
%%
%% @spec manage_existing_groups(Groups) -> void()
%% @end
%%--------------------------------------------------------------------
manage_existing_groups(Groups) ->
mnesia:activity(
transaction,
fun() ->
lists:foldl(fun(Group, _Acc) ->
mnesia:write(Group)
end, [], Groups)
end).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Manage the new groups.
%% Each group is inserted in the database.
%%
%% @spec manage_new_groups(Groups) -> void()
%% @end
%%--------------------------------------------------------------------
manage_new_groups(Groups) ->
mnesia:activity(
transaction,
fun() ->
lists:foldl(fun(Group, _Acc) ->
mnesia:write(Group)
end, [], Groups)
end).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Get all the groups in the database.
%%
%% @spec list_groups() -> Groups
%% @end
%%--------------------------------------------------------------------
list_groups() ->
mnesia:activity(
transaction,
fun() ->
mnesia:foldl(
fun(Group, Acc) ->
[Group#log_monitor_group.name | Acc]
end, [], log_monitor_group)
end).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Compare the old and new logfiles.
%%
%% @spec compare_logfiles(Logfiles) ->
%% [{deleted, D}, {existing, E}, {new, N}]
%% @end
%%--------------------------------------------------------------------
compare_logfiles(Logfiles) ->
Old = list_logfiles(),
Deleted = lists:filter(fun(File) -> not lists:any(fun(#log_monitor_file{file = F, error_regex = _, group = _}) -> F == File end, Logfiles) end, Old),
Existing = lists:filter(fun(#log_monitor_file{file = File, error_regex = _, group = _}) -> lists:member(File, Old) end, Logfiles),
New = lists:filter(fun(#log_monitor_file{file = File, error_regex = _, group = _}) -> not lists:member(File, Old) end, Logfiles),
[
{deleted, Deleted},
{existing, Existing},
{new, New}
].
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Manage the deleted logfiles.
%% Stop the monitoring and remove them from the database.
%%
%% @spec manage_deleted_logfiles(Logfiles, Statuses) -> void()
%% @end
%%--------------------------------------------------------------------
manage_deleted_logfiles(Logfiles, Statuses) ->
mnesia:activity(
transaction,
fun() ->
lists:foldl(fun(File, _Acc) ->
logfiles_sup:remove_child(File),
mnesia:delete({log_monitor_file, File}),
ets:delete(Statuses, File)
end, [], Logfiles)
end).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Manage the existing logfiles.
%% If the error regex has been changed, restart the monitoring.
%%
%% @spec manage_existing_logfiles(Logfiles, Statuses) -> void()
%% @end
%%--------------------------------------------------------------------
manage_existing_logfiles(Logfiles, Statuses) ->
mnesia:activity(
transaction,
fun() ->
lists:foldl(fun(Logfile = #log_monitor_file{file = File, error_regex = ErrorRegex, group = _}, _Acc) ->
mnesia:write(Logfile),
OldLogfile = hd(mnesia:read(log_monitor_file, File)),
FileStatus = ets:lookup(Statuses, File),
if (OldLogfile#log_monitor_file.error_regex =/= ErrorRegex) or (FileStatus == []) ->
logfiles_sup:remove_child(File),
ets:insert(Statuses, {File, inactive}),
logfiles_sup:add_child([File, ErrorRegex]);
true ->
ok
end
end, [], Logfiles)
end).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Manage the new logfiles.
%% Each file is saved to the database and starts being monitored.
%%
%% @spec manage_new_logfiles(Logfiles, Statuses) -> void()
%% @end
%%--------------------------------------------------------------------
manage_new_logfiles(Logfiles, Statuses) ->
mnesia:activity(
transaction,
fun() ->
lists:foldl(fun(Logfile = #log_monitor_file{file = File, error_regex = ErrorRegex, group = _}, _Acc) ->
mnesia:write(Logfile),
ets:insert(Statuses, {File, inactive}),
logfiles_sup:add_child([File, ErrorRegex])
end, [], Logfiles)
end).
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Get all the logfiles in the database.
%%
%% @spec list_logfiles() -> Logfiles
%% @end
%%--------------------------------------------------------------------
list_logfiles() ->
mnesia:activity(
transaction,
fun() ->
mnesia:foldl(
fun(#log_monitor_file{file = File, error_regex = _, group = _}, Acc) ->
[File | Acc]
end, [], log_monitor_file)
end).