Goslog日志打印最佳实践案例

Written by

in

文章目录
  • Go 1.21 引入了 log/slog 包,为 Go 语言带来了原生的结构化日志解决方案。与传统的简单日志包或第三方库相比,slog 提供了更强大、更灵活的日志记录能力。本文将深入探讨如何使用 slog 实现高效、可靠的日志记录。
  • slog 围绕三个核心类型构建: Logger – 用户交互的前端接口 Handler – 实际处理日志的后端 Record – 在前后端之间传递的日志数据 // 创建使用 JSON 格式的 Logger logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) // 创建记录并传递给 Handler logger.Info(“user logged in”, “user_id”, 123) 输出结果: {  "time": "…",  "level": "INFO",  "msg": "user logged in",  "user_id": 123}
  • slog 包为 Go 应用提供了现代化的结构化日志解决方案。通过遵循这些最佳实践,你可以构建出既高效又可靠的日志系统,为应用的可观测性和故障排查提供有力支持。好的日志实践不仅能帮助你快速定位问题,还能在不影响性能的前提下提供丰富的上下文信息。 到此这篇关于Go slog 日志打印最佳实践指南的文章就介绍到这了,更多相关Go slog 日志打印内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Go 1.21.0 新增结构化日志记录标准库log/slog使用详解 golang结构化日志log/slog包之slog.Record的用法简介 golang结构化日志log/slog包之LogValuer的用法简介 Go语言结构化日志slog的用法解析 golang结构化日志slog的用法简介 go1.21中slog日志包用法入门 Golang结构化日志包log/slog的使用详解
  • 目录
    • 引言
    • 核心概念
    • 最佳实践
      • 1. 使用强类型属性
      • 2. 使用 linter 强制一致性
      • 3. 合理设置日志级别
      • 4. 全局日志器配置
      • 5. 上下文传递日志器
      • 6. 敏感信息处理
      • 7. 性能优化
      • 8. 自定义 Handler
      • 9. 结构化错误处理
      • 10. 测试日志输出
    • 总结

      Go 1.21 引入了 log/slog 包,为 Go 语言带来了原生的结构化日志解决方案。与传统的简单日志包或第三方库相比,slog 提供了更强大、更灵活的日志记录能力。本文将深入探讨如何使用 slog 实现高效、可靠的日志记录。

      slog 围绕三个核心类型构建:

      1. Logger – 用户交互的前端接口
      2. Handler – 实际处理日志的后端
      3. Record – 在前后端之间传递的日志数据
      // 创建使用 JSON 格式的 Logger
      logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
      // 创建记录并传递给 Handler
      logger.Info("user logged in", "user_id", 123)

      输出结果:

      {
        "time": "…",
        "level": "INFO",
        "msg": "user logged in",
        "user_id": 123
      }

      避免使用易出错的键值对方式:

      // 容易出错 - 缺少值时会产生 !BADKEY
      logger.Warn("permission denied", "user_id", 12345, "resource")

      推荐使用类型安全的 slog.Attr

      logger.Warn("permission denied",
          slog.Int("user_id", 12345),
          slog.String("resource", "/api/admin"))

      通过 sloglint 确保代码库中的日志风格一致:

      # 安装 sloglint
      go install github.com/ettle/strcase/cmd/sloglint@latest
      # 运行检查
      sloglint ./...

      配置 .sloglintrc 文件来定义团队规范:

      # 强制使用 slog.Attr 而非原始键值对
      require-typed-attrs: true
      # 要求所有日志消息使用小写
      lowercase-messages: true
      # 禁止在日志中记录敏感信息
      forbidden-keys: ["password", "token", "secret"]

      根据日志的重要性选择合适的级别:

      // DEBUG - 详细调试信息,仅在开发/调试时启用
      logger.Debug("database query executed", slog.String("query", sql))
      // INFO - 常规操作信息
      logger.Info("user created", slog.Int("user_id", userID))
      // WARN - 异常情况但不影响系统运行
      logger.Warn("cache miss", slog.String("key", cacheKey))
      // ERROR - 错误情况需要关注
      logger.Error("database connection failed", slog.String("error", err.Error()))
      // Custom levels for business logic
      const (
          LevelTrace = slog.Level(-8)
          LevelFatal = slog.Level(12)
      )

      在应用启动时配置全局日志器:

      func init() {
          opts := &slog.HandlerOptions{
              Level:     slog.LevelInfo, // 生产环境通常使用 Info 级别
              AddSource: false,          // 生产环境关闭源码位置以提高性能
          }
          // 根据环境选择处理器
          var handler slog.Handler
          if os.Getenv("ENV") == "development" {
              handler = slog.NewTextHandler(os.Stdout, opts)
          } else {
              handler = slog.NewJSONHandler(os.Stdout, opts)
          }
          slog.SetDefault(slog.New(handler))
      }

      在 HTTP 处理器或服务方法中传递带有上下文信息的日志器:

      func handleRequest(w http.ResponseWriter, r *http.Request) {
          // 为当前请求创建带上下文的日志器
          requestLogger := slog.With(
              slog.String("request_id", getRequestID(r)),
              slog.String("user_agent", r.UserAgent()),
              slog.String("method", r.Method),
              slog.String("path", r.URL.Path),
          )
          // 在处理逻辑中使用上下文日志器
          if err := processRequest(r, requestLogger); err != nil {
              requestLogger.Error("request processing failed", slog.String("error", err.Error()))
              http.Error(w, "Internal Server Error", http.StatusInternalServerError)
              return
          }
          requestLogger.Info("request processed successfully")
      }

      永远不要在日志中记录敏感信息:

      // ❌ 错误做法
      logger.Info("user login", 
          slog.String("email", user.Email),
          slog.String("password", user.Password)) // 危险!
      // ✅ 正确做法
      logger.Info("user login",
          slog.String("email", maskEmail(user.Email)),
          slog.Bool("success", loginSuccess))
      func maskEmail(email string) string {
          parts := strings.Split(email, "@")
          if len(parts) != 2 {
              return "invalid-email"
          }
          username := parts[0]
          if len(username) <= 2 {
              return "**@" + parts[1]
          }
          masked := username[:2] + strings.Repeat("*", len(username)-2)
          return masked + "@" + parts[1]
      }

      避免在日志记录中执行昂贵操作:

      // ❌ 低效 - 即使日志级别不够也会执行序列化
      logger.Debug("expensive data", slog.Any("data", expensiveSerialization()))
      // ✅ 高效 - 使用惰性求值
      if logger.Enabled(context.Background(), slog.LevelDebug) {
          logger.Debug("expensive data", slog.Any("data", expensiveSerialization()))
      }
      // 或者使用 slog.Group 进行批量属性处理
      logger.Info("user profile",
          slog.Group("profile",
              slog.String("name", user.Name),
              slog.Int("age", user.Age),
              slog.String("city", user.City),
          ))

      为特殊需求创建自定义 Handler:

      type FilteringHandler struct {
          slog.Handler
          filter func(slog.Record) bool
      }
      func (h *FilteringHandler) Handle(ctx context.Context, r slog.Record) error {
          if h.filter(r) {
              return h.Handler.Handle(ctx, r)
          }
          return nil
      }
      // 使用示例:过滤掉包含特定关键词的日志
      filterHandler := &FilteringHandler{
          Handler: slog.NewJSONHandler(os.Stdout, nil),
          filter: func(r slog.Record) bool {
              return !strings.Contains(r.Message, "heartbeat")
          },
      }
      logger := slog.New(filterHandler)

      将错误信息结构化记录:

      // 自定义错误类型
      type AppError struct {
          Code    string
          Message string
          Cause   error
      }
      func (e *AppError) Error() string {
          return e.Message
      }
      // 记录结构化错误
      if err != nil {
          var appErr *AppError
          if errors.As(err, &appErr) {
              logger.Error("application error",
                  slog.String("error_code", appErr.Code),
                  slog.String("error_message", appErr.Message),
                  slog.String("cause", appErr.Cause.Error()))
          } else {
              logger.Error("unexpected error", slog.String("error", err.Error()))
          }
      }

      在单元测试中验证日志行为:

      func TestUserService_CreateUser(t *testing.T) {
          var buf bytes.Buffer
          logger := slog.New(slog.NewJSONHandler(&buf, nil))
          service := NewUserService(logger)
          err := service.CreateUser(context.Background(), "test@example.com")
          assert.NoError(t, err)
          // 验证日志内容
          var logRecord map[string]interface{}
          err = json.Unmarshal(buf.Bytes(), &logRecord)
          assert.NoError(t, err)
          assert.Equal(t, "user created", logRecord["msg"])
          assert.Equal(t, "test@example.com", logRecord["email"])
      }

      slog 包为 Go 应用提供了现代化的结构化日志解决方案。通过遵循这些最佳实践,你可以构建出既高效又可靠的日志系统,为应用的可观测性和故障排查提供有力支持。好的日志实践不仅能帮助你快速定位问题,还能在不影响性能的前提下提供丰富的上下文信息。

      到此这篇关于Go slog 日志打印最佳实践指南的文章就介绍到这了,更多相关Go slog 日志打印内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

      您可能感兴趣的文章:

      • Go 1.21.0 新增结构化日志记录标准库log/slog使用详解
      • golang结构化日志log/slog包之slog.Record的用法简介
      • golang结构化日志log/slog包之LogValuer的用法简介
      • Go语言结构化日志slog的用法解析
      • golang结构化日志slog的用法简介
      • go1.21中slog日志包用法入门
      • Golang结构化日志包log/slog的使用详解

      站内搜索