384 lines
14 KiB
Erlang
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).
|