英文:
Centralised logging configuration when sharing libraries between packages
问题
所以我正在使用一个结构化日志记录库(logrus),我有一个core包作为其他一些包的基础,让我们称这个包为me/core,然后有一些单独的包,比如me/foo-service,me/bar-service等,它们使用这个核心库来共享依赖项/实用程序,比如设置、配置加载,我还希望将其用于标准化的日志记录等方面,所以我希望me/core能够为其他包配置日志记录。使用Logrus,你可以这样做:
import (
log "github.com/Sirupsen/logrus"
)
[...]
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.TextFormatter{FullTimestamp:true})
然后可以这样使用:
log.Debug("Moo")
log.WithFields(log.Fields{"structured":"data"}).Debug("I have structure data")
输出结果如下:
DEBU[2016-04-12T22:11:38+01:00] Moo
DEBU[2016-04-12T22:11:38+01:00] I have structure data structured=data
所以我想在me/foo-service包中配置这个,类似于:
import (
"me/core/logging"
)
func main(){
logging.Setup()
}
但由于各种原因,我遇到了问题。主要问题似乎是me/core和me/foo-service都有一个logrus库的vendored版本,这些log.Set*命令修改了logrus.std变量,该变量保存了标准的全局日志记录器,但这是两个包的不同实例,因为me/core/vendor/.../logrus/std和me/foo-service/vendor/.../logrus/std是不同的对象。
我尝试的第一件事是在me/core/logging中创建一个变量,我可以在父级中使用,类似于:
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")
}
对于这种简单的情况,它可以工作,例如:
package main
import (
"me/core/logging"
)
func main() {
logging.Setup(parsedOptions.Verbose)
logging.Log.Debug("Moo")
}
然而,尝试使用结构化数据Fields会导致问题,我不能使用本地vendored的logrus字段,例如:
logging.Log.WithFields(log.Fields{"data":"hi"}).Debug("test")
会得到以下错误:
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
尝试在me/core/logging中使用与之相似的字段:
type Fields log.Fields
也不起作用,因为它仍然是不同的类型:
cannot use logging.Fields literal (type logging.Fields) as type "me/core/vendor/github.com/Sirupsen/logrus".Fields in argument to logging.Log.WithFields
我也想不到任何方法将me/foo-service中的本地log.std传递给me/core,因为它也是不同的类型,由于vendored包。
我目前的解决方法是创建每个方法的镜像,在me/core/logging中有如下设置:
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)
}
但这将涉及创建每个方法的镜像,这似乎非常低效。
所以我想要一个建议,如何使me/core/vendor/.../logrus/std可用于me/foo-service,或者如果我完全错误地思考了这个问题,有更好的方法来做到这一点。
英文:
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.
答案1
得分: 1
你只能镜像 WithFields,因为其他函数只接受内置类型。另外,由于 logging.Fields 和 logrus.Fields 是相同类型,你可以更简单地这样做:
func WithFields(f Fields) log.*Entry {
return Log.WithFields(log.Fields(f))
}
然后在你的服务中,你可以调用:
logging.Log.Debug("message")
logging.WithFields(logging.Fields{"k":"v"}).Debug("message")
英文:
You can only mirror WithFields since the other ones accept built in types. Also since logging.Fields and logrus.Fields are the same type you could more simply do
func WithFields(f Fields) log.*Entry {
return Log.WithFields(log.Fields(f))
}
Then in your service, you could call
logging.Log.Debug("message")
logging.WithFields(logging.Fields{"k":"v"}).Debug("message")
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论