共享库之间的集中式日志配置

huangapple go评论83阅读模式
英文:

Centralised logging configuration when sharing libraries between packages

问题

所以我正在使用一个结构化日志记录库(logrus),我有一个core包作为其他一些包的基础,让我们称这个包为me/core,然后有一些单独的包,比如me/foo-serviceme/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/coreme/foo-service都有一个logrus库的vendored版本,这些log.Set*命令修改了logrus.std变量,该变量保存了标准的全局日志记录器,但这是两个包的不同实例,因为me/core/vendor/.../logrus/stdme/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.Fieldslogrus.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")

huangapple
  • 本文由 发表于 2016年4月13日 05:38:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/36584564.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定