Slim

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=8080

3. 日志前缀

使用 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) *Logger

Logger 类型

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) error

LevelVar 类型

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

注意事项

  1. Panic 和 Fatal 的行为

    • Panic 方法会在记录日志后调用 panic()
    • Fatal 方法会在记录日志后调用 os.Exit(1)
    • 可通过设置 l4g.OsExiter 自定义退出行为(主要用于测试)
  2. 并发安全

    • Logger 类型是并发安全的,可在多个 goroutine 中使用
    • Handler 实现也必须是并发安全的
  3. 级别检查

    • 使用 Enabled() 方法可以避免构建不必要的日志参数
    • 禁用的日志级别会快速返回,几乎零开销
  4. WithAttrs/WithGroup/WithPrefix

    • 这些方法返回新的 Logger 实例,不修改原实例
    • 可以安全地在多个地方使用派生的日志器
  5. 属性参数格式

    • 支持 key, value, key, value, ... 形式
    • 支持 Attr 类型
    • 可以混合使用两种形式
  6. 时间格式

    • 默认使用 time.StampMilli 格式
    • 可通过 Options.TimeFormat 自定义
  7. Channel 日志器

    • 相同名称返回同一实例
    • 实例在程序生命周期内保持
    • 可通过 l4g.NewFunc 自定义创建逻辑

相关链接

切换 选择 esc 关闭搜索