Slim

Logger 中间件

Logger 中间件用于记录每个请求的开始和结束,包括请求信息、响应状态、处理时间等有用的数据。

使用方法

基础用法

import (    "go-slim.dev/slim"    "go-slim.dev/slim/middleware")func main() {    s := slim.New()    // 配置错误处理器以记录错误日志    s.ErrorHandler = func(c slim.Context, err error) error {        middleware.LogEnd(c, err)  // 确保错误也被记录        return err    }    // Logger 应该在 Recovery 之前    s.Use(middleware.Logger())    s.Use(slim.Recovery())    s.GET("/", func(c slim.Context) error {        return c.String(200, "Hello, World!")    })    s.Start(":8080")}

自定义配置

s := slim.New()s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{    TimeLayout: "2006-01-02 15:04:05",    NewEntry: func(c slim.Context) middleware.LogEntry {        return middleware.NewLogEntry(os.Stdout)    },}))

配置选项

LoggerConfig

type LoggerConfig struct {    // TimeLayout 定义时间格式    // 可选。默认值 "2006/01/02 15:04:05.000"    TimeLayout string    // NewEntry 为每个请求创建日志条目    // 可选。默认使用标准输出    NewEntry func(slim.Context) LogEntry}

LogEntry 接口

type LogEntry interface {    Begin(c slim.Context) map[string]any    End(c slim.Context, err error) map[string]any    SetTimeLayout(layout string)    SetColorable(colorable bool)    Print(c slim.Context, p LogPayload)    Panic(v any, stack []byte)}

LogPayload 结构

type LogPayload struct {    StartTime  time.Time          // 请求开始时间    Proto      string             // HTTP 协议版本    RequestURI string             // 请求 URI    RawQuery   string             // 查询字符串    Method     string             // HTTP 方法    RemoteAddr string             // 客户端地址    Extra      map[string]any     // 额外数据    Error      error              // 错误信息    StatusCode int                // 响应状态码    Written    int                // 响应字节数    Elapsed    time.Duration      // 处理耗时}

日志输出格式

默认日志输出包含以下信息:

2006/01/02 15:04:05.000 [request-id] "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 1234B in 45ms

日志格式说明

  • 时间戳(可配置格式)
  • 请求 ID(如果存在)
  • HTTP 方法和请求路径
  • 客户端地址
  • 响应状态码(带颜色标识)
  • 响应大小
  • 处理耗时(带颜色标识)

使用示例

1. 基本日志记录

s := slim.New()s.ErrorHandler = func(c slim.Context, err error) error {    middleware.LogEnd(c, err)    return err}s.Use(middleware.Logger())s.GET("/api/users", func(c slim.Context) error {    return c.JSON(200, map[string]string{"message": "success"})})

输出示例

2024/11/08 10:30:15.123 "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 28B in 2.5ms

2. 自定义时间格式

s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{    TimeLayout: "2006-01-02 15:04:05",}))

输出示例

2024-11-08 10:30:15 "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 28B in 2.5ms

3. 自定义日志条目

