"What is a functional approach to logging and transactions" is a question I get asked sometimes. So here's my take.
First, there is never a perfect answer! It depends on your tolerance for impurity, what's compatible with the rest of the code and what other maintainers expect.
I use F#, which generally has a pragmatic approach to this kind of thing.
In Haskell or purist FP Scala (e.g. with Cats/Scalaz) the standard approach would be to use a writer monad, but for F#/OCaml/ReasonML etc, that would probably be overkill.