How can I log in golang to a file with log rotation?

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

How can I log in golang to a file with log rotation?

问题

我正在尝试编写一个将在远程服务器上运行的Web应用程序。我需要记录以捕获错误、调试和审计信息。
我发现Go语言有多个可用的日志记录包,包括标准的"log"包。然而,我需要满足以下三个要求:

  1. 日志文件需要进行轮转。
  2. 它适用于使用"log"的其他包。
  3. 它需要跨平台。开发环境是Linux,但需要在Windows上部署。
英文:

I am trying to write a web application which will run on a remote server. I need to log to capture errors/debug/audit.
I find that multiple logging packages are available for golang including the standard "log" package. However, I need to fulfill three requirements:

  1. The log files need to be rotated
  2. It applies to the included packages
    which use "log"
  3. It needs to be cross-platform. Dev environment is
    Linux and needs to be deployed on Windows.

答案1

得分: 28

尽管@Crast给出了一个非常好的答案,但我还想提醒一下Nate Finchlumberjack日志记录器,我最终使用了它。

以下是如何使用它:

  1. 首先,克隆lumberjack存储库或以某种方式获取它。
  2. 在文件夹上运行go install命令。
  3. 现在导入go的"log"包和"lumberjack"包。
import (
    "log"
    "github.com/natefinch/lumberjack"
)
  1. 现在在代码中像这样使用它:

在main函数外部,声明日志变量。

var errLog *log.Logger

在main函数内部:

