noun: def. a unified/universal logger
A package that provides an adaptable logger implementation to be used by other re-usable modules that wish to emit logs using a logger supplied by the consuming project, ensuring consistent log output from both the application and packages in any modules (that support unilog
).
A module that supports logging via a unilog.Logger
may also register enrichment functions to automatically add enrichment from context at the time of emitting any log entry. Applications may also register their own enrichment functions and/or explicitly add enrichment to individual log entries.
unilog
supports blugnu/errorcontext when logging messages using any of these functions:
Error()
Errorf()
FatalError()
Fatalf()
Warnf()
Infof()
Debugf()
Tracef()
For Error()
and FatalError()
the error being logged is checked for an ErrorContext
.
For Tracef()
, Debugf()
, Infof()
, Warnf()
, Errorf()
and Fatalf()
args are inspected for any error
s. If an error
is identified it is checked for an ErrorContext
; if there is no ErrorContext
further args are checked until an ErrorContext
is found or there are no more args.
If an ErrorContext
is identified, the context in the error is used to provide enrichment of the log entry before being emitted.
unilog
does not implement an actual logger. It provides a delegate that routes logging calls via an adapter to a logger configured by an application. A consuming project will configure whatever logger it wishes and then wrap that with the appropriate adapter so that it may be injected into any modules or packages that support a unilog.Logger
.
An adapter is provided for the standard library log
package. This may be initialised using unitlog.StdLog()
.
A Nul
adapter is also provided. This produces no log output what-so-ever ("logging to NUL").
An adapter for logrus is available in a separate module: (unilog4logrus). The logrus
adapter is provided in a separate module to avoid unilog
itself taking any dependency on logrus
.
- Configure a logger
- Wrap your logger in a
unilog.Logger
using eitherunilog.UsingAdapter()
or a helper func such asunilog.Nul()
orunilog.StdLog()
. For any other adapters, refer to their documentation for any helper functions they may provide. - Pass your
unilog.Logger
into any modules/packages used that support it - OPTIONAL: Register any
Enrichment
functions provided by your project - To emit logs, initialise an entry with any relevant context and emit messages as required
- Enjoy your logs!
// Initialise a nul logger by default
import (
"flag"
"github.com/blugnu/unilog"
"myorg/foo"
)
// By default logs will be "redirected to nul". i.e. no log output
var logger unilog.Logger = unilog.Nul()
func main() {
// A hypothetical command flag to turn on logging.
logEnabled := true
flag.BoolVar(&logEnabled, "log", false, "turn on log output")
flag.Parse()
// If logging is enabled, replace the Nul() logger with a std log logger
if logEnabled {
logger = unilog.StdLog()
}
// Pass logger into the `foo` package, which supports unilog via an exported variable
foo.Logger = logger
// Do some logging ourselves
log := logger.NewEntry()
log.Info("logging initialised")
// Any logs written by foo.SetupTheFoo() will use the same logger as 'log'
if err := foo.SetupTheFoo(); err != nil {
// If foo uses go-errorcontext to capture context with errors, the error
// will automatically be logged with any enrichment available in the
// captured context
log.FatalError(err)
}
// ... etc
}
- Provide a mechanism for a
unilog.Logger
to be supplied for use by your module/package - OPTIONAL: Export any enrichment functions to be manually registered by projects
- OPTIONAL: Implement an
init()
function to auto-register any enrichment functions - OPTIONAL: Add log enrichment data to
context
where appropriate and useerrorcontext
to return errors with context for enriched logging - Write logs from your code using the supplied
Logger
- initialise a
unilog.Entry
in any function wishing to emit a log using eitherFromContext()
orNewEntry()
on theunilog.Logger
(depending on whether a context is accepted by or otherwise available to the function) - emit logs at the appropriate level using the
Entry
obtained
- initialise a
NOTE: You should ensure that logs are not written if no
Logger
is configured.
Either: ensure that logging statements are conditional (tedious and error prone)
or: initialise a defaultunilog.Logger
usingunilog.Nul()
.
Alternatively (recommended): treat the lack of aLogger
as an error in any initialization provided by your module, requiring applications to explicitly configure anyLogger
, includingNul()
.
-
Implement the
unilog.Adapter
interface (see below) -
OPTIONAL: provide an interface for any adapter specific configuration you wish to provide (the
Adapter
interface in unilog4logrus is an example). -
OPTIONAL (but recommended): Provide a helper function named
Logger
that accepts a logger and any additional required params that configures an adapter and returns the result ofunilog.UsingAdapter()
The following three functions are required to be implemented by an Adapter
:
function | description |
---|---|
Emit(unilog.Level, string) |
implement this function to emit logs using whichever logging package your adapter supports. Your adapter must map the unilog.Level to the corresponding level supported by the underlying logging package (or adapt the behaviour accordingly if the underlying logging package does not directly support leveled logging) |
NewEntry() Adapter |
implement this function to return a new adapter corresponding to a new log entry |
WithField(string, any) Adapter |
implement this function to return a new adapter with the supplied, named value added to any log enrichment on the receiving adapter |
The unilog4logrus adapter project provides a reference example, alongside the Nul()
and StdLog()
adapters implemented in the unilog
package itself.