printf is the only true debugging statement.
But seriously, I find that trace (or gtrace) is useful for understanding how some code works, as long as it doesn’t have complex data structures … at that point, strategic use of print (or debug/3 or similar) plus portray/1 is my go-to tool; sometimes using wrap_predicate/4.
One of these days, I’m going to write a conditional form of spy/1; but for now, adding a clause of the form
foo(X,Y,Z) :- ( some_condition(X,Y,Z) -> format('~q~n', [foo(X,Y,Z)]), fail ).
mostly does the job, even if it does require restarting the program.
PS: Is there a nice way to restart a goal that throws an exception? If a goal fails, it’s easy to do “r”, but there doesn’t seem to be the equivalent for an exception.