全局记录日志(跨包)

17

我已经花费了很多时间搜索和阅读有关此主题的帖子,但尚未完全回答我的问题 - 或者可能只是需要更多现有答案的澄清。

我已经阅读了这篇与我的标题相同的帖子,但它是关于在go协程而不是包之间进行日志记录。

我试图解决的是跨主应用程序和任何使用的包进行日志记录。我需要一个可以记录到多个位置的记录器(可以使用io.MultiWriter完成),并且可以执行log.Error()和log.Debug()等操作。

我知道有些软件包可以做到这一点,我也知道如何自己实现这些功能。

我无法理解的是如何正确地将其与我的软件包一起使用。

一种方法当然是在main中创建记录器,然后将其传递给所有需要日志记录的函数。 但这似乎很笨拙。

我理想的解决方案是拥有像日志包内置的全局记录器一样的记录器,但具有上述添加功能。

我主要想要这个是为了在包内部进行可选的调试日志记录,以便在需要时可以在生产版本中打开它。

这样做的正确方法是什么?


为什么不在每个包中使用全局日志记录器变量,并在主包中创建/设置它们呢? - Ainar-G
1
也许你可以尝试一下 https://github.com/golang/glog 。虽然它没有 Debug,但有 Info、Warning、Error 和 Fatal。它还允许详细程度日志记录。嗯,但它不允许设置写入器。哦,算了!值得一提。 - ANisus
5个回答

8

