IMO, there really isn't a super good answer. The simple answer is: You need to delay the actual `fmt.Sprintf` call as long as possible. Which generally means, that the interface you consume (to allow the user to direct and configure logging) will need to reflect formatting and all kinds of things that the user actually doesn't want to deal with. So it's hard to find a good tradeoff between API simplicity and usability and not allocating in this case.
Another, maybe unforseen problem, is that you don't want logging to interfere with production. It's a painful lesson to learn, that synchronous logging can take down a production service with ease. If you want to be prepared for that and reduce the runtime overhead of logging, you will have to make it asynchronous. But that's fundamentally at odds with the above goal of delaying formatting - the user will usually not expect arguments to loggers to be used after the logging call returns. Buffering a formatted string avoids that of course.
Lastly, if you want runtime-configurability of logging and conditionally discard messages, you will likely reach for interfaces and thus limit the usefulness of escape analysis and inlining. So, by trying to allocate less, you might end up allocating more, because the compiler has to put things on the heap to be safe.
FWIW, I've
blogged about this a couple of years ago. The opinions expressed there are probably controversial (and I haven't looked at it in a while - I might not even agree with them myself anymore), but it might help open up even more questions or give you some ideas. Also, there are of course many, many logging APIs already existing and I'm sure the people who wrote and use them will have strong opinions about those as well :) Personally, I've kind of given up on the idea of a panacea log API.