likes
comments
collection
share

Kratos的http服务日志增加traceId

作者站长头像
站长
· 阅读数 3

一、背景和意义

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

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

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

go install github.com/go-kratos/kratos/cmd/kratos/v2@latest

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

kratos new kratos-demo -r https://gitee.com/go-kratos/kratos-layout.git

将相关依赖下载到本地:

go mod download

生成代码:

go generate ./...

运行代码:

kratos run

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

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

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这个文件中的如下代码:

// 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

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,将其去掉:

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是一致的:

// 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'两次,可看到如下日志输出:

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