From a13564008f83146d93e41d2d259db13558f07825 Mon Sep 17 00:00:00 2001 From: Alex Thornton Date: Wed, 2 Jan 2013 23:48:43 -0800 Subject: [PATCH] Added ability to plug in a log function Added the ability to plug in a log function in place of the existing calls to io:fwrite and error_logger:format. The details are as follows: (1) Added two new types to unsplit.hrl: * log_type(), which is either the atom 'normal' or the atom 'error'. Each log message has one type or the other. * log_fun(), which is the kind of fun that writes log messages. It takes two arguments: a log_type() and a string(). (2) Added a key 'log_fun' to the {unsplit, ...} application environment variable in unsplit.app.src. Its value is a tuple {Module, FunctionName}. (3) Added a function unsplit:default_log_fun, which is what it sounds like: the default log_fun when none is explicitly specified. It writes normal messages using io:fwrite and error messages using error_logger:format, mirroring previous functionality. (4) Added three overloads of a function unsplit:log_write, which writes messages (with or without formatting) to whatever log function has been configured in the {unsplit, ...} application environment variable. (5) Added a function unsplit:get_logger, which builds a fun that calls the appropriate log_fun. (6) Replaced all explicit calls to io:fwrite(...) with calls to unsplit:log_write(normal, ...) instead. (7) Replaced all explicit calls to error_logger:format(...) with calls to unsplit:log_write(error, ...) instead. (8) Regenerated unsplit.md documentation file. When the new 'log_fun' application environment variable has its default value, unsplit behaves as it did before. However, logging functionality can now be plugged in to replace the existing functionality. --- doc/unsplit.md | 68 +++++++++++++++++++++++++++++------------- include/unsplit.hrl | 5 ++++ src/unsplit.app.src | 3 +- src/unsplit.erl | 38 +++++++++++++++++++++++ src/unsplit_lib.erl | 18 +++++------ src/unsplit_server.erl | 30 +++++++++---------- 6 files changed, 117 insertions(+), 45 deletions(-) diff --git a/doc/unsplit.md b/doc/unsplit.md index f41b6dd..33a1788 100644 --- a/doc/unsplit.md +++ b/doc/unsplit.md @@ -8,8 +8,6 @@ Framework for merging mnesia tables after netsplit. - - __Behaviours:__ [`application`](application.md), [`supervisor`](supervisor.md). __Authors:__ : Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@erlang-solutions.com)). @@ -25,38 +23,80 @@ __Authors:__ : Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@er ##Function Index## -
get_reporter/0Look up the predefined callback module for reporting inconsistencies.
report_inconsistency/4Report an inconcistency to the predefined reporter.
report_inconsistency/5Report an inconsistency to Reporter (an unsplit_reporter behaviour).
start/2Application start callback.
stop/1Application stop callback.
+
default_log_fun/2Default implementation of an unsplit log function.
get_logger/0Look up the predefined callback function for logging.
get_reporter/0Look up the predefined callback module for reporting inconsistencies.
log_write/2Writes a log message to the predefined logger.
log_write/3Writes a formatted log message to the predefined logger.
log_write/4Writes a formatted log message to the predefined logger.
report_inconsistency/4Report an inconcistency to the predefined reporter.
report_inconsistency/5Report an inconsistency to Reporter (an unsplit_reporter behaviour).
start/2Application start callback.
stop/1Application stop callback.
##Function Details## + + +###default_log_fun/2## + + +
default_log_fun(LogType::unsplit:log_type(), Message::string()) -> ok
+

+ + +Default implementation of an unsplit log function + + +###get_logger/0## + + +
get_logger() -> unsplit:log_fun()
+

+ + +Look up the predefined callback function for logging ###get_reporter/0## +
get_reporter() -> module()
+

-
get_reporter() -> module()
+Look up the predefined callback module for reporting inconsistencies + + +###log_write/2## + + +
log_write(LogType::unsplit:log_type(), Message::string()) -> ok


+Writes a log message to the predefined logger + +###log_write/3## -Look up the predefined callback module for reporting inconsistencies - -###report_inconsistency/4## +
log_write(LogType::unsplit:log_type(), Format::string(), Data::[any()]) -> ok
+

