原生日志模块
注意这个是 go 1.2 以后的版本才有的标准库
默认日志实例与方法
go
package main
import "log/slog"
func main() {
// 可以直接使用 slog 这个默认的实例
slog.Debug("[debug] log message") // 默认不输出
slog.Info("[info] log message") // 默认的日志输出等级是 info
slog.Warn("[warn] log message")
slog.Error("[error] log message")
}修改日志输出等级
如何修改日志输出等级呢, 这就需要自定义实例, 而不是使用默认的 slog 实例
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true, // 日志输出语句的源代码位置
Level: slog.LevelDebug, // 设置日志输出等级
}))
logger.Debug("[debug] log message") // 此时的 debug 语句也会输出
logger.Info("[info] log message")
logger.Warn("[warn] log message")
logger.Error("[error] log message")
}修改日志输出格式
go
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true, // 日志输出语句的源代码位置
Level: slog.LevelDebug, // 设置日志输出等级
}))
logger.Debug("[debug] log message")
logger.Info("[info] log message")
logger.Warn("[warn] log message")
logger.Error("[error] log message")
// slog.Debug("[debug] log message")
// slog.Info("[info] log message")
// slog.Warn("[warn] log message")
// slog.Error("[error] log message")
// 当我们使用自定义的实例后, 日志输出的格式好像和
// 默认的不一样, 那么应该如何调整日志的输出格式呢?
// NewJSONHandler: https://pkg.go.dev/log/slog#JSONHandler
// 让日志以 JSON 格式输出
logger2 := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: false, // 日志输出语句的源代码位置
Level: slog.LevelDebug, // 设置日志输出等级
}))
logger2.Debug("[debug] log message")
logger2.Info("[info] log message")
logger2.Warn("[warn] log message")
logger2.Error("[error] log message")将程序运行的变量内容写入日志
也就是一些资料中说的: 结构化日志
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 设置日志输出等级
}))
logger.Debug("hard code string message")
// 之前都是写死的字符串, 如何将一些程序运行中的变量内容写入日志呢?
logger.Debug("include some variables string message", slog.Int("id", 1001), slog.String("email", "test@example.com"))
}修改默认实例
go
package main
import (
"log/slog"
"os"
)
func main() {
// 这是我的自定义实例, 我想让所有的 slog.xxx 都使用这个 logger 的属性
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 设置日志输出等级
}))
// 修改默认实例后
slog.SetDefault(logger)
// 设置日志使用的属性都是 logger 设置的属性
// 会输出 debug 信息, 并且输出 json 格式日志
// 也就是说, 在其他文件中直接使用 slog 也会是 logger 设定的属性
slog.Debug("[debug] log message")
slog.Info("[info] log message")
slog.Warn("[warn] log message")
slog.Error("[error] log message")
}日志内容替换
go
package main
import (
"fmt"
"log/slog"
"os"
"time"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 设置日志输出等级
AddSource: false, // 输出日志的源代码文件位置
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
fmt.Println(a)
// a 输出的内容是这样的3个字段
// time=2026-04-20 15:48:43.222565 +0800 CST
// level=DEBUG
// msg=some debug log message
// 将 time 字段格式为: YYYY-MM-DD HH:MM:SS
if a.Key == slog.TimeKey {
a.Value = slog.StringValue(a.Value.Time().Format(time.DateTime))
}
return a
},
}))
logger.Debug("some debug log message")
}日志双写
所谓的日志双写, 其实就是同时输出到: 日志文件 和 标准输出 os.Stdout
go
package main
import (
"fmt"
"io"
"log/slog"
"os"
)
func main() {
// app.log: 表示打开 app.log 这个文件
// os.O_RDWR: 表示打开文件的读写权限
// os.O_CREATE: 表示如果文件不存在则创建文件
// os.O_APPEND: 表示如果文件存在则追加写入
// 0o666: 表示创建文件的权限为 666
logfile, err := os.OpenFile("app.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
if err != nil {
fmt.Println("日志文件创建失败:", err)
return
}
defer logfile.Close()
// MultiWriter: 多路复用写入器(os.Stdout:标准输出 logfile:日志文件)
mw := io.MultiWriter(os.Stdout, logfile)
logger := slog.New(slog.NewJSONHandler(mw, &slog.HandlerOptions{
Level: slog.LevelDebug, // 设置日志输出等级
AddSource: false, // 输出日志的源代码文件位置
}))
// 这个日志会同时输出到 app.log 文件和标准输出
logger.Debug("some debug log message")
}自定义日志等级
slog 默认的日志等级有:
- debug: 调试信息
- info: 普通日志消息
- warn: 警告信息
- error: 错误信息(会让程序退出)
go
package main
import (
"context"
"log/slog"
"os"
)
// 1.自定义 log 等级类型
type CustomLogLevel slog.Level
const (
// 2.定义 log 等级和值
PanicLevel CustomLogLevel = 16
FatalLevel CustomLogLevel = 32
)
// 3. 实现 toString 方法
func (l CustomLogLevel) String() string {
switch l {
case PanicLevel:
return "Panic"
case FatalLevel:
return "Fatal"
default:
return slog.Level(l).String()
}
}
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 设置日志输出等级
}))
// 4. 使用自定义 log 等级
logger.Log(context.Background(), slog.Level(PanicLevel), "程序恐慌")
logger.Log(context.Background(), slog.Level(FatalLevel), "致命错误")
// {"time":"2026-04-21T20:14:21.989557+08:00","level":"ERROR+8","msg":"程序恐慌"}
// {"time":"2026-04-21T20:14:21.989699+08:00","level":"ERROR+24","msg":"致命错误"}
// 输出的 level 字段并不直观, 可以用 ReplaceAttr 替换 level 字段的值
// 5.使用 ReplaceAttr 替换 level 字段的值
logger2 := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 设置日志输出等级
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.LevelKey {
logLvStr := CustomLogLevel(a.Value.Any().(slog.Level)).String()
a.Value = slog.StringValue(logLvStr)
}
return a
},
}))
logger2.Log(context.Background(), slog.Level(PanicLevel), "程序恐慌")
logger2.Log(context.Background(), slog.Level(FatalLevel), "致命错误")
// {"time":"2026-04-21T20:19:08.252617+08:00","level":"Panic","msg":"程序恐慌"}
// {"time":"2026-04-21T20:19:08.252632+08:00","level":"Fatal","msg":"致命错误"}
}添加固定字段输出
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 设置日志输出等级
}))
// 注: logger.With 会返回一个新的实例
logger = logger.With(slog.String("AppName", "slog-demo-app"))
// 请使用这个新的实例
// {"time":"2026-04-21T20:24:09.922246+08:00","level":"INFO","msg":"日志信息","AppName":"slog-demo-app"}
logger.Info("日志信息")
}go
package main
import (
"log/slog"
"os"
)
func main() {
var handler slog.Handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
// 重新赋值 handler
handler = handler.WithAttrs([]slog.Attr{
slog.String("request-client-ip", "127.0.0.1"),
slog.String("request-id", "mock-request-uuid"),
slog.String("request-path", "/api/v1/xxx"),
slog.String("request-method", "GET"),
})
logger := slog.New(handler)
logger.Info("日志信息")
// {
// "time": "2026-04-21T20:32:02.805126+08:00",
// "level": "INFO",
// "msg": "日志信息",
// "request-client-ip": "127.0.0.1",
// "request-id": "mock-request-uuid",
// "request-path": "/api/v1/xxx",
// "request-method": "GET"
// }
}日志轮转 Log Rotation
为了避免单个日志文件过大, 方便日志归档/清理
slog 本身并不支持日志轮转相关功能, 可以通过第三方开源库来实现 file-rotatelogs
- 安装
sh
go get github.com/lestrrat-go/file-rotatelogs- 使用
go
package main
import (
"fmt"
"io"
"log/slog"
"os"
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
)
func main() {
logfile, err := rotatelogs.New(
"logs/app-%Y-%m-%d.log", // 日志文件命名: 如 logs/app-2025-12-11.log
rotatelogs.WithMaxAge(24*time.Hour*7), // 日志保存时间: 7days
rotatelogs.WithRotationTime(24*time.Hour), // 日志文件轮转时间: 24hours
rotatelogs.WithLocation(time.Local), // 日志文件时区: 使用本地时间
// rotatelogs.ForceNewFile(), // 强制打开新文件
rotatelogs.WithClock(rotatelogs.Local), // 日志文件时间戳生成函数: 使用本地时间
)
if err != nil {
fmt.Println("日志文件创建失败:", err)
return
}
// 日志双写 + 日志轮转
mw := io.MultiWriter(os.Stdout, logfile)
logger := slog.New(slog.NewJSONHandler(mw, &slog.HandlerOptions{
Level: slog.LevelDebug,
AddSource: false,
}))
// 这个日志会同时输出到 logs/app-%Y-%m-%d.log 文件和标准输出
logger.Debug("some debug log message")
}最后
在一些小的项目中, 使用 slog + file-rotatelogs 也是没有问题的, 但是很遗憾 file-rotatelogs 已经不再维护了, 为了长远考虑, 可以使 用 zap 或者 logrus 来实现日志轮转