Correct approach to global logging

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

Correct approach to global logging

问题

在Go语言中,应用程序日志记录的模式有几种选择:

  1. 创建一个单独的log.Logger实例,并在需要记录日志的地方传递它。
  2. 传递一个指向log.Logger的指针,在需要记录日志的地方使用该指针。
  3. 每个goroutine或函数创建一个日志记录器。
  4. 将日志记录器作为全局变量创建。

这些选择取决于你的具体需求和偏好。如果你希望在多个goroutine之间共享相同的日志记录器,可以选择第一种或第二种方法。如果你希望每个goroutine或函数都有自己的日志记录器,可以选择第三种方法。如果你希望在整个应用程序中都能够访问同一个日志记录器,可以选择第四种方法。

英文:

What's the pattern for application logging in Go? If I've got, say, 5 goroutines I need to log from, should I...

  • Create a single log.Logger and pass it around?
  • Pass around a pointer to that log.Logger?
  • Should each goroutine or function create a logger?
  • Should I create the logger as a global variable?

答案1

得分: 67

> * 创建一个单独的log.Logger并传递它?

这是可能的。log.Logger可以从多个goroutine并发使用。

> * 传递一个指向log.Logger的指针?

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

> * 每个goroutine或函数是否应该创建一个日志记录器?

我不会为每个函数或goroutine创建一个单独的日志记录器。Goroutine(和函数)用于非常轻量级的任务,不会为维护单独的日志记录器而付出代价。对于项目的每个较大组件,创建一个日志记录器可能是个好主意。例如,如果你的项目使用SMTP服务发送邮件,为邮件服务创建一个单独的日志记录器听起来是个好主意,这样你可以单独过滤和关闭输出。

> * 我应该将日志记录器创建为全局变量吗?

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

英文:

> * Create a single log.Logger and pass it around?

That is possible. A log.Logger can be used concurrently from multiple goroutines.

> * Pass around a pointer to that log.Logger?

log.New returns a *Logger which is usually an indication that you should pass the object around as a pointer. Passing it as value would create a copy of the struct (i.e. a copy of the Logger) and then multiple goroutines might write to the same io.Writer concurrently. That might be a serious problem, depending on the implementation of the writer.

> * Should each goroutine or function create a logger?

I wouldn't create a separate logger for each function or goroutine. Goroutines (and functions) are used for very lightweight tasks that will not justify the maintenance of a separate logger. It's probably a good idea to create a logger for each bigger component of your project. For example, if your project uses a SMTP service for sending mails, creating a separate logger for the mail service sounds like a good idea so that you can filter and turn off the output separately.

> * Should I create the logger as a global variable?

That depends on your package. In the previous mail service example, it would be probably a good idea to have one logger for each instance of your service, so that users can log failures while using the gmail mail service differently than failures that occured while using the local MTA (e.g. sendmail).

答案2

得分: 45

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

之后,可以直接调用日志包的顶层函数,如 log.Printflog.Fatalf,它们会使用这个全局实例。

英文:

For simple cases, there is a global logger defined in the log package, log.Logger. This global logger can be configured through log.SetFlags.

Afterwards one can just call the top level functions of the log package like log.Printf and log.Fatalf, which use that global instance.

答案3

得分: 27

这是一个简单的日志记录器。

package customlogger

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

type logger struct {
	filename string
	*log.Logger
}

var logger *logger
var once sync.Once

// 获取日志记录器实例
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")
}
英文:

This is a simple logger

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),
	}
}

You can use it in this way

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")
}

答案4

得分: 11

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

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)
}
英文:

I know this question is a bit old, but if, like me, your projects are made up of multiple smaller files I vote for your 4th option - I've created a logger.go that is part of package main. This go file creates the logger, assigns it to a file, and provides it to the rest of main. Note I have not come up with a graceful way to close errorlog...

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)
}

答案5

得分: 3

这是一个较旧的问题,但我想建议使用http://github.com/romana/rlog(我们开发的)。它通过环境变量进行配置,当导入rlog时,会创建和初始化日志记录器对象。因此,不需要传递日志记录器。

rlog具有以下几个特点:

  • 完全可配置的日期/时间戳
  • 同时输出到stderr或stdout以及文件。
  • 标准日志级别(调试、信息等)以及自由配置的多级日志记录。
  • 按需记录调用者信息(文件、行号、函数)。
  • 能够为不同的源文件设置不同的日志级别。

它非常小巧,除了标准的Golang库外,没有外部依赖,并且正在积极开发中。示例代码可以在该代码库中找到。

英文:

This is an older question, but I would like to suggest the use of http://github.com/romana/rlog (which we developed). It is configured through environment variables, the logger object is created and initialized when rlog is imported. Therefore, no need to pass around a logger.

rlog has quite a few features:

  • Fully configurable date/time stamps
  • Simultaneous output to stderr or stdout as well as file.
  • Standard log levels (Debug, Info, etc.) as well as freely-configurable multi-level logging.
  • On demand logging of caller info (file, line number, function).
  • Ability to set different log levels for different source files.

It is very small, has no external dependencies, except the standard Golang library and is actively being developed. Examples are provided in the repo.

答案6

得分: 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移植版)。

英文:

I found the default log package (https://golang.org/pkg/log/) somewhat limiting. For example, no support for info vs. debug logs.
After some poking around, settled on using https://github.com/golang/glog . This seems to be a port of https://github.com/google/glog and gives decent flexibility in logging. For example when running an application locally you may want DEBUG level log but might want to run only in INFO/ERROR level in production. The list of full features/guide is, here https://google-glog.googlecode.com/svn/trunk/doc/glog.html (Its for the c++ module, but for the most part translates to the golang port)

答案7

得分: 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()
}
英文:

One of the logging module that you can consider is klog . It support 'V' logging which gives the flexibility to log at certain level

klog is a fork of glog and overcomes following drawbacks

  • glog presents a lot "gotchas" and introduces challenges in containerized environments, all of which aren't well documented.
  • glog doesn't provide an easy way to test logs, which detracts from the stability of software using it
  • glog is C++ based and klog is a pure golang implementation

Sample Implementation

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()
}

huangapple
  • 本文由 发表于 2013年8月21日 23:35:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/18361750.html
匿名

发表评论

匿名网友

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

确定