英文:
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()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论