l4g - 结构化日志库
l4g (Log for Go) 是一个高性能的结构化日志库,完全兼容 Go 标准库 log/slog 包。它提供零分配的日志记录、缓冲池优化、多种输出格式以及彩色终端输出支持。最低支持 Go 1.21.0 版本。
安装
go get -u go-slim.dev/l4g
快速开始
package main
import "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.100
Jan 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 和 Fatal
logger.Panic("严重错误", "reason", reason) // 记录后会 panic
logger.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=8080
3. 日志前缀
使用 WithPrefix 为日志添加前缀:
// 创建带前缀的日志器
apiLogger := logger.WithPrefix("api")
apiLogger.Info("请求处理完成")
// 输出: INFO [api] 请求处理完成
// 链式前缀
moduleLogger := logger.WithPrefix("auth").WithPrefix("jwt")
moduleLogger.Debug("验证令牌")
// 输出: DEBUG [jwtauth] 验证令牌
4. 通道日志器
使用 Channel 创建命名的日志器实例,多次调用相同名称返回同一实例:
// 在不同的包中使用相同的日志器
// package database
dbLogger := l4g.Channel("database")
dbLogger.Debug("执行查询", "sql", query)
// package cache
cacheLogger := 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 Handler
type 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
}
// 使用自定义 Handler
logger := 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() *Logger
func SetDefault(l *Logger)
// 获取和设置默认输出
func Output() io.Writer
func SetOutput(w io.Writer)
// 获取和设置默认级别
func GetLevel() Level
func 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) *Logger
func WithPrefix(prefix string) *Logger
func WithGroup(name string) *Logger
// 通道日志器
func Channel(name string) *Logger
Logger 类型
type Logger struct { ... }
// 创建日志器
func New(opts Options) *Logger
// 输出控制
func (l *Logger) Output() io.Writer
func (l *Logger) SetOutput(w io.Writer)
// 级别控制
func (l *Logger) Level() Level
func (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) *Logger
func (l *Logger) WithPrefix(prefix string) *Logger
func (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 int
func (l Level) Int() int
func (l Level) Real() Level
func (l Level) String() string
func (l Level) Level() Level
func (l Level) MarshalJSON() ([]byte, error)
func (l Level) UnmarshalJSON(data []byte) error
func (l Level) MarshalText() ([]byte, error)
func (l Level) UnmarshalText(data []byte) error
LevelVar 类型
type LevelVar struct { ... }
func NewLevelVar(lvl Leveler) *LevelVar
func (v *LevelVar) Level() Level
func (v *LevelVar) Set(l Level)
func (v *LevelVar) Int() int
func (v *LevelVar) String() string
属性函数
func String[T ~string](key string, value T) Attr
func Int[T ~int|~int8|~int16|~int32|~int64](key string, value T) Attr
func Int64(key string, value int64) Attr
func Uint[T ~uint|~uint8|~uint16|~uint32|~uint64](key string, value T) Attr
func Float[T ~float32|~float64](key string, value T) Attr
func Bool[T ~bool](key string, v T) Attr
func Time(key string, v time.Time) Attr
func Duration(key string, value time.Duration) Attr
func Group(key string, args ...any) Attr
func Any(key string, value any) Attr
func Err(err error) Attr
func 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自定义创建逻辑