e, err := os.OpenFile("./foo.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)

if err != nil {
    fmt.Printf("error opening file: %v", err)
    os.Exit(1)
}
errLog = log.New(e, "", log.Ldate|log.Ltime)
errLog.SetOutput(&lumberjack.Logger{
    Filename:   "./foo.log",
    MaxSize:    1,  // 超过此大小(以兆字节为单位)后将创建新文件
    MaxBackups: 3,  // 备份文件的数量
    MaxAge:     28, // 天数
})

现在,一旦文件大小达到1MB,将创建一个新文件,以保留先前的日志和当前时间戳,并且新的日志将继续记录到foo.log文件中。此外,我使用os.OpenFile创建了文件,但您可能不需要它,因为lumberjack在内部执行此操作,但我更喜欢这种方式。谢谢,希望对您有所帮助。
再次感谢@Crast和NateFinch

英文:

Though @Crast has given a very good answer, I want to also bring to the notice - lumberjack logger by Nate Finch which I ended up using.

Here is how to use it:

  1. First, clone the lumberjack repository OR get it somehow.
  2. Run the go install command on the folder.
  3. Now import go's "log" package and "lumberjack package".

import (
"log"
"github.com/natefinch/lumberjack"
)

  1. Now use it in your code like this:

Outside of main, declare your log variable.

var errLog *log.Logger

Inside main:

e, err := os.OpenFile("./foo.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)

if err != nil {
	fmt.Printf("error opening file: %v", err)
	os.Exit(1)
}
errLog = log.New(e, "", log.Ldate|log.Ltime)
errLog.SetOutput(&lumberjack.Logger{
	Filename:   "./foo.log",
	MaxSize:    1,  // megabytes after which new file is created
	MaxBackups: 3,  // number of backups
	MaxAge:     28, //days
})

Now as soon as the file size get 1MB, a new file is created to keep the previous logs with the current timestamps, and the new logs will continue to log into foo.log file. Also, I have created the file using os.OpenFile but you may not need it as lumberjack internally does it, but I preferred it that way. Thanks, hope it helps.
Once again thanks to @Crast and NateFinch.

答案2

得分: 19

满足你的三个要求的最佳方法是,如果你满意使用基本级别的log.Log,而不是创建一个替代的logger结构体,那么可以将logger的输出设置为自己的io.Writer实例。

所以基本上我要做的是展示一个例子,其中我创建了自己的io.Writer:

import (
	"os"
	"sync"
	"time"
)

type RotateWriter struct {
	lock     sync.Mutex
	filename string // 应设置为实际的文件名
	fp       *os.File
}

// 创建一个新的RotateWriter。如果设置过程中发生错误,则返回nil。
func New(filename string) *RotateWriter {
	w := &RotateWriter{filename: filename}
	err := w.Rotate()
	if err != nil {
		return nil
	}
	return w
}

// 满足io.Writer接口的Write方法。
func (w *RotateWriter) Write(output []byte) (int, error) {
	w.lock.Lock()
	defer w.lock.Unlock()
	return w.fp.Write(output)
}

// 执行实际的旋转和重新打开文件的操作。
func (w *RotateWriter) Rotate() (err error) {
	w.lock.Lock()
	defer w.lock.Unlock()

	// 如果文件已打开,则关闭现有文件
	if w.fp != nil {
		err = w.fp.Close()
		w.fp = nil
		if err != nil {
			return
		}
	}
	// 如果目标文件已存在,则重命名
	_, err = os.Stat(w.filename)
	if err == nil {
		err = os.Rename(w.filename, w.filename+"."+time.Now().Format(time.RFC3339))
		if err != nil {
			return
		}
	}

	// 创建一个文件。
	w.fp, err = os.Create(w.filename)
	return
}

然后,你可以创建一个RotateWriter,并使用log.SetOutput将其设置为写入器(如果其他包正在使用标准的logger实例),或者使用log.New创建自己的实例进行传递。

我还没有解决何时调用Rotate的情况,这个决定留给你。可以根据时间触发它,或者在写入一定数量的日志或字节数后触发。

英文:

The best way to fulfill all your three requirements instead of creating an alternate logger struct, if you were satisfied using the base-level log.Log, is instead to set the output of the logger to your own io.Writer instance.

So basically what I'm going to do here is show an example where I create my own io.Writer:

import (
"os"
"sync"
"time"
)
type RotateWriter struct {
lock     sync.Mutex
filename string // should be set to the actual filename
fp       *os.File
}
// Make a new RotateWriter. Return nil if error occurs during setup.
func New(filename string) *RotateWriter {
w := &RotateWriter{filename: filename}
err := w.Rotate()
if err != nil {
return nil
}
return w
}
// Write satisfies the io.Writer interface.
func (w *RotateWriter) Write(output []byte) (int, error) {
w.lock.Lock()
defer w.lock.Unlock()
return w.fp.Write(output)
}
// Perform the actual act of rotating and reopening file.
func (w *RotateWriter) Rotate() (err error) {
w.lock.Lock()
defer w.lock.Unlock()
// Close existing file if open
if w.fp != nil {
err = w.fp.Close()
w.fp = nil
if err != nil {
return
}
}
// Rename dest file if it already exists
_, err = os.Stat(w.filename)
if err == nil {
err = os.Rename(w.filename, w.filename+"."+time.Now().Format(time.RFC3339))
if err != nil {
return
}
}
// Create a file.
w.fp, err = os.Create(w.filename)
return
}

You then create a RotateWriter and use log.SetOutput to set this writer (if other packages are using the standard logger instance) or alternately create your own instances using log.New to pass around.

I haven't solved the situation of when to call Rotate, I'll leave that to you to decide. It'd be fairly simple to trigger it based on time, or alternately do so after some amount of writes or some amount of bytes.

答案3

得分: 8

使用 logruslumberjack 插件来配置 logrus

package mypackage

import (
	"io"
	"os"
	"path/filepath"
	"time"

	log "github.com/sirupsen/logrus"
	"gopkg.in/natefinch/lumberjack.v2"
)

func SetupLogger() {

	lumberjackLogger := &lumberjack.Logger{
		// 日志文件的绝对路径,与操作系统无关
		Filename:   filepath.ToSlash("/path/to/log/file"),
		MaxSize:    5, // MB
		MaxBackups: 10,
		MaxAge:     30,   // 天数
		Compress:   true, // 默认禁用
	}

	// 将日志写入两个输出
	multiWriter := io.MultiWriter(os.Stderr, lumberjackLogger)

	logFormatter := new(log.TextFormatter)
	logFormatter.TimestampFormat = time.RFC1123Z // 或者 RFC3339
	logFormatter.FullTimestamp = true

	log.SetFormatter(logFormatter)
	log.SetLevel(log.InfoLevel)
	log.SetOutput(multiWriter)
}

在程序的早期阶段(可能在 init 函数内部)使用此函数。

在任何文件中导入 log "github.com/sirupsen/logrus" 并使用以下方式记录日志:

log.Info("some message")
英文:

With logrus and lumberjack plugin for logrus:

package mypackage
import (
"io"
"os"
"path/filepath"
"time"
log "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
)
func SetupLogger() {
lumberjackLogger := &lumberjack.Logger{
// Log file abbsolute path, os agnostic
Filename:   filepath.ToSlash("/path/to/log/file"), 
MaxSize:    5, // MB
MaxBackups: 10,
MaxAge:     30,   // days
Compress:   true, // disabled by default
}
// Fork writing into two outputs
multiWriter := io.MultiWriter(os.Stderr, lumberjackLogger)
logFormatter := new(log.TextFormatter)
logFormatter.TimestampFormat = time.RFC1123Z // or RFC3339
logFormatter.FullTimestamp = true
log.SetFormatter(logFormatter)
log.SetLevel(log.InfoLevel)
log.SetOutput(multiWriter)
}

Use this function at early stage of your program (maybe inside init).

Import log "github.com/sirupsen/logrus" in any file and log with:

log.Info("some message")

答案4

得分: 4

这是一个轻量级的日志记录包,支持日志轮转和自动清理。

https://github.com/antigloss/go/tree/master/logger

// 首先调用logger.Init来设置日志记录器
logger.Init("./log", // 指定保存日志文件的目录
400, // 指定日志目录下允许的最大日志文件数
20, // 当日志文件数超过配置的限制时,删除的日志文件数
100, // 日志文件的最大大小(以MB为单位)
false) // 是否记录Trace级别的日志
logger.Info("未找到玩家!uid=%d plid=%d cmd=%s xxx=%d", 1234, 678942, "getplayer", 102020101)
logger.Warn("协议解析失败!uid=%d plid=%d cmd=%s", 1234, 678942, "getplayer")

英文:

Here is a light-weighted logging package that supports log rotation and auto purging

https://github.com/antigloss/go/tree/master/logger

// logger.Init must be called first to setup logger
logger.Init("./log", // specify the directory to save the logfiles
400, // maximum logfiles allowed under the specified log directory
20, // number of logfiles to delete when number of logfiles exceeds the configured limit
100, // maximum size of a logfile in MB
false) // whether logs with Trace level are written down
logger.Info("Failed to find player! uid=%d plid=%d cmd=%s xxx=%d", 1234, 678942, "getplayer", 102020101)
logger.Warn("Failed to parse protocol! uid=%d plid=%d cmd=%s", 1234, 678942, "getplayer")

答案5

得分: 2

我们还可以使用一个名为https://github.com/lestrrat/go-file-rotatelogs的库来实现相同的功能。它提供了以下选项:

最大日志保存时间
日志轮转时间

它还可以与任何类型的日志记录器进行连接。

来源:https://golangbyexample.com/go-logger-rotation/

英文:

We can also use a lib https://github.com/lestrrat/go-file-rotatelogs to achieve the same. It provides option to set

Max Age
Log Rotation Time

It can be hooked to any kind of logger too.

Source: https://golangbyexample.com/go-logger-rotation/

答案6

得分: 0

一种可能的选择是将日志记录包装在自己的类型中,并提供一个重新加载函数,类似于:

type Logger struct {
    l *log.Logger
    f *os.File
    m sync.RWMutex
}

func NewLogger(fn string) (*Logger, error) {
    f, err := os.Create(fn)
    if err != nil {
        return nil, err
    }
    l := &Logger{
        l: log.New(f, "your-app", log.Lshortfile),
        f: f,
    }
    return l, nil
}

func (l *Logger) Logf(f string, args ...interface{}) {
    l.m.RLock()
    l.l.Printf(f, args...)
    l.m.RUnlock()
}

func (l *Logger) Reload() (err error) {
    l.m.Lock()
    defer l.m.Unlock()
    l.f.Close()
    if l.f, err = os.Create(l.f.Name()); err != nil {
        return
    }
    l.l = log.New(l.f, "your-app", log.Lshortfile)
    return
}

然后,要么监听一个信号(通常是在*nix上使用-HUP),要么在应用程序中添加一个端点,调用Logger.Reload()函数。

英文:

One option that comes to mind is to wrap the logging in your own type and provide a reload function, something like:

type Logger struct {
l *log.Logger
f *os.File
m sync.RWMutex
}
func NewLogger(fn string) (*Logger, error) {
f, err := os.Create(fn)
if err != nil {
return nil, err
}
l := &Logger{
l: log.New(f, "your-app", log.Lshortfile),
f: f,
}
return l, nil
}
func (l *Logger) Logf(f string, args ...interface{}) {
l.m.RLock()
l.l.Printf(f, args...)
l.m.RUnlock()
}
func (l *Logger) Reload() (err error) {
l.m.Lock()
defer l.m.Unlock()
l.f.Close()
if l.f, err = os.Create(l.f.Name()); err != nil {
return
}
l.l = log.New(l.f, "your-app", log.Lshortfile)
return
}

Then either listen for a signal (usually -HUP on *nix) or add an endpoint in your app that would call Logger.Reload().

答案7

得分: 0

https://github.com/jame2981/log 我的包可以帮助你。

l1 := log.Pool.New("l1", "file:///tmp/test1.log")
l2 := log.Pool.New("l2", "file:///tmp/test2.log")
l3 := log.Pool.New("l3", "file:///tmp/test3.log")
l4 := log.Pool.New("l4", "file:///tmp/test4.log")

l1.Rotate() // 仅旋转l1
log.Pool.Rotate() // 旋转所有实例。

// 使用信号旋转
reopen := make(chan os.Signal, 1)
signal.Notify(reopen, syscall.SIGUSR1)
go func() {
for {
<-reopen
l.Pool.Rotate()
}
}()

设置标准日志记录器的写入器,以便旋转仍然起作用。

// 标准日志记录器的写入器
import "log"
logger := log.New("test", "", 0)
logger.SetOutput(l1.Writer())

英文:

https://github.com/jame2981/log My package can help you.

l1 := log.Pool.New(&quot;l1&quot;, &quot;file:///tmp/test1.log&quot;)
l2 := log.Pool.New(&quot;l2&quot;, &quot;file:///tmp/test2.log&quot;)
l3 := log.Pool.New(&quot;l3&quot;, &quot;file:///tmp/test3.log&quot;)
l4 := log.Pool.New(&quot;l4&quot;, &quot;file:///tmp/test4.log&quot;)
l1.Rotate() // rotate l1 only
log.Pool.Rotate() // was rotate all instances.
// rotate with signal
reopen := make(chan os.Signal, 1)
signal.Notify(reopen, syscall.SIGUSR1)
go func() {
for{
&lt;-reopen
l.Pool.Rotate()
}
}()

set std logger writer so rotate work yet.

// std logger writer
import &quot;log&quot;
logger := log.New(&quot;test&quot;, &quot;&quot;, 0)
logger.SetOutput(l1.Writer())

huangapple
  • 本文由 发表于 2015年3月2日 00:32:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/28796021.html
匿名

发表评论

匿名网友

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

确定