Golang错误日志记录的最佳实践与实现

Golang错误日志记录的最佳实践与实现 我正在用Go语言编写一个REST服务器,想知道将错误记录到/var/log/myservice.log文件的正确方法是什么。具体来说,对于一个每分钟可能接收数千个请求的服务,在Go语言中我是否需要关注某种缓冲机制或其他注意事项?

我是否可以打开单个文件句柄并在不同线程间传递以供写入,还是需要采用其他处理方式?

4 回复

log 包可以安全地从多个 Go 协程中调用。

更多关于Golang错误日志记录的最佳实践与实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的解释。我现在已经让日志记录器运行起来了,看起来工作得很好。显然我不会为每笔交易都写日志,只在出现问题时才记录,所以这应该不会造成太大影响。

再次感谢。

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

func doLog(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello world")
	log.Println(r.RemoteAddr, r.RequestURI)
}

func noLog(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello world")
}

func main() {
	f, err := os.Create("test.log")
	if err != nil {
		log.Fatalln(err)
	}
	defer f.Close()
	log.SetOutput(f)

	http.HandleFunc("/", noLog)
	http.HandleFunc("/log", doLog)
	fmt.Println(http.ListenAndServe(":8080", nil))
}

1000次请求 100并发

ab -n 1000 -c 100 http://localhost:8080/ 每秒请求数:5188.09 [#/秒] (平均值)

ab -n 1000 -c 100 http://localhost:8080/log 每秒请求数:4097.35 [#/秒] (平均值)

日志记录会占用一些时间,但相对于其他操作(如SQL访问等)来说并不算多。

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

func doLog(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello world")
	log.Println(r.RemoteAddr, r.RequestURI)
}

func noLog(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello world")
}

func main() {
	f, err := os.Create("test.log")
	if err != nil {
		log.Fatalln(err)
	}

	w := bufio.NewWriterSize(f, 1024*65)

	defer f.Close()
	log.SetOutput(w)

	http.HandleFunc("/", noLog)
	http.HandleFunc("/log", doLog)
	fmt.Println(http.ListenAndServe(":8080", nil))
}

通过给写入器添加缓冲区,差异几乎为零。但是这样你会以更大的块获取日志,如果你想实时查看日志可能会成为问题。

在Go语言中实现高性能的错误日志记录,特别是针对高并发REST服务,确实需要考虑多个关键因素。以下是基于标准库log和缓冲机制的实现方案:

核心实现代码

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "sync"
    "time"
)

// BufferedLogger 带缓冲的日志记录器
type BufferedLogger struct {
    file   *os.File
    writer *bufio.Writer
    mutex  sync.Mutex
}

// NewBufferedLogger 创建新的缓冲日志记录器
func NewBufferedLogger(filePath string, bufferSize int) (*BufferedLogger, error) {
    file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return nil, fmt.Errorf("failed to open log file: %v", err)
    }

    writer := bufio.NewWriterSize(file, bufferSize)
    
    return &BufferedLogger{
        file:   file,
        writer: writer,
    }, nil
}

// LogError 记录错误信息
func (bl *BufferedLogger) LogError(err error, context string) {
    bl.mutex.Lock()
    defer bl.mutex.Unlock()
    
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    logEntry := fmt.Sprintf("[%s] ERROR: %s - Context: %s\n", timestamp, err.Error(), context)
    
    _, writeErr := bl.writer.WriteString(logEntry)
    if writeErr != nil {
        log.Printf("Failed to write log entry: %v", writeErr)
    }
}

// Flush 强制刷新缓冲区到文件
func (bl *BufferedLogger) Flush() error {
    bl.mutex.Lock()
    defer bl.mutex.Unlock()
    return bl.writer.Flush()
}

// Close 关闭日志记录器
func (bl *BufferedLogger) Close() error {
    if err := bl.Flush(); err != nil {
        return err
    }
    return bl.file.Close()
}

// 全局日志记录器实例
var globalLogger *BufferedLogger

func init() {
    var err error
    globalLogger, err = NewBufferedLogger("/var/log/myservice.log", 64*1024) // 64KB缓冲区
    if err != nil {
        log.Fatalf("Failed to initialize logger: %v", err)
    }
}

// 在REST处理器中使用
func handleRequest() {
    // 模拟业务逻辑
    err := someBusinessLogic()
    if err != nil {
        globalLogger.LogError(err, "handleRequest - user processing")
    }
}

func someBusinessLogic() error {
    return fmt.Errorf("database connection failed")
}

并发安全的多goroutine使用示例

package main

import (
    "net/http"
    "sync"
)

func main() {
    // 启动定期刷新缓冲区的goroutine
    go func() {
        ticker := time.NewTicker(30 * time.Second)
        defer ticker.Stop()
        
        for range ticker.C {
            if err := globalLogger.Flush(); err != nil {
                log.Printf("Failed to flush logs: %v", err)
            }
        }
    }()

    http.HandleFunc("/api/users", usersHandler)
    http.HandleFunc("/api/orders", ordersHandler)
    
    log.Println("Server starting on :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        globalLogger.LogError(err, "HTTP server failed")
    }
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
    var wg sync.WaitGroup
    
    // 模拟并发处理
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(userID int) {
            defer wg.Done()
            
            err := processUser(userID)
            if err != nil {
                globalLogger.LogError(err, fmt.Sprintf("usersHandler - userID: %d", userID))
            }
        }(i)
    }
    
    wg.Wait()
    w.WriteHeader(http.StatusOK)
}

func ordersHandler(w http.ResponseWriter, r *http.Request) {
    err := validateOrder(r)
    if err != nil {
        globalLogger.LogError(err, "ordersHandler - validation")
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    w.WriteHeader(http.StatusOK)
}

func processUser(userID int) error {
    if userID%7 == 0 { // 模拟错误条件
        return fmt.Errorf("user processing failed for ID: %d", userID)
    }
    return nil
}

func validateOrder(r *http.Request) error {
    return fmt.Errorf("invalid order data")
}

关键设计要点

  1. 单文件句柄共享:使用单个BufferedLogger实例,通过sync.Mutex确保线程安全
  2. 缓冲写入bufio.Writer提供64KB缓冲区,减少磁盘I/O操作
  3. 定期刷新:后台goroutine每30秒强制刷新缓冲区,防止日志丢失
  4. 时间戳格式:包含精确时间戳便于问题排查
  5. 上下文信息:记录错误发生的具体上下文

这种实现方式能够有效处理每分钟数千请求的场景,通过缓冲机制显著降低文件系统调用频率,同时保持线程安全性。

回到顶部