Echoing stonemetal, I would recommend seeing if you can refactor so that you can keep your pure function as-is, and then lift it into some monad that takes care of the logging. The Writer monad can be a good way to do this elegantly.
But doesn't using that involve making the rest of the code (at least from the function that needs to log out to the top level) return monads, rather than returning whatever they were returning before?
1. Explicitly end the scope of implicitly logging things by calling runWriter, and getting a pair of (result, log) out.
2. Continue working in the implicit log monad, which would mostly involve extracting the result of the log function with `do x <- theLoggingThing`, but you can still mix in pure statments with `do let y = 5` or other function calls, and then end the do block with `return val`, where val was what you were going to return before.