Description
Motivation
Go stdlib provides a log.Logger
that is widely used in both Go programs and Go libraries (eg. most notably in http.Server
) as a standardized logging component.
However, the current implementation of the log.Logger
enforces a specific logging format with limited flexibility which requires text parsing to allow it to be connected to any other logging system implementation through the swappable io.Writer
interface. This adds complexity and overhead. The other alternative of directly using third party logging libraries is not very scalable both because existing libraries including components like http.Server
in the stdlib expect log.Logger
and because of network effects of stdlib.
Proposal
This proposes a minor change to the log package to make log.Logger
more extensible in a fully backwards compatible by allowing Logger.Output()
to write logs using custom logging implementations.
We do this by defining a function type called OutputFn
with the same function signature as the current Logger.Output()
and introducing an unexported field outputfn
in Logger
to hold the function that should be used for output much like we use the out
field to hold the io.Writer
currently. By default, log.New()
will set the outputfn
to the current implementation which is moved to Logger.DefOutputFn()
. This can be swapped to any other OutputFn
implementation by calling Logger.SetOutputFn()
in the same way as we currently can swap io.Writer
by calling Logger.SetOutput()
For the top level log
package, we provide log.SetOutputFn()
and log.SetDefOutputFn()
to set the OutputFn
of the log.std
logger and reset it to default.
Proof of Concept
I have implemented a proof of concept for this proposed change and added an example in the test suite that demonstrates logging to a Tab Separated Variable format. While in the example i am merely writing to a Writer, the Output could be to anything including over the network etc.
If this approach looks ok, I can package this into a CL incorporating any feedback.
Rationale for the Approach
Most of the actual work of logging happens in Logger.Output()
(and in the unexported Logger.formatHeader()
called by it) which makes it a prime candidate for flexibility. We cannot change log.Logger
into an interface due to Go1 compatibility promise and this approach therefore allows a lot of flexibility with a very minor change.