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")
}
关键设计要点
- 单文件句柄共享:使用单个
BufferedLogger实例,通过sync.Mutex确保线程安全 - 缓冲写入:
bufio.Writer提供64KB缓冲区,减少磁盘I/O操作 - 定期刷新:后台goroutine每30秒强制刷新缓冲区,防止日志丢失
- 时间戳格式:包含精确时间戳便于问题排查
- 上下文信息:记录错误发生的具体上下文
这种实现方式能够有效处理每分钟数千请求的场景,通过缓冲机制显著降低文件系统调用频率,同时保持线程安全性。