+Writes a formatted log message to the predefined logger + +###log_write/4## -
report_inconsistency(Tab::Table, Key, ObjA::ObjectA, ObjB::ObjectB) -> ok
+ +
log_write(Logger::unsplit:log_fun(), LogType::unsplit:log_type(), Format::string(), Data::[any()]) -> ok


+Writes a formatted log message to the predefined logger + + +###report_inconsistency/4## + + +
report_inconsistency(Tab::Table, Key, ObjA::ObjectA, ObjB::ObjectB) -> ok
+

Report an inconcistency to the predefined reporter @@ -65,40 +105,28 @@ Report an inconcistency to the predefined reporter ###report_inconsistency/5## - -
report_inconsistency(Reporter, Tab::Table, Key, ObjA::ObjectA, ObjB::ObjectB) -> ok


- - Report an inconsistency to Reporter (an unsplit_reporter behaviour) ###start/2## - -
start(X1::Type, X2::Arg) -> {ok, pid()}


- - Application start callback ###stop/1## - -
stop(X1::State) -> ok


- - Application stop callback diff --git a/include/unsplit.hrl b/include/unsplit.hrl index 93ea7fa..6adea18 100644 --- a/include/unsplit.hrl +++ b/include/unsplit.hrl @@ -10,3 +10,8 @@ | {ok, any()} | {ok, merge_actions(), any()} | {ok, merge_actions(), merge_strategy(), any()}. + +-type log_fun() :: fun((LogType :: log_type(), Message :: string()) -> 'ok'). + +-type log_type() :: 'error' | 'normal'. + diff --git a/src/unsplit.app.src b/src/unsplit.app.src index 28f9233..40905f8 100644 --- a/src/unsplit.app.src +++ b/src/unsplit.app.src @@ -36,6 +36,7 @@ {registered, []}, {mod, {unsplit, []}}, {env, [ - {reporter, unsplit_reporter} + {reporter, unsplit_reporter}, + {log_fun, {unsplit, default_log_fun}} ]} ]}. diff --git a/src/unsplit.erl b/src/unsplit.erl index 8e86449..4ddebed 100644 --- a/src/unsplit.erl +++ b/src/unsplit.erl @@ -34,6 +34,10 @@ -export([get_reporter/0, report_inconsistency/4, report_inconsistency/5]). +-export([get_logger/0, + default_log_fun/2, + log_write/2, log_write/3, log_write/4]). + %% application start/stop API -export([start/2, stop/1]). @@ -60,6 +64,40 @@ report_inconsistency(Reporter, Tab, Key, ObjA, ObjB) -> Reporter:inconsistency(Tab, Key, ObjA, ObjB). +%% @spec get_logger() -> unsplit:log_fun() +%% @doc Look up the predefined callback function for logging +%% +get_logger() -> + {ok, {Module, FunctionName}} = application:get_env(unsplit, log_fun), + fun(LogType, Message) -> Module:FunctionName(LogType, Message) end. + +%% @spec default_log_fun(LogType :: unsplit:log_type(), Message :: string()) -> 'ok' +%% @doc Default implementation of an unsplit log function +%% +default_log_fun(normal, Message) -> + io:fwrite(Message); +default_log_fun(error, Message) -> + error_logger:format(Message, []). + +%% @spec log_write(LogType :: unsplit:log_type(), Message :: string()) -> 'ok' +%% @doc Writes a log message to the predefined logger +%% +log_write(LogType, Message) -> + log_write(LogType, Message, []). + +%% @spec log_write(LogType :: unsplit:log_type(), Format :: string(), Data :: [any()]) -> 'ok' +%% @doc Writes a formatted log message to the predefined logger +%% +log_write(LogType, Format, Data) -> + log_write(get_logger(), LogType, Format, Data). + +%% @spec log_write(Logger :: unsplit:log_fun(), LogType :: unsplit:log_type(), Format :: string(), Data :: [any()]) -> 'ok' +%% @doc Writes a formatted log message to the predefined logger +%% +log_write(Logger, LogType, Format, Data) -> + Logger(LogType, io_lib:format(Format, Data)). + + %% @spec start(Type, Arg) -> {ok, pid()} %% @doc Application start callback %% diff --git a/src/unsplit_lib.erl b/src/unsplit_lib.erl index 9f4c2dc..d054525 100644 --- a/src/unsplit_lib.erl +++ b/src/unsplit_lib.erl @@ -41,7 +41,7 @@ %% @end %% no_action(init, [Tab|_]) -> - error_logger:format("Will not merge table ~p~n", [Tab]), + unsplit:log_write(error, "Will not merge table ~p~n", [Tab]), stop. %% @spec last_modified(Phase, State) -> merge_ret() @@ -81,11 +81,11 @@ bag(Objs, S) -> last_version(init, [Tab, Attrs, Attr]) -> case lists:member(Attr, Attrs) of false -> - error_logger:format("Cannot merge table ~p." - "Missing ~p attribute~n", [Tab, Attr]), + unsplit:log_write(error, "Cannot merge table ~p." + "Missing ~p attribute~n", [Tab, Attr]), stop; true -> - io:fwrite("Starting merge of ~p (~p)~n", [Tab, Attrs]), + unsplit:log_write(normal, "Starting merge of ~p (~p)~n", [Tab, Attrs]), {ok, {Tab, pos(Attr, Tab, Attrs)}} end; last_version(done, _S) -> @@ -99,11 +99,11 @@ last_version(Objs, {T, P} = S) when is_list(Objs) -> vclock(init, [Tab, Attrs, Attr]) -> case lists:member(Attr, Attrs) of false -> - error_logger:format("Cannot merge table ~p." - "Missing ~p attribute~n", [Tab, Attr]), + unsplit:log_write(error, "Cannot merge table ~p." + "Missing ~p attribute~n", [Tab, Attr]), stop; true -> - io:fwrite("Starting merge of ~p (~p)~n", [Tab, Attrs]), + unsplit:log_write(normal, "Starting merge of ~p (~p)~n", [Tab, Attrs]), {ok, {Tab, pos(Attr, Tab, Attrs)}} end; vclock(done, _) -> @@ -127,14 +127,14 @@ vclock(Objs, {T, P} = S) -> {ok, Actions, same, S}. last_version_entry(Obj, T, P) -> - io:fwrite("last_version_entry(~p)~n", [Obj]), + unsplit:log_write(normal, "last_version_entry(~p)~n", [Obj]), compare(Obj, T, P, fun(A, B) when A < B -> left; (A, B) when A > B -> right; (_, _) -> neither end). compare(Obj, T, P, Comp) -> - io:fwrite("compare(~p)~n", [Obj]), + unsplit:log_write("compare(~p)~n", [Obj]), case Obj of {A, []} -> {write, A}; {[], B} -> {write, B}; diff --git a/src/unsplit_server.erl b/src/unsplit_server.erl index c4bb474..3b9618e 100644 --- a/src/unsplit_server.erl +++ b/src/unsplit_server.erl @@ -104,17 +104,17 @@ handle_cast(_Msg, State) -> %%-------------------------------------------------------------------- handle_info({mnesia_system_event, {inconsistent_database, Context, Node}}, State) -> - io:fwrite("inconsistency. Context = ~p; Node = ~p~n", [Context, Node]), + unsplit:log_write(normal, "inconsistency. Context = ~p; Node = ~p~n", [Context, Node]), Res = global:trans( {?LOCK, self()}, fun() -> - io:fwrite("have lock...~n", []), + unsplit:log_write(normal, "have lock...~n", []), stitch_together(node(), Node) end), - io:fwrite("Res = ~p~n", [Res]), + unsplit:log_write(normal, "Res = ~p~n", [Res]), {noreply, State}; handle_info(_Info, State) -> - io:fwrite("Got event: ~p~n", [_Info]), + unsplit:log_write(normal, "Got event: ~p~n", [_Info]), {noreply, State}. %%-------------------------------------------------------------------- @@ -142,7 +142,7 @@ code_change(_OldVsn, State, _Extra) -> stitch_together(NodeA, NodeB) -> case lists:member(NodeB, mnesia:system_info(running_db_nodes)) of true -> - io:fwrite("~p already stitched, it seems. All is well.~n", [NodeB]), + unsplit:log_write(normal, "~p already stitched, it seems. All is well.~n", [NodeB]), ok; false -> do_stitch_together(NodeA, NodeB) @@ -152,14 +152,14 @@ do_stitch_together(NodeA, NodeB) -> [IslandA, IslandB] = [rpc:call(N, mnesia, system_info, [running_db_nodes]) || N <- [NodeA, NodeB]], - io:fwrite("IslandA = ~p;~nIslandB = ~p~n", [IslandA, IslandB]), + unsplit:log_write(normal, "IslandA = ~p;~nIslandB = ~p~n", [IslandA, IslandB]), TabsAndNodes = affected_tables(IslandA, IslandB), Tabs = [T || {T,_} <- TabsAndNodes], - io:fwrite("Affected tabs = ~p~n", [Tabs]), + unsplit:log_write(normal, "Affected tabs = ~p~n", [Tabs]), DefaultMethod = default_method(), TabMethods = [{T, Ns, get_method(T, DefaultMethod)} || {T,Ns} <- TabsAndNodes], - io:fwrite("Methods = ~p~n", [TabMethods]), + unsplit:log_write(normal, "Methods = ~p~n", [TabMethods]), mnesia_controller:connect_nodes( [NodeB], fun(MergeF) -> @@ -169,7 +169,7 @@ do_stitch_together(NodeA, NodeB) -> %% For now, assume that we have merged with the right %% node, and not with others that could also be %% consistent (mnesia gurus, how does this work?) - io:fwrite("stitching: ~p~n", [TabMethods]), + unsplit:log_write(normal, "stitching: ~p~n", [TabMethods]), stitch_tabs(TabMethods, NodeB), Res; Other -> @@ -181,7 +181,7 @@ show_locks(OtherNode) -> Info = [{node(), mnesia_locker:get_held_locks()}, {OtherNode, rpc:call(OtherNode, mnesia_locker,get_held_locks,[])}], - io:fwrite("Held locks = ~p~n", [Info]). + unsplit:log_write(normal, "Held locks = ~p~n", [Info]). stitch_tabs(TabMethods, NodeB) -> @@ -193,16 +193,16 @@ stitch_tabs(TabMethods, NodeB) -> do_stitch({Tab, Ns, {M, F, XArgs}} = TM, Remote) -> - io:fwrite("do_stitch(~p, ~p).~n", [TM,Remote]), + unsplit:log_write(normal, "do_stitch(~p, ~p).~n", [TM,Remote]), HasCopy = lists:member(Remote, Ns), - io:fwrite("~p has a copy of ~p? -> ~p~n", [Remote, Tab, HasCopy]), + unsplit:log_write(normal, "~p has a copy of ~p? -> ~p~n", [Remote, Tab, HasCopy]), Attrs = mnesia:table_info(Tab, attributes), S0 = #st{module = M, function = F, extra_args = XArgs, table = Tab, attributes = Attrs, remote = Remote, chunk = get_table_chunk_factor(Tab), strategy = default_strategy()}, - io:fwrite("Calling ~p:~p(init, ~p)", [M,F,[Tab,Attrs|XArgs]]), + unsplit:log_write(normal, "Calling ~p:~p(init, ~p)", [M,F,[Tab,Attrs|XArgs]]), try run_stitch(check_return(M:F(init, [Tab, Attrs | XArgs]), S0)) catch @@ -213,7 +213,7 @@ do_stitch({Tab, Ns, {M, F, XArgs}} = TM, Remote) -> -spec check_return(unsplit:merge_ret(), #st{}) -> #st{}. check_return(Ret, S) -> - io:fwrite(" -> ~p~n", [Ret]), + unsplit:log_write(normal, " -> ~p~n", [Ret]), case Ret of stop -> throw(?DONE); {ok, St} -> @@ -308,7 +308,7 @@ affected_tables(IslandA, IslandB) -> Nodes = lists:concat( [mnesia:table_info(T, C) || C <- backend_types()]), - io:fwrite("nodes_of(~p) = ~p~n", [T, Nodes]), + unsplit:log_write(normal, "nodes_of(~p) = ~p~n", [T, Nodes]), case {intersection(IslandA, Nodes), intersection(IslandB, Nodes)} of {[_|_], [_|_]} ->