l4g - 结构化日志库
l4g (Log for Go) 是一个高性能的结构化日志库,完全兼容 Go 标准库 log/slog 包。它提供零分配的日志记录、缓冲池优化、多种输出格式以及彩色终端输出支持。最低支持 Go 1.21.0 版本。
安装
go get -u go-slim.dev/l4g快速开始
package mainimport "go-slim.dev/l4g"func main() { // 使用默认日志器 l4g.Info("服务启动", "port", 8080) l4g.Debug("调试信息", "user", "alice") l4g.Error("发生错误", "error", err) // 创建自定义日志器 logger := l4g.New(l4g.Options{ Level: l4g.LevelDebug, Prefix: "myapp", }) logger.Info("应用启动")}核心概念
日志级别
l4g 提供 7 个日志级别,按严重程度从低到高:
const ( LevelTrace // 最详细的追踪信息 LevelDebug // 调试信息 LevelInfo // 一般信息(默认级别) LevelWarn // 警告信息 LevelError // 错误信息 LevelPanic // 严重错误,记录后会 panic LevelFatal // 致命错误,记录后会退出程序)日志格式
每条日志包含以下字段:
时间 级别 [前缀] 消息 key1=value1 key2=value2示例输出:
Jan 2 15:04:05 INFO [myapp] 用户登录 user=alice ip=192.168.1.100Jan 2 15:04:06 ERROR [myapp] 数据库错误 error="connection timeout" retry=3基本使用
包级函数(使用默认日志器)
import "go-slim.dev/l4g"func main() { // 各级别日志 l4g.Trace("追踪信息") l4g.Debug("调试信息") l4g.Info("一般信息") l4g.Warn("警告信息") l4g.Error("错误信息") // 带结构化字段 l4g.Info("用户登录", "user", "alice", "ip", "192.168.1.100") l4g.Error("数据库错误", "error", err, "retry", 3) // 格式化日志(Printf 风格) l4g.Infof("服务器启动在端口 %d", 8080) l4g.Errorf("无法连接到 %s: %v", host, err) // JSON 风格日志 l4g.Infoj(map[string]any{ "event": "user_login", "user": "alice", "timestamp": time.Now(), })}创建自定义日志器
import ( "os" "go-slim.dev/l4g")func main() { // 基本配置 logger := l4g.New(l4g.Options{ Output: os.Stdout, Level: l4g.LevelDebug, Prefix: "myapp", }) // 自定义时间格式 logger = l4g.New(l4g.Options{ TimeFormat: "2006-01-02 15:04:05", Level: l4g.LevelInfo, }) // 禁用颜色输出 logger = l4g.New(l4g.Options{ NoColor: true, }) // 自定义级别格式 logger = l4g.New(l4g.Options{ LevelFormat: func(lvl l4g.Level) string { return "[" + lvl.String() + "]" }, }) // 自定义前缀格式 logger = l4g.New(l4g.Options{ PrefixFormat: func(prefix string) string { return "<" + prefix + ">" }, })}日志器方法
logger := l4g.New(l4g.Options{Level: l4g.LevelDebug})// 基本日志方法logger.Trace("追踪", "detail", "...")logger.Debug("调试", "var", value)logger.Info("信息", "status", "ok")logger.Warn("警告", "threshold", 90)logger.Error("错误", "error", err)// 格式化方法logger.Debugf("变量值: %v", value)logger.Infof("请求处理耗时: %v", duration)logger.Errorf("失败: %s", reason)// JSON 风格方法logger.Debugj(map[string]any{"query": sql, "duration": dur})logger.Infoj(map[string]any{"event": "request", "path": path})// Panic 和 Fatallogger.Panic("严重错误", "reason", reason) // 记录后会 paniclogger.Fatal("致命错误", "error", err) // 记录后会调用 os.Exit(1)高级特性
1. 结构化属性
使用 WithAttrs 创建带有固定属性的日志器:
// 创建带有请求 ID 的日志器requestLogger := logger.WithAttrs("request_id", reqID, "user", userID)// 所有日志都会包含这些属性requestLogger.Info("处理开始")requestLogger.Info("处理完成", "duration", dur)// 输出:// INFO 处理开始 request_id=abc123 user=alice// INFO 处理完成 request_id=abc123 user=alice duration=150ms链式调用:
logger. WithAttrs("service", "api"). WithAttrs("version", "v1.0"). Info("服务启动")2. 属性分组
使用 WithGroup 组织相关属性:
// 创建带分组的日志器httpLogger := logger.WithGroup("http")httpLogger.Info("请求", "method", "GET", "path", "/api/users")// 输出: INFO 请求 http.method=GET http.path=/api/users// 嵌套分组serverLogger := logger.WithGroup("server").WithGroup("http")serverLogger.Info("监听", "port", 8080)// 输出: INFO 监听 server.http.port=80803. 日志前缀
使用 WithPrefix 为日志添加前缀:
// 创建带前缀的日志器apiLogger := logger.WithPrefix("api")apiLogger.Info("请求处理完成")// 输出: INFO [api] 请求处理完成// 链式前缀moduleLogger := logger.WithPrefix("auth").WithPrefix("jwt")moduleLogger.Debug("验证令牌")// 输出: DEBUG [jwtauth] 验证令牌4. 通道日志器
使用 Channel 创建命名的日志器实例,多次调用相同名称返回同一实例:
// 在不同的包中使用相同的日志器// package databasedbLogger := l4g.Channel("database")dbLogger.Debug("执行查询", "sql", query)// package cachecacheLogger := l4g.Channel("cache")cacheLogger.Info("缓存命中", "key", key)// 相同名称返回同一实例logger1 := l4g.Channel("myapp")logger2 := l4g.Channel("myapp")// logger1 == logger2 (true)自定义 Channel 创建函数:
// 设置自定义的日志器创建函数l4g.NewFunc = func(name string) *l4g.Logger { return l4g.New(l4g.Options{ Prefix: name, Level: l4g.LevelDebug, TimeFormat: "15:04:05", })}// 之后创建的 Channel 日志器都使用此配置logger := l4g.Channel("myservice")5. 动态日志级别
使用 LevelVar 实现运行时动态调整日志级别:
levelVar := l4g.NewLevelVar(l4g.LevelInfo)logger := l4g.New(l4g.Options{ Level: levelVar,})logger.Debug("调试信息") // 不会输出(级别低于 Info)logger.Info("一般信息") // 会输出// 运行时调整级别levelVar.Set(l4g.LevelDebug)logger.Debug("调试信息") // 现在会输出了6. 自定义输出
使用 OutputVar 实现动态切换输出目标:
import "os"outputVar := l4g.NewOutputVar(os.Stdout)logger := l4g.New(l4g.Options{ Output: outputVar,})logger.Info("输出到标准输出")// 切换到文件file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)outputVar.Set(file)logger.Info("输出到文件")使用 SetOutput 方法:
logger := l4g.New(l4g.Options{})logger.Info("输出到 stderr") // 默认// 切换输出file, _ := os.Create("app.log")logger.SetOutput(file)logger.Info("输出到文件")7. 属性替换
使用 ReplaceAttr 自定义属性的输出格式:
logger := l4g.New(l4g.Options{ ReplaceAttr: func(groups []string, attr l4g.Attr) l4g.Attr { // 隐藏敏感信息 if attr.Key == "password" { return l4g.String("password", "***") } // 自定义时间格式 if attr.Key == l4g.TimeKey { return l4g.String("time", time.Now().Format("15:04:05")) } // 自定义级别显示 if attr.Key == l4g.LevelKey { level := attr.Value.Any().(l4g.Level) return l4g.String("level", "[" + level.String() + "]") } return attr },})logger.Info("用户登录", "username", "alice", "password", "secret123")// 输出: 15:04:05 [info] 用户登录 username=alice password=***8. 彩色输出
l4g 默认启用彩色终端输出,不同级别使用不同颜色:
- TRACE: 灰色
- DEBUG: 亮青色
- INFO: 白色
- WARN: 亮绿色
- ERROR: 亮黄色
- PANIC: 亮红色
- FATAL: 亮红色
禁用颜色:
logger := l4g.New(l4g.Options{ NoColor: true,})自定义颜色属性:
import "go-slim.dev/l4g"// 使用预定义的错误属性(红色)logger.Info("操作失败", l4g.Err(err))// 自定义颜色属性attr := l4g.ColorAttr(9, l4g.String("status", "critical"))logger.Warn("警告", attr)// ANSI 256 色支持// 0-7: 标准颜色// 8-15: 高亮颜色// 16-231: 6×6×6 色彩立方// 232-255: 24 级灰度属性类型
l4g 提供类型化的属性构造函数:
import ( "time" "go-slim.dev/l4g")logger.Info("类型化属性", l4g.String("name", "alice"), // 字符串 l4g.Int("age", 30), // 整数 l4g.Int64("count", 1000000), // int64 l4g.Uint("port", 8080), // 无符号整数 l4g.Uint64("size", 1024), // uint64 l4g.Float("score", 95.5), // 浮点数 l4g.Bool("active", true), // 布尔值 l4g.Time("timestamp", time.Now()), // 时间 l4g.Duration("elapsed", dur), // 时间间隔 l4g.Any("data", someValue), // 任意类型 l4g.Err(err), // 错误(红色))// 分组属性logger.Info("服务器启动", l4g.Group("server", l4g.String("host", "localhost"), l4g.Int("port", 8080), ), l4g.Group("database", l4g.String("driver", "postgres"), l4g.String("host", "db.example.com"), ),)// 输出:// INFO 服务器启动 server.host=localhost server.port=8080 database.driver=postgres database.host=db.example.com自定义 Handler
实现自己的 Handler 接口:
type Handler interface { Enabled(Level) bool Handle(Record) error WithAttrs(attrs []Attr) Handler WithGroup(name string) Handler WithPrefix(prefix string) Handler}// 示例:JSON Handlertype JSONHandler struct { output io.Writer level l4g.Leveler}func (h *JSONHandler) Enabled(level l4g.Level) bool { return level >= h.level.Level()}func (h *JSONHandler) Handle(r l4g.Record) error { data := map[string]any{ "time": r.Time, "level": r.Level.String(), "msg": r.Message, } r.Attrs(func(attr l4g.Attr) bool { data[attr.Key] = attr.Value.Any() return true }) enc := json.NewEncoder(h.output) return enc.Encode(data)}func (h *JSONHandler) WithAttrs(attrs []l4g.Attr) l4g.Handler { // 实现属性追加逻辑 return h}func (h *JSONHandler) WithGroup(name string) l4g.Handler { // 实现分组逻辑 return h}func (h *JSONHandler) WithPrefix(prefix string) l4g.Handler { // 实现前缀逻辑 return h}// 使用自定义 Handlerlogger := l4g.New(l4g.Options{ Handler: &JSONHandler{ output: os.Stdout, level: l4g.NewLevelVar(l4g.LevelInfo), },})使用场景
1. Web 应用日志
import ( "go-slim.dev/l4g" "go-slim.dev/slim")func main() { // 创建应用日志器 logger := l4g.New(l4g.Options{ Prefix: "webapp", Level: l4g.LevelInfo, }) s := slim.New() // 请求日志中间件 s.Use(func(next slim.HandlerFunc) slim.HandlerFunc { return func(c slim.Context) error { start := time.Now() // 为每个请求创建独立的日志器 reqLogger := logger.WithAttrs( "request_id", c.Response().Header().Get("X-Request-ID"), "method", c.Request().Method, "path", c.Request().URL.Path, ) // 将日志器存入上下文 c.Set("logger", reqLogger) err := next(c) // 记录请求完成 reqLogger.Info("请求完成", "status", c.Response().Status, "duration", time.Since(start), ) return err } }) s.GET("/users/:id", func(c slim.Context) error { logger := c.Get("logger").(*l4g.Logger) logger.Debug("获取用户", "id", c.PathParam("id")) // ... return nil }) logger.Info("服务器启动", "port", 8080) s.Start(":8080")}2. 微服务日志
// 服务级日志器serviceLogger := l4g.New(l4g.Options{ Prefix: "user-service",}).WithAttrs( "service", "user-service", "version", "v1.2.3", "instance", hostname,)// 模块级日志器type UserRepository struct { logger *l4g.Logger}func NewUserRepository() *UserRepository { return &UserRepository{ logger: serviceLogger.WithGroup("repository").WithPrefix("db"), }}func (r *UserRepository) FindByID(id int) (*User, error) { r.logger.Debug("查询用户", "user_id", id) user, err := r.queryUser(id) if err != nil { r.logger.Error("查询失败", "user_id", id, l4g.Err(err)) return nil, err } r.logger.Info("查询成功", "user_id", id) return user, nil}3. 后台任务日志
func runBackgroundJob(jobID string) { // 为每个任务创建独立日志器 jobLogger := l4g.Channel("jobs").WithAttrs( "job_id", jobID, "worker", workerID, ) jobLogger.Info("任务开始") defer func() { if r := recover(); r != nil { jobLogger.Panic("任务崩溃", "panic", r) } }() // 执行任务... for step := range steps { stepLogger := jobLogger.WithAttrs("step", step) stepLogger.Debug("执行步骤") if err := executeStep(step); err != nil { stepLogger.Error("步骤失败", l4g.Err(err)) return } stepLogger.Info("步骤完成") } jobLogger.Info("任务完成")}4. 开发/生产环境切换
func newLogger() *l4g.Logger { var opts l4g.Options if os.Getenv("ENV") == "production" { // 生产环境:JSON 格式,Info 级别,输出到文件 file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) opts = l4g.Options{ Output: file, Level: l4g.LevelInfo, NoColor: true, NewHandlerFunc: NewJSONHandler, // 假设实现了 JSON Handler } } else { // 开发环境:彩色文本,Debug 级别,输出到控制台 opts = l4g.Options{ Output: os.Stdout, Level: l4g.LevelDebug, NoColor: false, TimeFormat: "15:04:05", } } return l4g.New(opts)}5. 测试中使用
import ( "bytes" "testing" "go-slim.dev/l4g")func TestUserService(t *testing.T) { // 捕获日志输出 var buf bytes.Buffer logger := l4g.New(l4g.Options{ Output: &buf, Level: l4g.LevelDebug, }) service := NewUserService(logger) service.CreateUser("alice") // 验证日志内容 logs := buf.String() if !strings.Contains(logs, "创建用户") { t.Error("缺少预期的日志") }}// 禁用测试日志func TestQuiet(t *testing.T) { logger := l4g.New(l4g.Options{ Output: io.Discard, // 丢弃所有日志 }) // 测试代码...}性能优化
零分配日志
当日志级别被禁用时,l4g 实现零内存分配:
logger := l4g.New(l4g.Options{Level: l4g.LevelInfo})// Debug 被禁用,此调用不会分配内存logger.Debug("调试信息", "key", "value") // 0 allocs// Info 启用,会正常分配内存logger.Info("一般信息", "key", "value") // 有 allocs检查级别
在构建复杂日志消息前先检查级别:
// 不推荐:总是构建 expensive 对象logger.Debug("详细信息", "data", buildExpensiveObject())// 推荐:先检查级别if logger.Enabled(l4g.LevelDebug) { logger.Debug("详细信息", "data", buildExpensiveObject())}缓冲池
l4g 内部使用 sync.Pool 复用缓冲区,减少 GC 压力:
// 内部实现(用户无需关心)var bufferPool = sync.Pool{ New: func() any { return new(buffer) },}API 参考
包级函数
// 获取和设置默认日志器func Default() *Loggerfunc SetDefault(l *Logger)// 获取和设置默认输出func Output() io.Writerfunc SetOutput(w io.Writer)// 获取和设置默认级别func GetLevel() Levelfunc SetLevel(level Level)// 日志方法(所有级别)func Trace(msg string, args ...any)func Debug(msg string, args ...any)func Info(msg string, args ...any)func Warn(msg string, args ...any)func Error(msg string, args ...any)func Panic(msg string, args ...any)func Fatal(msg string, args ...any)// 格式化日志方法func Tracef(format string, args ...any)func Debugf(format string, args ...any)func Infof(format string, args ...any)func Warnf(format string, args ...any)func Errorf(format string, args ...any)func Panicf(format string, args ...any)func Fatalf(format string, args ...any)// JSON 风格日志方法func Tracej(j map[string]any)func Debugj(j map[string]any)func Infoj(j map[string]any)func Warnj(j map[string]any)func Errorj(j map[string]any)func Panicj(j map[string]any)func Fatalj(j map[string]any)// 创建带属性/分组/前缀的日志器func WithAttrs(args ...any) *Loggerfunc WithPrefix(prefix string) *Loggerfunc WithGroup(name string) *Logger// 通道日志器func Channel(name string) *LoggerLogger 类型
type Logger struct { ... }// 创建日志器func New(opts Options) *Logger// 输出控制func (l *Logger) Output() io.Writerfunc (l *Logger) SetOutput(w io.Writer)// 级别控制func (l *Logger) Level() Levelfunc (l *Logger) SetLevel(lvl Level)func (l *Logger) Enabled(level Level) bool// 日志方法(与包级函数相同)func (l *Logger) Trace(msg string, args ...any)func (l *Logger) Debug(msg string, args ...any)// ... 其他级别// 格式化方法func (l *Logger) Tracef(format string, args ...any)func (l *Logger) Debugf(format string, args ...any)// ... 其他级别// JSON 方法func (l *Logger) Tracej(j map[string]any)func (l *Logger) Debugj(j map[string]any)// ... 其他级别// 创建派生日志器func (l *Logger) WithAttrs(args ...any) *Loggerfunc (l *Logger) WithPrefix(prefix string) *Loggerfunc (l *Logger) WithGroup(name string) *Logger// 通用日志方法func (l *Logger) Log(level Leveler, msg string, args ...any)func (l *Logger) Logf(level Level, format string, args ...any)func (l *Logger) Logj(level Level, j map[string]any)Options 类型
type Options struct { Prefix string // 日志前缀 Level Level // 最小日志级别 NewHandlerFunc func(HandlerOptions) Handler // Handler 工厂函数 Handler Handler // 自定义 Handler ReplaceAttr func([]string, Attr) Attr // 属性替换函数 TimeFormat string // 时间格式 LevelFormat func(Level) string // 级别格式化函数 PrefixFormat func(string) string // 前缀格式化函数 Output io.Writer // 输出目标 NoColor bool // 禁用颜色}Level 类型
type Level intfunc (l Level) Int() intfunc (l Level) Real() Levelfunc (l Level) String() stringfunc (l Level) Level() Levelfunc (l Level) MarshalJSON() ([]byte, error)func (l Level) UnmarshalJSON(data []byte) errorfunc (l Level) MarshalText() ([]byte, error)func (l Level) UnmarshalText(data []byte) errorLevelVar 类型
type LevelVar struct { ... }func NewLevelVar(lvl Leveler) *LevelVarfunc (v *LevelVar) Level() Levelfunc (v *LevelVar) Set(l Level)func (v *LevelVar) Int() intfunc (v *LevelVar) String() string属性函数
func String[T ~string](key string, value T) Attrfunc Int[T ~int|~int8|~int16|~int32|~int64](key string, value T) Attrfunc Int64(key string, value int64) Attrfunc Uint[T ~uint|~uint8|~uint16|~uint32|~uint64](key string, value T) Attrfunc Float[T ~float32|~float64](key string, value T) Attrfunc Bool[T ~bool](key string, v T) Attrfunc Time(key string, v time.Time) Attrfunc Duration(key string, value time.Duration) Attrfunc Group(key string, args ...any) Attrfunc Any(key string, value any) Attrfunc Err(err error) Attrfunc ColorAttr(color uint8, attr Attr) Attr注意事项
-
Panic 和 Fatal 的行为:
Panic方法会在记录日志后调用panic()Fatal方法会在记录日志后调用os.Exit(1)- 可通过设置
l4g.OsExiter自定义退出行为(主要用于测试)
-
并发安全:
Logger类型是并发安全的,可在多个 goroutine 中使用Handler实现也必须是并发安全的
-
级别检查:
- 使用
Enabled()方法可以避免构建不必要的日志参数 - 禁用的日志级别会快速返回,几乎零开销
- 使用
-
WithAttrs/WithGroup/WithPrefix:
- 这些方法返回新的 Logger 实例,不修改原实例
- 可以安全地在多个地方使用派生的日志器
-
属性参数格式:
- 支持
key, value, key, value, ...形式 - 支持
Attr类型 - 可以混合使用两种形式
- 支持
-
时间格式:
- 默认使用
time.StampMilli格式 - 可通过
Options.TimeFormat自定义
- 默认使用
-
Channel 日志器:
- 相同名称返回同一实例
- 实例在程序生命周期内保持
- 可通过
l4g.NewFunc自定义创建逻辑