How to get buffered logging with Golang logrus

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

How to get buffered logging with Golang logrus

问题

我正在使用github.com/sirupsen/logrus模块进行工作,并且有一个要求,即我生成的日志应该是带有缓冲的(时间戳保留)。我还会在操作开始时启动一个计时器,并且操作的各个步骤将继续向此缓冲区记录日志。

最后,如果操作中花费的总时间超过某个阈值,我将记录其他所有内容,然后丢弃缓冲区。

我只是想找出如何使用logrus优雅地实现这一点。

我尝试了类似以下的代码:

type BufferedLog struct {
  Message string
  Level   logrus.Level
  Time    time.Time
}

type BufferingFormatter struct {
  bufferMutex sync.Mutex
  buffer      []*BufferedLog
}

func (f *BufferingFormatter) Format(entry *logrus.Entry) ([]byte, error) {

  bl := &BufferedLog{
    Message: entry.Message,
    Level:   entry.Level,
    Time:    entry.Time,
  }

  f.buffer = append(f.buffer, bl)
  return nil, nil
}

func PrintBufferedLogs(buffer []*BufferedLog) {
  now := time.Now()
  for _, log := range buffer {
    if now.Sub(log.Time) >= 5 * time.Second {
      data, err := json.Marshal(log)
      if err != nil {
        logrus.Errorf("error marshaling log entry to JSON: %v", err)
        continue
      }
      logrus.Print(string(data))
    }
  }
}

func NewBufferingLogger() *logrus.Logger {
  logger := logrus.New()
  bufferingFormatter := &BufferingFormatter{buffer: make([]*BufferedLog, 0)}
  logger.Formatter = bufferingFormatter
  return logger
}

这是我的main()函数:

func main() {
  logrus.SetLevel(logrus.DebugLevel)
  logrus.SetFormatter(&logrus.TextFormatter{
    FullTimestamp: true,
  })
  bufferingLogger := NewBufferingLogger()
  bufferingLogger.SetLevel(logrus.DebugLevel)

  // 使用自定义的bufferingLogger记录一些消息
  bufferingLogger.Info("This is an informational message.")
  bufferingLogger.Warn("This is a warning message.")
  bufferingLogger.Error("This is an error message.")
  bufferingLogger.Debug("This is a debug message.")
  time.Sleep(5 * time.Second)
  PrintBufferedLogs(bufferingLogger.Formatter.(*BufferingFormatter).buffer) 
}

它生成以下输出:

INFO {"message":"This is an informational message.","level":"info","time":"2023-07-25T11:41:17.545041+05:30"} 
INFO {"message":"This is a warning message.","level":"warning","time":"2023-07-25T11:41:17.545056+05:30"} 
INFO {"message":"This is an error message.","level":"error","time":"2023-07-25T11:41:17.545059+05:30"} 
INFO {"message":"This is a debug message.","level":"debug","time":"2023-07-25T11:41:17.545061+05:30"} 

输出中仍然有残留的INFO,而且这也改变了全局的logrus行为。

我想做的是将此缓冲日志作为一个单独的记录器,并保持全局的logrus不变。

这看起来有点复杂。有更好的想法吗?

英文:

I am working with github.com/sirupsen/logrus module and have a requirement that the logs I generate should be buffered(time stamp preserved). I'll also start a timer when the operation begins and various steps of the operation will keep logging to this buffer.

At the end, if the total time spent in the operation is more than some threshold, I'll log everything else just discard the buffer.

I am just trying to find out how to elegantly implement this with logrus.

I have tried something like this:

type BufferedLog struct {
  Message string
  Level   logrus.Level
  Time    time.Time
}

type BufferingFormatter struct {
  bufferMutex sync.Mutex
  buffer      []*BufferedLog
}

func (f *BufferingFormatter) Format(entry *logrus.Entry) ([]byte, error) {

  bl := &BufferedLog{
    Message: entry.Message,
	Level:   entry.Level,
	Time:    entry.Time,
  }

  f.buffer = append(f.buffer, bl)
  return nil, nil
}

func PrintBufferedLogs(buffer []*BufferedLog) {
  now := time.Now()
  for _, log := range buffer {
	if now.Sub(log.Time) >= 5 * time.Second {
		data, err := json.Marshal(log)
		if err != nil {
			logrus.Errorf("error marshaling log entry to JSON: %v", err)
			continue
		}
		logrus.Print(string(data))
	}
  }
}

func NewBufferingLogger() *logrus.Logger {
  logger := logrus.New()
  bufferingFormatter := &BufferingFormatter{buffer: make([]*BufferedLog, 0)}
  logger.Formatter = bufferingFormatter
  return logger
}

And this is my main():