type customLogEntry struct {    *middleware.defaultLogEntry    logger *log.Logger}func (c *customLogEntry) Begin(ctx slim.Context) map[string]any {    // 在请求开始时记录额外信息    return map[string]any{        "user_agent": ctx.Request().UserAgent(),    }}func (c *customLogEntry) End(ctx slim.Context, err error) map[string]any {    // 在请求结束时记录额外信息    extra := make(map[string]any)    if userID := ctx.Get("user_id"); userID != nil {        extra["user_id"] = userID    }    return extra}s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{    NewEntry: func(c slim.Context) middleware.LogEntry {        return &customLogEntry{            logger: log.New(os.Stdout, "", 0),        }    },}))

4. 添加请求 ID

s.Use(middleware.RequestID()) // 添加请求 ID 中间件s.Use(middleware.Logger())    // Logger 会自动记录请求 IDs.GET("/api/users", func(c slim.Context) error {    return c.JSON(200, map[string]string{"message": "success"})})

输出示例

2024/11/08 10:30:15.123 [abc123-def456] "GET /api/users HTTP/1.1" from 127.0.0.1:54321 - 200 28B in 2.5ms

5. 记录错误信息

s.ErrorHandler = func(c slim.Context, err error) error {    middleware.LogEnd(c, err)  // 记录错误    return c.JSON(500, map[string]string{"error": err.Error()})}s.Use(middleware.Logger())s.GET("/api/error", func(c slim.Context) error {    return errors.New("something went wrong")})

输出示例

2024/11/08 10:30:15.123 "GET /api/error HTTP/1.1" from 127.0.0.1:54321 - 500 35B in 1.2msSpot a mistake:  something went wrong

6. 记录额外数据

s.Use(middleware.Logger())s.GET("/api/users/:id", func(c slim.Context) error {    // 在日志中添加额外信息    payload, _ := middleware.GetLogPayload(c)    if payload.Extra == nil {        payload.Extra = make(map[string]any)    }    payload.Extra["user_id"] = c.Param("id")    middleware.ProvideLogPayload(c, payload)    return c.JSON(200, map[string]string{"id": c.Param("id")})})

输出示例

2024/11/08 10:30:15.123 "GET /api/users/123 HTTP/1.1" from 127.0.0.1:54321 - 200 15B in 3msAdditional data:  {    "user_id": "123"  }

颜色输出

Logger 中间件在 TTY 环境下会自动使用彩色输出:

  • 状态码颜色

    • 1xx(信息):蓝色
    • 2xx(成功):绿色
    • 3xx(重定向):青色
    • 4xx(客户端错误):黄色
    • 5xx(服务器错误):红色
  • 耗时颜色

    • < 500ms:绿色
    • 500ms - 5s:黄色
    • 5s:红色

工作原理

  1. 请求开始Logger 中间件在 LogBegin() 中记录请求开始时间和基本信息
  2. 处理请求:调用下一个处理器处理请求
  3. 请求结束
    • 如果没有错误,在中间件中调用 LogEnd(c, nil) 记录日志
    • 如果有错误,需要在 ErrorHandler 中调用 LogEnd(c, err) 记录日志
  4. 输出日志Print() 方法格式化并输出日志

中间件顺序

重要:Logger 应该在 Recovery 之前,这样可以确保 panic 也被正确记录:

s.ErrorHandler = func (c slim.Context, err error) error {    middleware.LogEnd(c, err)  // 在错误处理器中记录日志    return err}s.Use(middleware.Logger())    // Logger 在前s.Use(slim.Recovery())        // Recovery 在后

最佳实践

1. 配置错误处理器

s.ErrorHandler = func(c slim.Context, err error) error {    middleware.LogEnd(c, err)    // 根据错误类型返回不同的响应    if he, ok := err.(*slim.HTTPError); ok {        return c.JSON(he.Code, he)    }    return c.JSON(500, map[string]string{"error": "Internal Server Error"})}

2. 使用结构化日志

type structuredLogEntry struct {    logger *slog.Logger}func (s *structuredLogEntry) Print(c slim.Context, p middleware.LogPayload) {    s.logger.Info("request completed",        slog.String("method", p.Method),        slog.String("uri", p.RequestURI),        slog.Int("status", p.StatusCode),        slog.Duration("elapsed", p.Elapsed),        slog.String("remote_addr", p.RemoteAddr),    )}s.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{    NewEntry: func(c slim.Context) middleware.LogEntry {        return &structuredLogEntry{            logger: slog.New(slog.NewJSONHandler(os.Stdout, nil)),        }    },}))

3. 根据环境调整日志级别

func newLogger() slim.MiddlewareFunc {    if os.Getenv("ENV") == "production" {        // 生产环境使用结构化日志        return middleware.LoggerWithConfig(middleware.LoggerConfig{            NewEntry: func(c slim.Context) middleware.LogEntry {                return newStructuredLogger()            },        })    }    // 开发环境使用默认彩色日志    return middleware.Logger()}s.Use(newLogger())

4. 记录慢请求

s.Use(middleware.Logger())s.Use(func(c slim.Context, next slim.HandlerFunc) error {    start := time.Now()    err := next(c)    elapsed := time.Since(start)    // 记录超过 1 秒的慢请求    if elapsed > time.Second {        log.Printf("SLOW REQUEST: %s %s took %v",            c.Request().Method,            c.Request().URL.Path,            elapsed)    }    return err})

高级功能

LogBegin 和 LogEnd

您可以手动控制日志记录:

// 在处理器中手动记录日志s.GET("/api/custom", func(c slim.Context) error {    // 开始记录    middleware.LogBegin(c)    // 处理请求    result := doSomething()    // 结束记录    middleware.LogEnd(c, nil)    return c.JSON(200, result)})

获取和设置日志数据

s.Use(middleware.Logger())s.GET("/api/users", func(c slim.Context) error {    // 获取日志负载    payload, ok := middleware.GetLogPayload(c)    if ok {        // 添加自定义数据        if payload.Extra == nil {            payload.Extra = make(map[string]any)        }        payload.Extra["custom_field"] = "custom_value"        // 更新日志负载        middleware.ProvideLogPayload(c, payload)    }    return c.JSON(200, map[string]string{"message": "success"})})

与其他日志库集成

与 slog 集成

import (    "log/slog"    "go-slim.dev/slim/middleware")type slogEntry struct {    logger *slog.Logger}func (s *slogEntry) Begin(c slim.Context) map[string]any { return nil }func (s *slogEntry) End(c slim.Context, err error) map[string]any { return nil }func (s *slogEntry) SetTimeLayout(layout string) {}func (s *slogEntry) SetColorable(colorable bool) {}func (s *slogEntry) Print(c slim.Context, p middleware.LogPayload) {    attrs := []slog.Attr{        slog.String("method", p.Method),        slog.String("uri", p.RequestURI),        slog.Int("status", p.StatusCode),        slog.Duration("elapsed", p.Elapsed),    }    if p.Error != nil {        s.logger.LogAttrs(context.Background(), slog.LevelError, "request failed", attrs...)    } else {        s.logger.LogAttrs(context.Background(), slog.LevelInfo, "request completed", attrs...)    }}func (s *slogEntry) Panic(v any, stack []byte) {    s.logger.Error("panic recovered",        slog.Any("panic", v),        slog.String("stack", string(stack)))}

参考资料

切换 选择 esc 关闭搜索