Another thing to consider and is important to me - logging objects and state isn't always so simple. It can often be easier for me to open the debugger to look at the state of an object which cannot easily be printed.
An object with many fields (in a language with no conveniences for it).
An object tree with multiple levels of nesting.
A list or dictionary of such objects.
In general, print-based debugging requires a greater degree of specificity. If you know exactly what you're looking for it's great.
If you are performing a more exploratory sort of debugging, a decent graphical debugger will save you a ton of time.
In my case it's basically everything since I work in java, jackson's object mapper can easily get stuck or deserialise something incorrectly if the class hasn't been annotated correctly. So it's simpler for me to pull up the debugger and I can see the "actual" data thats making up my object, it also lets me run "queries" against anything of interest (i.e. computed fields).
The default toString method I've found to be useless almost every time I wanted to inspect an object in our codebase since it just prints the type + "id" for the object
The Java language should provide an internal serialization of its structures for debug purposes capable of handling nested objects and scalars of any type ... (something like Serializable, but meant for readability, not re-loading the result and also Objects and primitives should get it automatically without declaring it).
Neither JSON should be needed, not chaining together gets and prints.