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.5ms2. 自定义时间格式
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.5ms3. 自定义日志条目
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.5ms5. 记录错误信息
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 wrong6. 记录额外数据
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:红色
工作原理
- 请求开始:
Logger中间件在LogBegin()中记录请求开始时间和基本信息 - 处理请求:调用下一个处理器处理请求
- 请求结束:
- 如果没有错误,在中间件中调用
LogEnd(c, nil)记录日志 - 如果有错误,需要在
ErrorHandler中调用LogEnd(c, err)记录日志
- 如果没有错误,在中间件中调用
- 输出日志:
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)))}