func main() {
  logrus.SetLevel(logrus.DebugLevel)
  logrus.SetFormatter(&logrus.TextFormatter{
	FullTimestamp: true,
  })
  bufferingLogger := NewBufferingLogger()
  bufferingLogger.SetLevel(logrus.DebugLevel)

  // Log some messages using the custom bufferingLogger
  bufferingLogger.Info("This is an informational message.")
  bufferingLogger.Warn("This is a warning message.")
  bufferingLogger.Error("This is an error message.")
  bufferingLogger.Debug("This is a debug message.")
  time.Sleep(5 * time.Second)
  PrintBufferedLogs(bufferingLogger.Formatter.(*BufferingFormatter).buffer) 
}

It generates this output:

INFO {"message":"This is an informational message.","level":"info","time":"2023-07-25T11:41:17.545041+05:30"} 
INFO {"message":"This is a warning message.","level":"warning","time":"2023-07-25T11:41:17.545056+05:30"} 
INFO {"message":"This is an error message.","level":"error","time":"2023-07-25T11:41:17.545059+05:30"} 
INFO {"message":"This is a debug message.","level":"debug","time":"2023-07-25T11:41:17.545061+05:30"} 

There are still lingering INFO in the output and this is changing the global logrus behavior as well.

What I am trying to do is to have this buffered logging as a separate logger and keep global logrus the way it is.

This looks like an overkill. Any better ideas ?

答案1

得分: 3

你的方法看起来是正确的,但是有几个要点被忽略了。

独立的日志记录器:你可以使用logrus.New()创建一个新的日志记录器实例,而不是更改全局的logrus日志记录器。每个logrus Logger实例都是独立的,彼此之间不会干扰。因此,你可以创建一个带有缓冲格式化程序的单独日志记录器,它不会影响全局日志记录器。

打印缓冲日志:你可以使用一个单独的方法在某些操作完成后刷新日志。该方法应该扫描缓冲的日志,并使用原始(全局)日志记录器记录它们。

使用正确的级别:你应该使用被记录日志的正确级别来记录缓冲的日志。在你的代码中,你使用了logrus.Print(),它不会尊重日志的级别。

清除缓冲区:在打印缓冲的日志后,你应该清除缓冲区,以防止在下一次刷新时出现重复的日志。

更新后的代码将类似于以下内容:

package main

import (
	"bytes"
	"fmt"
	"time"

	"github.com/sirupsen/logrus"
)

type MyFormatter struct {
}

func (f *MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	b := &bytes.Buffer{}
	fmt.Fprintf(b, "{\"Level\":\"%s\",\"Time\":\"%s\",\"Message\":\"%s\"}\n", entry.Level, entry.Time.Format(time.RFC3339), entry.Message)
	return b.Bytes(), nil
}

type BufferedLog struct {
	Message string
	Level   logrus.Level
	Time    time.Time
}

func NewBufferingLogger() *logrus.Logger {
	logger := logrus.New()
	logger.Formatter = new(MyFormatter)
	logger.Level = logrus.DebugLevel
	return logger
}

func main() {
	bufferingLogger := NewBufferingLogger()

	// 使用自定义的缓冲日志记录器记录一些消息
	bufferingLogger.Info("这是一条信息消息。")
	bufferingLogger.Warn("这是一条警告消息。")
	bufferingLogger.Error("这是一条错误消息。")
	bufferingLogger.Debug("这是一条调试消息。")
}

英文:

Your approach looks correct but a few points are missing.

Independent Loggers: Instead of changing global logrus logger, you can create a new logger instance using logrus.New(). Each instance of logrus Logger is independent and doesn't interfere with each other. Therefore, you can create a separate logger with a buffering formatter that won't impact the global logger.

Printing Buffer Logs: You can utilise a separate method to flush out logs after some operations are completed. This method should scan the buffered logs, and log them with your original (global) logger.

Using Correct Level: You should log the buffered logs with the correct level they were logged with. In your code, you used logrus.Print(), which doesn't respect the level of logs.

Clear Buffer: After printing buffered logs, you should clear the buffer to prevent duplicated logs in the next flush.

The updated code will be similar to this

package main

import (
	"bytes"
	"fmt"
	"time"

	"github.com/sirupsen/logrus"
)

type MyFormatter struct {
}

func (f *MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	b := &bytes.Buffer{}
	fmt.Fprintf(b, "{\"Level\":\"%s\",\"Time\":\"%s\",\"Message\":\"%s\"}\n", entry.Level, entry.Time.Format(time.RFC3339), entry.Message)
	return b.Bytes(), nil
}

type BufferedLog struct {
	Message string
	Level   logrus.Level
	Time    time.Time
}

func NewBufferingLogger() *logrus.Logger {
	logger := logrus.New()
	logger.Formatter = new(MyFormatter)
	logger.Level = logrus.DebugLevel
	return logger
}

func main() {
	bufferingLogger := NewBufferingLogger()

	// Log some messages using the custom bufferingLogger
	bufferingLogger.Info("This is an informational message.")
	bufferingLogger.Warn("This is a warning message.")
	bufferingLogger.Error("This is an error message.")
	bufferingLogger.Debug("This is a debug message.")
}

答案2

得分: 0

根据@ekundayo-blessing-funminiyi的答案,我稍微修改了他的代码,得到了以下结果:

