Golang异步协程调用中Logger阻塞问题解决方案

Golang异步协程调用中Logger阻塞问题解决方案 我的错误日志函数如下,它返回一个 *logger。

func StartLogs(logFile string) *log.Logger {
    // 打开文件,如果不存在则创建
    file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    logger := log.New(file, "Custom Log", log.LstdFlags)
    return logger
}

我像这样使用它。

myLogger:=StartLogs('logfile.log')
go DoSomething(user.Filename, myLogger)

现在,*logger 会阻止 goroutine 异步运行。 有什么方法可以执行这个操作吗?


更多关于Golang异步协程调用中Logger阻塞问题解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你好,@Arshpreet_Singh,欢迎来到论坛。我不太确定你所说的 goroutine 无法异步运行具体指什么,但我发现你的 StartLogs 函数存在一个潜在问题:它推迟了日志文件的关闭。这意味着当 StartLogs 的调用者获得返回的 logger 时,logger 的目标文件已经关闭了,我不确定之后尝试写入日志消息时会发生什么。我建议将你的 StartLogs 函数修改为类似这样:

func StartLogs(logFile strinf) (logger *log.Logger, closer func() error) {
    // existing code here, but don't defer closing the file
    return logger, f.Close
}

然后,你可以在调用 StartLogs 的地方推迟调用返回的关闭函数:

logger, closer := StartLogs(logFilename)
defer closer()
// use logger

更多关于Golang异步协程调用中Logger阻塞问题解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在异步协程中使用同步Logger确实会导致阻塞问题。以下是几种解决方案:

方案1:使用带缓冲的通道实现异步日志

type AsyncLogger struct {
    logger *log.Logger
    ch     chan string
}

func NewAsyncLogger(logFile string) *AsyncLogger {
    file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }
    
    logger := log.New(file, "Custom Log", log.LstdFlags)
    al := &AsyncLogger{
        logger: logger,
        ch:     make(chan string, 1000), // 缓冲大小根据需求调整
    }
    
    go al.process()
    return al
}

func (al *AsyncLogger) process() {
    for msg := range al.ch {
        al.logger.Println(msg)
    }
}

func (al *AsyncLogger) Log(msg string) {
    select {
    case al.ch <- msg:
        // 成功写入通道
    default:
        // 通道满时的处理策略,例如丢弃或写入备用日志
        fmt.Println("Log buffer full, dropping message:", msg)
    }
}

func (al *AsyncLogger) Close() {
    close(al.ch)
}

// 使用示例
func main() {
    logger := NewAsyncLogger("logfile.log")
    defer logger.Close()
    
    go DoSomething(user.Filename, logger)
}

方案2:使用sync.Pool减少锁竞争

type BufferedLogger struct {
    logger *log.Logger
    mu     sync.Mutex
    pool   sync.Pool
}

func NewBufferedLogger(logFile string) *BufferedLogger {
    file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }
    
    logger := log.New(file, "Custom Log", log.LstdFlags)
    
    return &BufferedLogger{
        logger: logger,
        pool: sync.Pool{
            New: func() interface{} {
                return &bytes.Buffer{}
            },
        },
    }
}

func (bl *BufferedLogger) Log(format string, args ...interface{}) {
    go func() {
        buf := bl.pool.Get().(*bytes.Buffer)
        buf.Reset()
        defer bl.pool.Put(buf)
        
        fmt.Fprintf(buf, format, args...)
        
        bl.mu.Lock()
        bl.logger.Println(buf.String())
        bl.mu.Unlock()
    }()
}

// 使用示例
func DoSomething(filename string, logger *BufferedLogger) {
    logger.Log("Processing file: %s", filename)
}

方案3:使用现有的异步日志库

// 使用zerolog示例
import "github.com/rs/zerolog"

func SetupAsyncLogger() zerolog.Logger {
    file, err := os.OpenFile("logfile.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }
    
    // 创建异步writer
    asyncWriter := zerolog.AsyncWriter(file)
    
    return zerolog.New(asyncWriter).With().
        Timestamp().
        Str("service", "myapp").
        Logger()
}

// 使用示例
func main() {
    logger := SetupAsyncLogger()
    
    go func() {
        logger.Info().Str("filename", user.Filename).Msg("Processing file")
    }()
}

方案4:批量写入优化

type BatchLogger struct {
    logger     *log.Logger
    batchSize  int
    batch      []string
    mu         sync.Mutex
    flushTimer *time.Ticker
}

func NewBatchLogger(logFile string, batchSize int, flushInterval time.Duration) *BatchLogger {
    file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }
    
    logger := log.New(file, "Custom Log", log.LstdFlags)
    
    bl := &BatchLogger{
        logger:     logger,
        batchSize:  batchSize,
        batch:      make([]string, 0, batchSize),
        flushTimer: time.NewTicker(flushInterval),
    }
    
    go bl.autoFlush()
    return bl
}

func (bl *BatchLogger) Log(msg string) {
    bl.mu.Lock()
    bl.batch = append(bl.batch, msg)
    
    if len(bl.batch) >= bl.batchSize {
        bl.flush()
    }
    bl.mu.Unlock()
}

func (bl *BatchLogger) flush() {
    if len(bl.batch) == 0 {
        return
    }
    
    var sb strings.Builder
    for _, msg := range bl.batch {
        sb.WriteString(msg)
        sb.WriteString("\n")
    }
    
    bl.logger.Print(sb.String())
    bl.batch = bl.batch[:0]
}

func (bl *BatchLogger) autoFlush() {
    for range bl.flushTimer.C {
        bl.mu.Lock()
        bl.flush()
        bl.mu.Unlock()
    }
}

func (bl *BatchLogger) Close() {
    bl.flushTimer.Stop()
    bl.mu.Lock()
    bl.flush()
    bl.mu.Unlock()
}

选择哪种方案取决于具体需求:

  • 方案1适合需要完全异步且不丢失日志的场景
  • 方案2适合高并发但允许少量锁竞争的场景
  • 方案3适合希望使用成熟解决方案的场景
  • 方案4适合需要减少磁盘I/O次数的场景
回到顶部