全局日志记录的正确方法

151

Go中应用程序日志记录的模式是什么?如果我有5个goroutine需要记录,我应该...

  • 创建单个log.Logger并传递它?
  • 传递指向该log.Logger的指针?
  • 每个goroutine或函数是否应该创建一个记录器?
  • 我应该将记录器创建为全局变量吗?
7个回答

67
  • 要创建一个单一的log.Logger并传递它吗?

这是可能的。一个log.Logger可以从多个goroutine同时使用。

  • 传递指向该log.Logger的指针吗?

log.New返回一个*Logger,这通常意味着您应该将对象作为指针传递。将其作为值传递将创建一个结构体的副本(即Logger的副本),然后多个goroutine可能会同时写入同一个io.Writer,这可能是一个严重的问题,这取决于writer的实现。

  • 每个goroutine或函数都应该创建一个logger吗?

我不会为每个函数或goroutine创建单独的logger。goroutine(和函数)用于非常轻量级的任务,将不足以证明维护单独的记录器是合理的。为项目的每个较大组件创建一个记录器可能是一个好主意。例如,如果您的项目使用SMTP服务发送邮件,为邮件服务创建一个单独的记录器似乎是一个不错的主意,以便您可以分别过滤和关闭输出。

  • 应该将记录器创建为全局变量吗?

这取决于您的包。在上一个邮件服务示例中,为每个服务实例拥有一个记录器可能是一个好主意,以便用户可以在使用gmail邮件服务时记录失败与使用本地MTA(例如sendmail)发生的失败不同。


46

对于简单的情况,日志包中定义了一个全局记录器 log.Logger。该全局记录器可以通过log.SetFlags进行配置。

之后,只需调用日志包的顶级函数,如log.Printflog.Fatalf,它们将使用该全局实例。


虽然你可以设置标志,但不能使用自定义记录器。 - 0xcaff
@caffinatedmonkey 实际上,如果它们实现了 io.Writer 接口并且您通过 SetOutput() 更改了默认记录器的输出,则可以使用自定义记录器。 - congusbongus

27

这是一个简单的记录器

package customlogger

import (
    "log"
    "os"
    "sync"
)

type logger struct {
    filename string
    *log.Logger
}

var logger *logger
var once sync.Once

// start loggeando
func GetInstance() *logger {
    once.Do(func() {
        logger = createLogger("mylogger.log")
    })
    return logger
}

func createLogger(fname string) *logger {
    file, _ := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)

    return &logger{
        filename: fname,
        Logger:   log.New(file, "My app Name ", log.Lshortfile),
    }
}

你可以这样使用它

package main

import (
    "customlogger"
    "fmt"
    "net/http"
)

func main() {
    logger := customlogger.GetInstance()
    logger.Println("Starting")

    http.HandleFunc("/", sroot)
    http.ListenAndServe(":8080", nil)
}

func sroot(w http.ResponseWriter, r *http.Request) {
    logger := customlogger.GetInstance()

    fmt.Fprintf(w, "welcome")
    logger.Println("Starting")
}

我收到了错误信息:包customlogger不在GOROOT(C:\ Program Files \ Go \ src \ customlogger)中。 - MswatiLomnyama

11

我知道这个问题有点老,但是如果像我一样,你的项目由多个较小的文件组成,我投票支持你的第四个选项 - 我创建了一个 logger.go,它是主包的一部分。这个go文件创建了日志记录器,将其分配给一个文件,并将其提供给main的其他部分。注意,我还没有想出一种优雅的方式来关闭错误日志...

package main

import (
    "fmt"
    "log"
    "os"
)

var errorlog *os.File
var logger *log.Logger

func init() {
    errorlog, err := os.OpenFile(logfile,  os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        fmt.Printf("error opening file: %v", err)
        os.Exit(1)
    }

    logger = log.New(errorlog, "applog: ", log.Lshortfile|log.LstdFlags)
}

9
为了优雅地关闭,你可以在执行结束时使用defer errorlog.Close()方法。或者为了更好地确保其被关闭,可以使用Go的signal包设置信号处理程序 https://golang.org/pkg/os/signal/。 - Anfernee

3
这是一个较旧的问题,但我想建议使用我们开发的http://github.com/romana/rlog。它通过环境变量进行配置,当导入rlog时,日志记录器对象被创建和初始化。因此,无需传递日志记录器。
rlog具有以下特点:
- 完全可配置的日期/时间戳 - 同时输出到stderr或stdout以及文件。 - 标准日志级别(调试、信息等),以及自由配置的多级日志记录。 - 按需记录调用者信息(文件、行号、函数)。 - 能够为不同的源文件设置不同的日志级别。
它非常小巧,除了标准的Golang库外,没有外部依赖项,并且正在积极开发中。示例提供在repo中。

3
谢谢您公开您与所推荐产品的关联关系!这受到赞赏。 - Robert Columbia

2

我发现默认的日志包(https://golang.org/pkg/log/)有些局限性。例如,不支持信息和调试日志。
经过一番探索后,我决定使用https://github.com/golang/glog。这似乎是https://github.com/google/glog的一个移植版本,并提供了很好的日志灵活性。例如,在本地运行应用程序时,您可能希望获得DEBUG级别的日志,但在生产中可能只想运行INFO / ERROR级别。完整功能/指南列表在此处:https://google-glog.googlecode.com/svn/trunk/doc/glog.html(它是针对c++模块的,但在很大程度上适用于golang移植版)


0

你可以考虑使用klog作为日志模块之一。它支持'V'日志级别,使得记录特定级别的日志更加灵活。

klog是glog的一个分支,克服了以下缺点:

  • glog存在许多“陷阱”,在容器化环境中引入挑战,而这些都没有得到很好的记录。
  • glog没有提供一种简单的方法来测试日志,这会影响使用它的软件的稳定性。
  • glog基于C++,而klog是纯Go语言实现的。

示例实现

package main

import (
    "flag"

    "k8s.io/klog"


)

type myError struct {
    str string
}

func (e myError) Error() string {
    return e.str
}

func main() {
    klog.InitFlags(nil)
    flag.Set("v", "1")
    flag.Parse()

    klog.Info("hello", "val1", 1, "val2", map[string]int{"k": 1})
    klog.V(3).Info("nice to meet you")
    klog.Error(nil, "uh oh", "trouble", true, "reasons", []float64{0.1, 0.11, 3.14})
    klog.Error(myError{"an error occurred"}, "goodbye", "code", -1)
    klog.Flush()
}

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