Go 标准库 log
1. 概述
Go 标准库有两个日志包:log 是基础版,输出一行文本加前缀;slog 是后来加入的,带日志级别和结构化字段。两个包对照着看。
2. 基本使用
2.1 初始化和基本配置
log 包
go
package main
import (
"log"
)
func main() {
log.Println("This is a log message")
// 自定义配置
log.SetPrefix("INFO: ")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("This is a customized log message")
}slog 包
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("This is a log message")
}2.2 记录日志
log 包
go
package main
import "log"
func main() {
log.Println("This is a log message")
log.Printf("This is a formatted log message: %d", 42)
// log.Fatal / log.Panic 会调用 os.Exit(1) / panic() 终止程序,
// 这里注释掉,避免示例运行中途退出。
// log.Fatal("This is a fatal log message")
// log.Panic("This is a panic log message")
}slog 包
go
package main
import (
"errors"
"log/slog"
"os"
)
func main() {
err := errors.New("example failure")
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("This is an info message")
logger.Warn("This is a warning message")
logger.Error("This is an error message", slog.Any("error", err))
}2.3 日志输出格式
log 包
go
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile)
log.Println("This is a log message with custom format")slog 包
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("This is a log message in JSON format")
}3. 日志级别
log 包的局限性
log 包没有内置的日志级别机制,可以通过自定义实现日志级别。
go
package main
import (
"log"
)
type Level int
const (
DEBUG Level = iota
INFO
WARN
ERROR
)
var logLevel = INFO
func logMessage(level Level, msg string) {
if level >= logLevel {
log.Println(msg)
}
}
func main() {
logMessage(INFO, "This is an info message")
logMessage(DEBUG, "This is a debug message")
}slog 包
slog 包支持内置的日志级别。
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("This is an info message")
logger.Warn("This is a warning message")
logger.Error("This is an error message")
}4. 输出重定向
log 包
go
logFile, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
log.SetOutput(logFile)
log.Println("This message is written to the log file")slog 包
go
package main
import (
"log"
"log/slog"
"os"
)
func main() {
logFile, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
logger := slog.New(slog.NewJSONHandler(logFile, nil))
logger.Info("This message is written to the log file")
}5. 日志前缀和标志
log 包
go
log.SetPrefix("INFO: ")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("This is a log message with prefix and flags")slog 包
slog 包没有前缀的概念,但可以通过添加字段实现类似功能。
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("This is a log message", slog.String("prefix", "INFO"))
}6. 日志轮转
log 包
使用第三方库(如 lumberjack)来实现日志轮转。
go
package main
import (
"log"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
log.SetOutput(&lumberjack.Logger{
Filename: "app.log",
MaxSize: 10, // megabytes
MaxBackups: 3,
MaxAge: 28, //days
})
log.Println("This is a log message with log rotation")
}slog 包
同样可以使用 lumberjack 实现日志轮转。
go
package main
import (
"gopkg.in/natefinch/lumberjack.v2"
"log/slog"
)
func main() {
logger := slog.New(slog.NewJSONHandler(&lumberjack.Logger{
Filename: "app.log",
MaxSize: 10, // megabytes
MaxBackups: 3,
MaxAge: 28, //days
}, nil))
logger.Info("This is a log message with log rotation")
}7. 并发与安全
log 包
log 包是并发安全的,但在高并发场景下可能需要优化。
go
package main
import (
"log"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
log.Printf("Logging from goroutine %d", i)
}(i)
}
wg.Wait()
}slog 包
slog 包也是并发安全的。
go
package main
import (
"log/slog"
"os"
"sync"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
logger.Info("Logging from goroutine", slog.Int("goroutine", i))
}(i)
}
wg.Wait()
}8. 实践案例
8.1 构建一个简单的日志工具包
go
package logutil
import (
"log"
"gopkg.in/natefinch/lumberjack.v2"
)
func NewLogger(filename string) *log.Logger {
logger := log.New(&lumberjack.Logger{
Filename: filename,
MaxSize: 10, // megabytes
MaxBackups: 3,
MaxAge: 28, //days
}, "", log.Ldate|log.Ltime|log.Lshortfile)
return logger
}8.2 集成到一个实际项目中
假设上面的 logutil 包位于当前模块的 logutil 子目录中(模块名为 example.com/myapp),则在主包中这样引用:
go
package main
import (
"example.com/myapp/logutil"
)
func main() {
logger := logutil.NewLogger("app.log")
logger.Println("This is a log message from the main package")
}9. 总结与最佳实践
日志记录的最佳实践:
- 始终记录足够的上下文信息,便于调试和分析。
- 合理设置日志级别,避免过多无用日志。
- 使用日志轮转,防止日志文件过大。
- 在高并发场景下,确保日志记录的性能和安全。
避免常见错误:
- 不要在日志中记录敏感信息。
- 注意日志文件的权限设置,防止未授权访问。
提高日志记录的效率与可读性:
- 使用结构化日志,提高日志的可解析性。
- 选择合适的日志格式(如 JSON)以便于日志聚合和分析。