英文:
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")
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论