This article won't compare all the loggers available out there nor will we tackle specific cases of what should or shouldn't be logged. Instead, we will focus entirely on how to use a logger of your choosing in your application.
In all examples we will be using the builtin
log package for simplicity and ease of trying it out (e.g. in Go's playground).
Available to every function
It's a common approach in many libraries, often given as an example
Testing is difficult
It is sometimes discouraged by the creators of logging libraries
To address concerns with global logger, in all other approaches we'll be using a
A Logger interface contains just the Debug-related methods for simplicity, but you can add more methods if you'd like.
Sample implementation using built-in Go's logger:
Can be easily replaced with another implementation
Available to every function 
Much like the previous logigng approach, this loggeris also still hard to test. It isn't possible to simply inject anotherimplementation just fortests
It needs to have some kind of a default implementation initialized so it doesn't break tests
There's no need to pass logger everywhere. This is because you will be re-using the context, which is often passed as a first parameter to all functions
This log approach can be easily replaced with other loggers
Context doesn't have any schema, so there's no guarantee that the key is in the context 
Logger becomes an implicit dependency
[...] passing loggers inside context.Context would be the worst solution to the problem of decoupling loggers from implementations. Weʼd have gone from an explicit compile time dependency to an implicit run time dependency, one that could not be enforced by the compiler.
Dependencies should be explicit
It can be easily replaced with another implementation
The only one which allows to injection of a mock for testing purposes
Make dependencies explicit! Loggers are dependencies [...] 
[...] logging, like instrumentation, is often crucial to the operation of a service. And hiding dependencies in the global scope can and does come back to bite us, whetheritʼs something as seemingly benign as a logger, or perhaps another, more important, domain-specific component that we havenʼt bothered to parameterize. Save yourself the future pain by being strict: make all your dependencies explicit.
[...] itʼs the only way to achieve decoupled design.
Verbose - we need to pass it to any struct (or function) we're using
At this stage, you're probably wondering: which logging approach should I choose?
We believe it depends on the codebase you're dealing with. For a brand new project injecting an explicit dependency is a worthwhile option. However, if you're dealing with legacy code, changing it overnight is hard. For that reason, in this case we would advise opting for a global logger used as an interface.
No matter which Golang logging approaches you choose, always pass it as an interface, and not as a concrete type. This will guarantee you the flexibility to change components of your code whenever you need, and also helps to avoid spreading external dependencies everywhere.