var buflogger *BufferedLogger

type BufferedLog struct {
    Message     string          `json:"message"`
    Level       logrus.Level    `json:"level"`
    Time        time.Time       `json:"time"`
    TimeSpent   time.Time       `json:"time_spent,omitempty"`
}

type BufferingFormatter struct {
    bufferMutex sync.Mutex
    buffer []*BufferedLog
}

func init() {
    logrus.SetLevel(logrus.DebugLevel)
    logrus.SetFormatter(&logrus.JSONFormatter{})
    once := sync.Once{}
    once.Do(func() {
        buflogger = NewBufferingLogger()
    })
}

func (f *BufferingFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    f.bufferMutex.Lock()
    defer f.bufferMutex.Unlock()

    bl := &BufferedLog{
        Message: entry.Message,
        Level:   entry.Level,
        Time:    entry.Time.UTC(),
    }

    f.buffer = append(f.buffer, bl)
    return nil, nil
}

func (f *BufferingFormatter) PrintBufferedLogs() {
    f.bufferMutex.Lock()
    defer f.bufferMutex.Unlock()

    dumpAll := false
    now := time.Now()
    for _, log := range f.buffer {
        if dumpAll || (now.Sub(log.Time) >= 5*time.Minute) {
            dumpAll = true
            logrus.WithTime(log.Time).Log(log.Level, log.Message)
        }
    }

    // Flush the buffer to avoid memory leaks
    f.buffer = nil
}

func NewBufferingLogger() *BufferedLogger {
    if buflogger != nil {
        return buflogger
    }

    bufferingFormatter := &BufferingFormatter{buffer: make([]*BufferedLog, 0)}
    logger := logrus.New()
    logger.Level = logrus.DebugLevel
    logger.Formatter = bufferingFormatter
    buflogger =  &BufferedLogger{
        Logger:             logger,
        bufferingFormatter: bufferingFormatter,
    }
    return buflogger
}

type BufferedLogger struct {
    *logrus.Logger
    bufferingFormatter *BufferingFormatter
}

func (bl *BufferedLogger) PrintBufferedLogs() {
    bl.bufferingFormatter.PrintBufferedLogs()
}

这段代码是一个用于缓冲日志的实现。它定义了BufferedLog结构体来表示缓冲的日志条目,BufferingFormatter结构体来格式化日志并将其缓冲起来。BufferedLogger结构体是一个包装了logrus.Logger的类型,它使用BufferingFormatter来格式化和缓冲日志。PrintBufferedLogs函数用于打印并清空缓冲区中的日志条目。

init函数中,代码设置了日志级别和格式化器,并创建了一个全局的BufferedLogger实例。

你可以根据需要使用这些代码来实现缓冲日志功能。

英文:

Based on @ekundayo-blessing-funminiyi 's answer I modified his code a bit to get this

var buflogger *BufferedLogger
type BufferedLog struct {
Message		string			`json:"message"`
Level		logrus.Level	`json:"level"`
Time		time.Time		`json:"time"`
TimeSpent	time.Time		`json:"time_spent,omitempty"`
}
type BufferingFormatter struct {
bufferMutex sync.Mutex
buffer []*BufferedLog
}
func init() {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetFormatter(&logrus.JSONFormatter{})
once := sync.Once{}
once.Do(func() {
buflogger = NewBufferingLogger()
})
}
func (f *BufferingFormatter) Format(entry *logrus.Entry) ([]byte, error) 
{
f.bufferMutex.Lock()
defer f.bufferMutex.Unlock()
bl := &BufferedLog{
Message: entry.Message,
Level:   entry.Level,
Time:    entry.Time.UTC(),
}
f.buffer = append(f.buffer, bl)
return nil, nil
}
func (f *BufferingFormatter)PrintBufferedLogs() {
f.bufferMutex.Lock()
defer f.bufferMutex.Unlock()
dumpAll := false
now := time.Now()
for _, log := range f.buffer {
if dumpAll || (now.Sub(log.Time) >= 5*time.Minute) {
dumpAll = true
logrus.WithTime(log.Time).Log(log.Level, log.Message)
}
}
// Flush the buffer to avoid memory leaks
f.buffer = nil
}
func NewBufferingLogger() *BufferedLogger {
if buflogger != nil {
return buflogger
}
bufferingFormatter := &BufferingFormatter{buffer: make([]*BufferedLog, 0)}
logger := logrus.New()
logger.Level = logrus.DebugLevel
logger.Formatter = bufferingFormatter
buflogger =  &BufferedLogger{
Logger:             logger,
bufferingFormatter: bufferingFormatter,
}
return buflogger
}
type BufferedLogger struct {
*logrus.Logger
bufferingFormatter *BufferingFormatter
}
func (bl *BufferedLogger) PrintBufferedLogs() {
bl.bufferingFormatter.PrintBufferedLogs()
}

huangapple
  • 本文由 发表于 2023年7月25日 14:16:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76759898.html
匿名

发表评论

匿名网友

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

确定