Using message_hook/3

Note: This is a topic for using message_hook/3 with SWI-Prolog.
If you have Trust level: Basic you can edit this Wiki topic by clicking on the edit icon in the lower right. Capture

Note: Do not reply to this topic; questions, concerns, comments, etc. are to be handled in
Wiki Discussion: Using message_hook/3



message_hook/3 as the name implies is a hook.
For an overview of SWI-Prolog hooks see: An overview of hook predicates.
message_hook/3 is called using module user, i.e. user:message_hook(A,B,C) instead of message_hook(A,B,C).

Often when using such predicates for the first time having real world example code to look at is helpful. Such examples from the SWI-Prolog source code are referenced in the following.

The hook can be a static predicate (example 1) or a dynamic predicate that is asserted. (example 2,3).

Some other hooks define dynamic/1 and multifile/1 in init.pl, message_hook/3 does not.
If message_hook/3 is defined in more than one file then use multifile/1 to suppress the warning message.
Use dynamic/1 if you want to allow dynamic modification later, e.g. retract/1 and assert/1.


A simple example of message_hook/3

This will just take the values received from the messages and print them to the current output stream.

user:message_hook(Term, Kind, Lines) :-
    format('Term: ~w, Kind: ~w, Lines: ~w~n',[Term,Kind,Lines]).

A demonstration of the simple example

Directory: C:/Users/Groot (You can change the directory as needed).
File: format_example.pl

user:message_hook(Term, Kind, Lines) :-
    format('Term: ~w, Kind: ~w, Lines: ~w~n',[Term,Kind,Lines]).

Example run

Welcome to SWI-Prolog (threaded, 64 bits, version 8.3.28-20)
...

?- working_directory(_,'C:/Users/Groot').
true.

?- [format_example].
Term: load_file(done(0,file(format_example,c:/users/Groot/format_example.pl),compiled,user,0.0,1)), Kind: silent, Lines: [~|~t~*+ - [0],~w-[format_example], ~w-[compiled], ~2f sec, ~D clauses-[0.0,1]]
Term: query(yes([],true,[]-[])), Kind: query, Lines: [ansi(truth(true),true.,[]),~N-[]]

An example that uses print_message/2.

This shows both the input values output using format/2 and then uses print_message/2 to print a message. Notice that messages with Kind is silent print_message/2 does not print a message.

Directory: C:/Users/Groot (You can change the directory as needed).
File: print_message_example.pl

user:message_hook(Term, Kind, Lines) :-
    format('Term: ~w~nKind: ~w~nLines: ~w~n~n',[Term,Kind,Lines]),
    print_message(Kind, Term),
    nl,nl.

:- append([1,2],[3,4],R).
Example run (Click triangle to expand)
Welcome to SWI-Prolog (threaded, 64 bits, version 8.3.28-20)
...

?- working_directory(_,'C:/Users/Groot').
true.

?- [print_message_example].
Term: singletons((:-append([1,2],[3,4],_20202)),[R])
Kind: warning
Lines: [Singleton variables: ~w-[[R]]]

Recursive warning message: singletons((:-append([1,2],[3,4],_20202)),['R'])


Term: autoload(user:append/3,c:/program files/swipl/library/lists)
Kind: silent
Lines: [autoloading ~p from ~w-[user:append/3,c:/program files/swipl/library/lists]]



