So I'm using a structured logging library (logrus), and I have a core
package used as a base for some other packages, lets call this package me/core
, then individual packages like me/foo-service
, me/bar-service
etc. that use this core library for common dependencies/utilities such as setup, configuration loading, and I also wanted to use it for standardized things like logging, so I want me/core
to be able to configure logging for the other packages, with Logrus you can do things like
import(
log "github.com/Sirupsen/logrus"
)
[...]
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.TextFormatter{FullTimestamp:true})
Then do;
log.Debug("Moo")
log.WithFields(log.Fields{"structured":"data"}).Debug("I have structure data")
getting an output like
> DEBU[2016-04-12T22:11:38+01:00] Moo
> DEBU[2016-04-12T22:11:38+01:00] I have structure data structured=data
So I want to configure this in my me/foo-service
package with something like
import(
"me/core/logging"
)
func main(){
logging.Setup()
}
Only for various reasons I'm running into issues. The primary issue seems to be that both me/core
, and me/foo-service
have a vendored version of the logrus
library, those log.Set*
commands modify the variable logrus.std
logger which holds the standard, global logger, but this is a seperate instance for both packages because me/core/vendor/.../logrus/std
and me/foo-service/vendor/.../logrus/std
are different objects.
First thing I tried was creating a variable in me/core/logging
that I could use in the parent, so something like
package logging
import(
log "github.com/Sirupsen/logrus"
)
var Log *log.Logger
func Setup(verbose bool){
Log = log.New()
if verbose{
Log.Level = log.DebugLevel
} else {
Log.Level = log.InfoLevel
}
Log.Formatter = &log.TextFormatter{FullTimestamp:true}
Log.Debug("Logging verbosely")
}
and that works for simple cases like this
package main
import(
"me/core/logging"
)
func main() {
logging.Setup(parsedOptions.Verbose)
logging.Log.Debug("Moo")
}
However trying to use the structured data Fields
causes problems, I can't use the local vendored logrus
fields like
logging.Log.WithFields(log.Fields{"data":"hi"}).Debug("test")
as I get
cannot use "me/foo-service/vendor/github.com/Sirupsen/logrus".Fields literal (type "me/foo-service/vendor/github.com/Sirupsen/logrus".Fields) as type "me/core/vendor/github.com/Sirupsen/logrus".Fields in argument to logging.Log.WithFields
And trying to mirror the field in me/core/logging
with something like
type Fields log.Fields
Doesn't work ether because it's still a different type
cannot use logging.Fields literal (type logging.Fields) as type "me/core/vendor/github.com/Sirupsen/logrus".Fields in argument to logging.Log.WithFields
I also can't think of any way to pass my local log.std
from me/foo-service
to me/core
as it's also a different type due to the vendored package.
My work around for the moment involves creating a mirror of every method, so in me/core/logging
I have a set up like
package logging
import(
log "github.com/Sirupsen/logrus"
)
var Log *log.Logger
type Fields map[string]interface{}
func Setup(verbose bool){
Log = log.New()
if verbose{
Log.Level = log.DebugLevel
} else {
Log.Level = log.InfoLevel
}
Log.Formatter = &log.TextFormatter{FullTimestamp:true}
Log.Debug("Logging verbosely")
}
func Debug(msg interface{}){
Log.Debug(msg)
}
func WithFields(fields Fields) *log.Entry{
lf := log.Fields{}
for k,v := range fields{
lf[k] = v
}
return Log.WithFields(lf)
}
But that would involve creating a mirror for every method which seems really inefficient.
So I'd like a suggestion for how I can make me/core/vendor/.../logrus/std
avaliable to me/foo-service
, or if I'm thinking about this in completely the wrong way a better way to do it.