In 10 years of C++ I have never dropped back to pouring over logs once I have an idea of where the problem is. Drop a code breakpoint, inspect the stack, find the variable that ain't what it's supposed to be. The next step is to watch every change to that variable over its lifetime and figure out what went wrong.
The dumb way to do this is to drop printfs in the code on every state change of that variable. The tooling way is a data breakpoint. If you have a decent test suite for the codebase that can replicate the fault, the data breakpoint is IMHO always faster.
If you can't replicate the fault, you're going to need logging. So logging isn't obsolete in all cases, but with Haskell (at least in my experience) the excessive logging approach is your only option really.
With time travel debugging, data breakpoints become even more useful, because you can backtrack to the root cause once you've recorded the fault (rather than needing to run forwards and hope to replicate a corruption at the same location as last time).
I'm not quite sure what a meaningful functional equivalent would be given, semantically, no state is actually mutable in Haskell - but "where did this value come from?" still ought to be useful.
The dumb way to do this is to drop printfs in the code on every state change of that variable. The tooling way is a data breakpoint. If you have a decent test suite for the codebase that can replicate the fault, the data breakpoint is IMHO always faster.
If you can't replicate the fault, you're going to need logging. So logging isn't obsolete in all cases, but with Haskell (at least in my experience) the excessive logging approach is your only option really.