Term: load_file(start(1,file(c:/program files/swipl/library/lists,c:/program files/swipl/library/lists.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[1],~w-[c:/program files/swipl/library/lists], ...]



Term: load_file(done(1,file(c:/program files/swipl/library/lists,c:/program files/swipl/library/lists.pl),compiled,lists,0.0,111))
Kind: silent
Lines: [~|~t~*+ - [1],~w-[c:/program files/swipl/library/lists], ~w-[compiled], into ~w-[lists], ~2f sec, ~D clauses-[0.0,111]]



Term: load_file(done(0,file(print_message_example,c:/users/groot/print_message_example.pl),compiled,user,0.0,2))
Kind: silent
Lines: [~|~t~*+ - [0],~w-[print_message_example], ~w-[compiled], ~2f sec, ~D clauses-[0.0,2]]



Term: query(yes([],true,[]-[]))
Kind: query
Lines: [ansi(truth(true),true.,[]),~N-[]]

Recursive query message: query(yes([],true,[]-[]))

An example that uses message_to_string/2.

Directory: C:/Users/Groot (You can change the directory as needed).
File: message_to_string_example.pl

user:message_hook(Term, Kind, Lines) :-
    format('Term: ~w~nKind: ~w~nLines: ~w~n~n',[Term,Kind,Lines]),
    message_to_string(Term,String),
    format('~s~n',[String]),
    nl,nl.

:- append([1,2],[3,4],R).
Example run (Click triangle to expand)
Welcome to SWI-Prolog (threaded, 64 bits, version 8.3.28-20)
...

?- working_directory(_,'C:/Users/Groot').
true.

?- [message_to_string_example].
Term: singletons((:-append([1,2],[3,4],_20202)),[R])
Kind: warning
Lines: [Singleton variables: ~w-[[R]]]

Singleton variables: [R]


Term: autoload(user:append/3,c:/program files/swipl/library/lists)
Kind: silent
Lines: [autoloading ~p from ~w-[user:append/3,c:/program files/swipl/library/lists]]

autoloading user:append/3 from c:/program files/swipl/library/lists


Term: load_file(start(1,file(c:/program files/swipl/library/lists,c:/program files/swipl/library/lists.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[1],~w-[c:/program files/swipl/library/lists], ...]

 Loading c:/program files/swipl/library/lists ...


Term: load_file(done(1,file(c:/program files/swipl/library/lists,c:/program files/swipl/library/lists.pl),compiled,lists,0.015625,111))
Kind: silent
Lines: [~|~t~*+ - [1],~w-[c:/program files/swipl/library/lists], ~w-[compiled], into ~w-[lists], ~2f sec, ~D clauses-[0.015625,111]]

 c:/program files/swipl/library/lists compiled into lists 0.02 sec, 111 clauses


Term: load_file(done(0,file(message_to_string_example,c:/users/groot/message_to_string_example.pl),compiled,user,0.015625,2))
Kind: silent
Lines: [~|~t~*+ - [0],~w-[message_to_string_example], ~w-[compiled], ~2f sec, ~D clauses-[0.015625,2]]

message_to_string_example compiled 0.02 sec, 2 clauses


Term: query(yes([],true,[]-[]))
Kind: query
Lines: [ansi(truth(true),true.,[]),~N-[]]

true.

An example that uses print_message_lines/3.

Directory: C:/Users/Groot (You can change the directory as needed).
File: print_message_lines_example.pl

user:message_hook(Term, Kind, Lines) :-
    format('Term: ~w~nKind: ~w~nLines: ~w~n~n',[Term,Kind,Lines]),
    print_message_lines(current_output,kind(Kind),Lines),
    nl.

:- append([1,2],[3,4],R).
Example run (Click triangle to expand)
Welcome to SWI-Prolog (threaded, 64 bits, version 8.3.28-20)
...

?- working_directory(_,'C:/Users/Groot').
true.

?- [print_message_lines_example].
Term: singletons((:-append([1,2],[3,4],_20202)),[R])
Kind: warning
Lines: [Singleton variables: ~w-[[R]]]

Warning: Singleton variables: [R]

Term: autoload(user:append/3,c:/program files/swipl/library/lists)
Kind: silent
Lines: [autoloading ~p from ~w-[user:append/3,c:/program files/swipl/library/lists]]

autoloading user:append/3 from c:/program files/swipl/library/lists

Term: load_file(start(1,file(c:/program files/swipl/library/lists,c:/program files/swipl/library/lists.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[1],~w-[c:/program files/swipl/library/lists], ...]

 Loading c:/program files/swipl/library/lists ...

Term: load_file(done(1,file(c:/program files/swipl/library/lists,c:/program files/swipl/library/lists.pl),compiled,lists,0.015625,111))
Kind: silent
Lines: [~|~t~*+ - [1],~w-[c:/program files/swipl/library/lists], ~w-[compiled], into ~w-[lists], ~2f sec, ~D clauses-[0.015625,111]]

 c:/program files/swipl/library/lists compiled into lists 0.02 sec, 111 clauses

Term: load_file(done(0,file(print_message_lines_example,c:/users/groot/print_message_lines_example.pl),compiled,user,0.015625,2))
Kind: silent
Lines: [~|~t~*+ - [0],~w-[print_message_lines_example], ~w-[compiled], ~2f sec, ~D clauses-[0.015625,2]]

print_message_lines_example compiled 0.02 sec, 2 clauses

Term: query(yes([],true,[]-[]))
Kind: query
Lines: [ansi(truth(true),true.,[]),~N-[]]

true.

An example that prints informational messages using unit test.

Directory: C:/Users/Groot (You can change the directory as needed).
File: print_message_lines_example.pl

user:message_hook(Term, Kind, Lines) :-
    format('Term: ~w~nKind: ~w~nLines: ~w~n~n',[Term,Kind,Lines]),
    print_message_lines(current_output,kind(Kind),Lines),
    nl.

:- begin_tests(message_hook).

:- use_module(library(lists)).

test(reverse) :-
        reverse([a,b], [b,a]).

:- end_tests(message_hook).

Note: In the example run below, the informational messages are not generated when the code is loaded, the informational messages are generated for run_tests/0.

Example run (Click triangle to expand)
Welcome to SWI-Prolog (threaded, 64 bits, version 8.3.28-20)
...
?- [informational_message_example].
Term: autoload(user:begin_tests/1,c:/program files/swipl/library/plunit)
Kind: silent
Lines: [autoloading ~p from ~w-[user:begin_tests/1,c:/program files/swipl/library/plunit]]

autoloading user:begin_tests/1 from c:/program files/swipl/library/plunit

Term: load_file(start(1,file(c:/program files/swipl/library/plunit,c:/program files/swipl/library/plunit.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[1],~w-[c:/program files/swipl/library/plunit], ...]

 Loading c:/program files/swipl/library/plunit ...

Term: load_file(done(1,file(c:/program files/swipl/library/plunit,c:/program files/swipl/library/plunit.pl),compiled,plunit,0.015625000000000028,280))
Kind: silent
Lines: [~|~t~*+ - [1],~w-[c:/program files/swipl/library/plunit], ~w-[compiled], into ~w-[plunit], ~2f sec, ~D clauses-[0.015625000000000028,280]]

 c:/program files/swipl/library/plunit compiled into plunit 0.02 sec, 280 clauses

Term: autoload(plunit:must_be/2,c:/program files/swipl/library/error.pl)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit:must_be/2,c:/program files/swipl/library/error.pl]]

autoloading plunit:must_be/2 from c:/program files/swipl/library/error.pl

Term: load_file(start(2,file(c:/program files/swipl/library/error.pl,c:/program files/swipl/library/error.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[2],~w-[c:/program files/swipl/library/error.pl], ...]

  Loading c:/program files/swipl/library/error.pl ...

Term: load_file(done(2,file(c:/program files/swipl/library/error.pl,c:/program files/swipl/library/error.pl),compiled,error,0.015625,90))
Kind: silent
Lines: [~|~t~*+ - [2],~w-[c:/program files/swipl/library/error.pl], ~w-[compiled], into ~w-[error], ~2f sec, ~D clauses-[0.015625,90]]

  c:/program files/swipl/library/error.pl compiled into error 0.02 sec, 90 clauses

Term: autoload(plunit:option/3,c:/program files/swipl/library/option.pl)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit:option/3,c:/program files/swipl/library/option.pl]]

autoloading plunit:option/3 from c:/program files/swipl/library/option.pl

Term: load_file(start(2,file(c:/program files/swipl/library/option.pl,c:/program files/swipl/library/option.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[2],~w-[c:/program files/swipl/library/option.pl], ...]

  Loading c:/program files/swipl/library/option.pl ...

Term: load_file(done(2,file(c:/program files/swipl/library/option.pl,c:/program files/swipl/library/option.pl),compiled,swi_option,0.0,42))
Kind: silent
Lines: [~|~t~*+ - [2],~w-[c:/program files/swipl/library/option.pl], ~w-[compiled], into ~w-[swi_option], ~2f sec, ~D clauses-[0.0,42]]

  c:/program files/swipl/library/option.pl compiled into swi_option 0.00 sec, 42 clauses

Term: load_file(start(1,file(library(lists),c:/program files/swipl/library/lists.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[1],~p-[library(lists)], ...]

 Loading library(lists) ...

Term: load_file(done(1,file(library(lists),c:/program files/swipl/library/lists.pl),compiled,lists,0.0,111))
Kind: silent
Lines: [~|~t~*+ - [1],~p-[library(lists)], ~w-[compiled], into ~w-[lists], ~2f sec, ~D clauses-[0.0,111]]

 library(lists) compiled into lists 0.00 sec, 111 clauses

Term: autoload(plunit:ord_intersection/3,c:/program files/swipl/library/ordsets.pl)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit:ord_intersection/3,c:/program files/swipl/library/ordsets.pl]]

autoloading plunit:ord_intersection/3 from c:/program files/swipl/library/ordsets.pl

Term: load_file(start(2,file(c:/program files/swipl/library/ordsets.pl,c:/program files/swipl/library/ordsets.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[2],~w-[c:/program files/swipl/library/ordsets.pl], ...]

  Loading c:/program files/swipl/library/ordsets.pl ...

Term: load_file(done(2,file(c:/program files/swipl/library/ordsets.pl,c:/program files/swipl/library/ordsets.pl),compiled,ordsets,0.0,88))
Kind: silent
Lines: [~|~t~*+ - [2],~w-[c:/program files/swipl/library/ordsets.pl], ~w-[compiled], into ~w-[ordsets], ~2f sec, ~D clauses-[0.0,88]]

  c:/program files/swipl/library/ordsets.pl compiled into ordsets 0.00 sec, 88 clauses

Term: autoload(plunit:maplist/3,c:/program files/swipl/library/apply.pl)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit:maplist/3,c:/program files/swipl/library/apply.pl]]

autoloading plunit:maplist/3 from c:/program files/swipl/library/apply.pl

Term: load_file(start(2,file(c:/program files/swipl/library/apply.pl,c:/program files/swipl/library/apply.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[2],~w-[c:/program files/swipl/library/apply.pl], ...]

  Loading c:/program files/swipl/library/apply.pl ...

Term: load_file(done(2,file(c:/program files/swipl/library/apply.pl,c:/program files/swipl/library/apply.pl),compiled,apply,0.0,75))
Kind: silent
Lines: [~|~t~*+ - [2],~w-[c:/program files/swipl/library/apply.pl], ~w-[compiled], into ~w-[apply], ~2f sec, ~D clauses-[0.0,75]]

  c:/program files/swipl/library/apply.pl compiled into apply 0.00 sec, 75 clauses

Term: autoload(plunit:include/3,c:/program files/swipl/library/apply.pl)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit:include/3,c:/program files/swipl/library/apply.pl]]

autoloading plunit:include/3 from c:/program files/swipl/library/apply.pl

Term: autoload(plunit_message_hook:end_tests/1,c:/program files/swipl/library/plunit)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit_message_hook:end_tests/1,c:/program files/swipl/library/plunit]]

autoloading plunit_message_hook:end_tests/1 from c:/program files/swipl/library/plunit

Term: load_file(done(0,file(informational_message_example,c:/users/groot/informational_message_example.pl),compiled,user,0.03125000000000003,9))
Kind: silent
Lines: [~|~t~*+ - [0],~w-[informational_message_example], ~w-[compiled], ~2f sec, ~D clauses-[0.03125000000000003,9]]

informational_message_example compiled 0.03 sec, 9 clauses

Term: query(yes([],true,[]-[]))
Kind: query
Lines: [ansi(truth(true),true.,[]),~N-[]]

true.

?- run_tests.
Term: autoload(user:run_tests/0,c:/program files/swipl/library/plunit)
Kind: silent
Lines: [autoloading ~p from ~w-[user:run_tests/0,c:/program files/swipl/library/plunit]]

autoloading user:run_tests/0 from c:/program files/swipl/library/plunit

Term: toplevel_goal(user:run_tests,[])
Kind: silent
Lines: [Unknown message: ~p-[toplevel_goal(user:run_tests,[])]]

Unknown message: toplevel_goal(user:run_tests,[])

Term: autoload(plunit:option/2,c:/program files/swipl/library/option.pl)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit:option/2,c:/program files/swipl/library/option.pl]]

autoloading plunit:option/2 from c:/program files/swipl/library/option.pl

Term: autoload(plunit:debug/3,c:/program files/swipl/library/debug)
Kind: silent
Lines: [autoloading ~p from ~w-[plunit:debug/3,c:/program files/swipl/library/debug]]

autoloading plunit:debug/3 from c:/program files/swipl/library/debug

Term: load_file(start(1,file(c:/program files/swipl/library/debug,c:/program files/swipl/library/debug.pl)))
Kind: silent
Lines: [~|~t~*+Loading -[1],~w-[c:/program files/swipl/library/debug], ...]

 Loading c:/program files/swipl/library/debug ...

Term: load_file(done(1,file(c:/program files/swipl/library/debug,c:/program files/swipl/library/debug.pl),compiled,prolog_debug,0.015625,48))
Kind: silent
Lines: [~|~t~*+ - [1],~w-[c:/program files/swipl/library/debug], ~w-[compiled], into ~w-[prolog_debug], ~2f sec, ~D clauses-[0.015625,48]]

 c:/program files/swipl/library/debug compiled into prolog_debug 0.02 sec, 48 clauses

Term: plunit(begin(message_hook))
Kind: informational
Lines: [PL-Unit: ~w -[message_hook],flush]

% PL-Unit: message_hook 
Term: plunit(begin(message_hook:reverse,c:/users/groot/informational_message_example.pl:26,rational_trees))
Kind: silent
Lines: [Unknown message: ~p-[plunit(begin(message_hook:reverse,c:/users/groot/informational_message_example.pl:26,rational_trees))]]

Unknown message: plunit(begin(message_hook:reverse,'c:/users/groot/informational_message_example.pl':26,rational_trees))

Term: plunit(end(message_hook:reverse,c:/users/groot/informational_message_example.pl:26,rational_trees))
Kind: silent
Lines: [Unknown message: ~p-[plunit(end(message_hook:reverse,c:/users/groot/informational_message_example.pl:26,rational_trees))]]

Unknown message: plunit(end(message_hook:reverse,'c:/users/groot/informational_message_example.pl':26,rational_trees))

Term: plunit(progress(message_hook,reverse,passed))
Kind: information
Lines: [at_same_line,(.)-[],flush]

.
Term: plunit(end(message_hook))
Kind: informational
Lines: [at_same_line, done]

 done

Term: plunit(summary(plunit{blocked:0,failed:0,failed_assertions:0,passed:1,sto:0}))
Kind: silent
Lines: [Unknown message: ~p-[plunit(summary(plunit{blocked:0,failed:0,failed_assertions:0,passed:1,sto:0}))]]

Unknown message: plunit(summary(plunit{blocked:0,failed:0,failed_assertions:0,passed:1,sto:0}))

Term: plunit(fixme(0,0,0))
Kind: informational
Lines: []

% 

Term: plunit(all_passed(1))
Kind: informational
Lines: [test passed]

% test passed

Term: query(yes([],true,[]-[]))
Kind: query
Lines: [ansi(truth(true),true.,[]),~N-[]]

true.


Notes:


One would think that if the hook is asserted then assert/1 would be used to assert message_hook/3, typically asserta/2 is used to capture the reference to the asserted clause.

The reason that temporary installed hooks use

setup_call_cleanup(
	asserta(HookTerm, Ref),
	ProtectedCode,
	erase(Ref))

Is because this makes it trivial to remove exactly the added clause. If we use retract/1, it may retract another clause that happens to unify with the one you just added. It may also leave a choice point. Using erase/1 guarantee we have the right clause without any search and without unwanted choice points. (ref)


SWI-Prolog is written in C and low level Prolog constructs such as a clause are actually C structures (struct clause).

A side benefit of having the reference is that you can use it with certain predicates like clause_property/2.


For more clarification on the differences between static and dynamic predicates with SWI-Prolog see this other reply by Jan W.