State can also be managed using the pure functional paradigm (using recursion to provide an updated state as an argument or using a State monad, for example).
Logging is a side-effect and should be treated as such by using a declarative side-effect type, like an IO monad, or could be tracked for later persistence with a Writer monad.
The oft repeated “Functional core, imperative shell” is just nonsense when you try to do it for real. It’s one of those dogmatic statements that gets dragged out in these types of discussions and adds nothing. This is why effect monads are so useful: there’s no need to create arbitrary boundaries to have effectful and pure code.
Alan Turing already proved the equivalence between lambda calculus and the Turing Machine: anything imperative can be done functionally. There really is no need to create arbitrary boundaries.
In languages like C# it may be pragmatic to do so in certain circumstances, like making ASP.NET request-handlers invoke pure functional computations and return the result of the invocation. This is pragmatic/preferable to writing a competitor to ASP.NET. Outside of situations like that I don’t see any value to an ‘imperative shell’, whatever that actually means.