Kratos的http服务日志增加traceId

一、背景和意义

kratos是go语言中常用的微服务框架,该框架自带有log组件。在打印日志时如果输出traceId就可以方便地追踪同一请求的日志,kratos官方的traceId相关示例比较复杂,对于一个简单的、不涉及大量上下游服务的提供给前端页面调用的http服务,文本提供一个更简单使用示例。

二、创建运行简单的kratos应用

执行如下命令安装kratos工具:

bash 复制代码
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest

接下来创建一个简单项目:

bash 复制代码
kratos new kratos-demo -r https://gitee.com/go-kratos/kratos-layout.git

将相关依赖下载到本地:

bash 复制代码
go mod download

生成代码:

bash 复制代码
go generate ./...

运行代码:

bash 复制代码
kratos run

用浏览器打开链接:http://localhost:8000/helloworld/kratos'。正常情况下会返回:{"message":"Hello kratos"}

同时,服务器上会输出如下一行日志:

log 复制代码
INFO ts=2024-03-03T18:10:58+08:00 caller=biz/greeter.go:43 service.id=xxxxxx service.name= service.version= trace.id= span.id= msg=CreateGreeter: kratos

对应的打印日志的代码是在internal/biz/greeter.go这个文件中的如下代码:

go 复制代码
// CreateGreeter creates a Greeter, and returns the new Greeter.
func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {
   uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
   return uc.repo.Save(ctx, g)
}

在前面输出的日志中,trace.id还是空的。

三、增加traceId输出

修改internal/server/http.go文件,添加一个增加traceid的中间件方法simpleTraceHandler

go 复制代码
func simpleTraceHandler(handler middleware.Handler) middleware.Handler {
   return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
      ctx = context.WithValue(ctx, "simpleTraceId", uuid.NewString())
      return handler(ctx, req)
   }
}

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *http.Server {
   var opts = []http.ServerOption{
      http.Middleware(
         recovery.Recovery(),
         simpleTraceHandler,
      ),
   }
   if c.Http.Network != "" {
      opts = append(opts, http.Network(c.Http.Network))
   }
   if c.Http.Addr != "" {
      opts = append(opts, http.Address(c.Http.Addr))
   }
   if c.Http.Timeout != nil {
      opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
   }
   srv := http.NewServer(opts...)
   v1.RegisterGreeterHTTPServer(srv, greeter)
   return srv
}

simpleTraceHandler方法使用uuid作为traceId。

接下来修改cmd/kratos-demo/main.go的代码,增加一个traceId函数用于获取前面中间件设置的traceId,另外简单应用暂时不需要输出spanId,将其去掉:

go 复制代码
func traceId() log.Valuer {
   return func(ctx context.Context) interface{} {
      traceId := ctx.Value("simpleTraceId")
      if traceId == nil {
         return ""
      } else {
         return traceId
      }
   }
}

func main() {
   flag.Parse()
   logger := log.With(log.NewStdLogger(os.Stdout),
      "ts", log.DefaultTimestamp,
      "caller", log.DefaultCaller,
      "service.id", id,
      "service.name", Name,
      "service.version", Version,
      "trace.id", traceId(),
   )
   c := config.New(
      config.WithSource(
         file.NewSource(flagconf),
      ),
   )
   defer c.Close()

   if err := c.Load(); err != nil {
      panic(err)
   }

   var bc conf.Bootstrap
   if err := c.Scan(&bc); err != nil {
      panic(err)
   }

   app, cleanup, err := wireApp(bc.Server, bc.Data, logger)
   if err != nil {
      panic(err)
   }
   defer cleanup()

   // start and wait for stop signal
   if err := app.Run(); err != nil {
      panic(err)
   }
}

为了验证,我们在internal/service/greeter.go中也增加一行日志输入,以确认同一请求的traceId是一致的:

go 复制代码
// GreeterService is a greeter service.
type GreeterService struct {
   v1.UnimplementedGreeterServer

   uc  *biz.GreeterUsecase
   log *log.Helper
}

// NewGreeterService new a greeter service.
func NewGreeterService(uc *biz.GreeterUsecase, logger log.Logger) *GreeterService {
   return &GreeterService{uc: uc, log: log.NewHelper(logger)}
}

// SayHello implements helloworld.GreeterServer.
func (s *GreeterService) SayHello(ctx context.Context, in *v1.HelloRequest) (*v1.HelloReply, error) {
   g, err := s.uc.CreateGreeter(ctx, &biz.Greeter{Hello: in.Name})
   if err != nil {
      return nil, err
   }
   s.log.WithContext(ctx).Info("Say Hello get Greeter: %v", g)
   return &v1.HelloReply{Message: "Hello " + g.Hello}, nil
}

改好上述代码之后,运行go generate ./...kratos run,访问http://localhost:8000/helloworld/kratos'两次,可看到如下日志输出:

log 复制代码
INFO ts=2024-03-03T18:24:01+08:00 caller=biz/greeter.go:43 service.id=xxxxxx service.name= service.version= trace.id=91ad483f-d724-4055-906d-34d255c93b01 msg=CreateGreeter: kratos
INFO ts=2024-03-03T18:24:01+08:00 caller=service/greeter.go:30 service.id=xxxxxx service.name= service.version= trace.id=91ad483f-d724-4055-906d-34d255c93b01 msg=Say Hello get Greeter: %v&{kratos}
INFO ts=2024-03-03T18:24:09+08:00 caller=biz/greeter.go:43 service.id=xxxxxx service.name= service.version= trace.id=c52f4d91-6a7d-4c6d-a69b-7c3f496bc8fe msg=CreateGreeter: kratos
INFO ts=2024-03-03T18:24:09+08:00 caller=service/greeter.go:30 service.id=xxxxxx service.name= service.version= trace.id=c52f4d91-6a7d-4c6d-a69b-7c3f496bc8fe msg=Say Hello get Greeter: %v&{kratos}

第一次请求的traceId为91ad483f-d724-4055-906d-34d255c93b01,而第二次为c52f4d91-6a7d-4c6d-a69b-7c3f496bc8fe

相关推荐
王元_SmallA几秒前
IDEA + Spring Boot 的三种热加载方案
java·后端
LCG元2 分钟前
实战:用 Shell 脚本自动备份网站和数据库,并上传到云存储
后端
Yeats_Liao2 分钟前
时序数据库系列(四):InfluxQL查询语言详解
数据库·后端·sql·时序数据库
清空mega8 分钟前
从零开始搭建 flask 博客实验(常见疑问)
后端·python·flask
白衣鸽子8 分钟前
MySQL数据库的“隐形杀手”:深入理解文件结构与治理数据碎片
数据库·后端·mysql
neoooo13 分钟前
⚙️ Spring Boot × @RequiredArgsConstructor:写出最干净的依赖注入代码
spring boot·后端·spring
Victor35624 分钟前
Redis(111)Redis的持久化机制有哪些?
后端
Victor35626 分钟前
Redis(110)Redis的发布订阅机制如何使用?
后端
Python私教1 小时前
使用 FastAPI 实现文件上传接口:从入门到进阶
后端
Mos_x2 小时前
springboot系列--自动配置原理
java·后端