正确的方式就是你认为理想的方式。只需创建一个包(最好遵循Go的约定https://golang.org/doc/code.html),并将您的日志设置为全局变量:

package mylog

// Define your custom logger type.
type logger struct { /* Whatever you want */ }

// Optionally make it a interface.
type Logger interface { /* Your functions */ }

// And just go global.
var defaultLogger *Logger

func init(){
   defaultLogger = new(logger)
}

func Debug(params ...string){
   // Have some fun.
}

// ...

我建议在文档中描述您的项目使用的日志记录功能。

8

我在这里发布了适用于我的解决方案!我刚创建了自己的软件包,并使用了 init 函数。

package logging

import (
    "io"
    logging "log"
    "os"

    "github.com/Sirupsen/logrus"
)

var (
    log *logrus.Logger
)

func init() {
    f, err := os.OpenFile("logs/application.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

    if err != nil {
        logging.Fatalf("error opening file: %v", err)
    }

    log = logrus.New()

    //log.Formatter = &logrus.JSONFormatter{}

    log.SetReportCaller(true)

    mw := io.MultiWriter(os.Stdout, f)
    log.SetOutput(mw)
}

// Info ...
func Info(format string, v ...interface{}) {
    log.Infof(format, v...)
}

// Warn ...
func Warn(format string, v ...interface{}) {
    log.Warnf(format, v...)
}

// Error ...
func Error(format string, v ...interface{}) {
    log.Errorf(format, v...)
}

var (

    // ConfigError ...
    ConfigError = "%v type=config.error"

    // HTTPError ...
    HTTPError = "%v type=http.error"

    // HTTPWarn ...
    HTTPWarn = "%v type=http.warn"

    // HTTPInfo ...
    HTTPInfo = "%v type=http.info"
)

在任何包中,只需导入我的包并执行(Info、Warn、Error)函数即可。

package main

import (

    log "logging"
)

func main() {
    log.Error(log.ConfigError, "Testing the error")
}

该日志条目将在屏幕上呈现并保存在文件中。


你为什么使用github.com/Sirupsen/logrus而不是Go的常规日志记录包? - JeyJ
log.SetReportCaller(true) 不会报告正确的调用者吗? - Wayne

6

@CedmundoMartinez的回答让我头疼了一阵,但也因此想到了(非常简单和相当显而易见,只要我能使用后视镜)的答案。

我在这里发布我的答案,供任何有兴趣寻找类似解决方案的人参考。

我所做的是复制标准日志包(src/log/log.go)并对其进行扩展。通过这种方式,很容易获得一个全局记录器,它已经实现了标准记录器的所有功能,并且可以添加你想要的其他任何功能!在这种情况下,支持级别化记录。

我所做的唯一修改:

type Logger struct {
    mu     sync.Mutex // ensures atomic writes; protects the following fields
    prefix string     // prefix to write at beginning of each line
    flag   int        // properties
    out    io.Writer  // destination for output
    buf    []byte     // for accumulating text to write
    level  int        // One of DEBUG, ERROR, INFO
}

只有最后一行是新增的。日志包设置了全局变量std,可以从包中的任何函数中访问结构字段。

接下来,我添加了不同日志级别的常量:

const (
    DEBUG = 1 << iota
    INFO
    ERROR
)

接下来,我添加了我的功能:

(注意:ct是包https://github.com/seago/go-colortext,它允许在Windows上对控制台文本进行着色。因此,这里的所有错误都会以红色打印出来)

func Error(v ...interface{}) {
    if std.level <= ERROR {
        ct.ChangeColor(ct.Red, true, ct.None, false)
        s := fmt.Sprintf("ERROR: %v", v...)
        std.Output(2, s)
        ct.ResetColor()
    }
}

func Info(format string, v ...interface{}) {
    if std.level <= INFO {
        s := fmt.Sprintf("INFO: "+format, v...)
        std.Output(2, s)
    }
}

func Debug(v ...interface{}) {
    if std.level <= DEBUG {
        s := fmt.Sprintf("DEBUG: %v", v...)
        std.Output(2, s)
    }
}

func SetLogLevel(lvl int) {
    std.level = lvl
}

就这样!现在我只需导入修改后的包而不是标准日志包,就可以使用它记录日志了:

import (
    "errors"
    "tryme/log" 
)

func main() {
    log.SetLogLevel(log.INFO)
    log.Info("This is a test Info")
    err := errors.New("This is a test error!!!")
    log.Error(err)
    log.Debug("Testing debugging") // won't be printed with log.INFO
}

当然,这只是一个演示,可以通过添加更多的日志级别、输出格式等来进一步扩展。

您可以使用标准日志包提供的所有函数,例如SetOutput将日志写入文件或MultiWriter将日志写入文件和控制台等。


3
为什么你要复制整个日志记录器而不是将 Logger 嵌入到一个新的结构体中,然后也实现 ErrorInfo 等方法呢?复制所有代码似乎有点过于冗长了…… - rpkamp
因为我想要一个全局记录器。如果我自己创建了一种类型并嵌入标准记录器,那么我就必须包装日志包的所有函数(那些不是方法的函数)才能在全局访问它们。而那将比我上面所做的更加繁琐,但我不知道还有其他的方法可以做到这一点。也许我遗漏了什么,请指点我正确的方向。 - IamNaN
1
我以为只需嵌入 log.Logger 就可以轻松解决问题,但我刚试了一下,你会遇到一个麻烦,即 log.Logger 的字段不被导出,因此您无法引用它们。要解决这个问题,您需要跳过很多障碍,而且确实不值得。就像您现在正在做的那样,将其全部复制过去似乎是最好的解决方案。 - rpkamp

1
创建一个Logger的包装器,并将logger变量公开给所有其他包。
package logger

import (
   "io"
   logging "log"
   "os"

   "github.com/sirupsen/logrus"
)

var (
   Log *logrus.Logger // share will all packages
)

func init() {
   // the file needs to exist prior
   f, err := os.OpenFile("logs/application.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

   if err != nil {
      // use go's logger, while we configure Logrus
      logging.Fatalf("error opening file: %v", err)
   }

   // configure Logrus
   Log = logrus.New()
   Log.Formatter = &logrus.JSONFormatter{}
   Log.SetReportCaller(true)

   mw := io.MultiWriter(os.Stdout, f)
   Log.SetOutput(mw)
 }

 // use the pre-configured Logrus, using the public variable
 package main
 
 import (
   "logger" // import our Logrus wrapper
 )

 func main() {
    logger.Log.Info("Info") // use the wrapper and public configured Logrus
    logger.Log.Error("Error")
 }

1
如果您想在多个Go应用程序上进行日志记录,可以使用RPC并创建一个日志记录服务。这将是一个独立的应用程序,为其他应用程序提供服务。Golang有自己的包可用于此

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接