likes
comments
collection
share

kratos日志文件分割

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

一、背景和意义

kratos的官方文档中关于日志的内容不多,基本都是在控制台上输出日志。在实际应用中往往需要把日志写到文件中,按日期和内容大小分割、压缩旧日志、仅保留最近若干天的数据。本文给出kratos日志分割的例子。

二、kratos项目搭建

执行如下命令创建一个简单的kratos项目:

kratos new kratos-demo -r https://gitee.com/go-kratos/kratos-layout.git
cd kratos-demo
make generate
kratos run

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

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

INFO ts=2024-03-31T18:00:28+08:00 caller=biz/greeter.go:44 service.id=xxxxxx service.name= service.version= trace.id= span.id= msg=CreateGreeter: kratos

三、使用lumberjack实现日志分割

修改main.go文件的内容,引入lumberjack实现日志分割:

package main

import (
   "flag"
   "os"

   "kratos-demo/internal/conf"

   "github.com/go-kratos/kratos/v2"
   "github.com/go-kratos/kratos/v2/config"
   "github.com/go-kratos/kratos/v2/config/file"
   "github.com/go-kratos/kratos/v2/log"
   "github.com/go-kratos/kratos/v2/transport/grpc"
   "github.com/go-kratos/kratos/v2/transport/http"
   _ "go.uber.org/automaxprocs"
   lumberjack "gopkg.in/natefinch/lumberjack.v2"
)

// go build -ldflags "-X main.Version=x.y.z"
var (
   // Name is the name of the compiled software.
   Name string
   // Version is the version of the compiled software.
   Version string
   // flagconf is the config flag.
   flagconf string

   id, _ = os.Hostname()
)

func init() {
   flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml")
}

func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server) *kratos.App {
   return kratos.New(
      kratos.ID(id),
      kratos.Name(Name),
      kratos.Version(Version),
      kratos.Metadata(map[string]string{}),
      kratos.Logger(logger),
      kratos.Server(
         gs,
         hs,
      ),
   )
}

func main() {
   flag.Parse()
   fileWriter := &lumberjack.Logger{
      Filename: "./log/main.log",
      MaxSize:  1,    // 单个文件的大小,单位为MB,这里为了验证分割效果设置得比较小
      MaxAge:   28,   // 保存多少天的日志
      Compress: true, // 是否启用压缩,默认为不启用
   }
   logger := log.With(log.NewStdLogger(fileWriter),
      "ts", log.DefaultTimestamp,
      "caller", log.DefaultCaller,
      "service.id", id,
      "service.name", Name,
      "service.version", Version,
      "trace.id", tracing.TraceID(),
      "span.id", tracing.SpanID(),
   )
   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)
   }
}

然后执行命令:

go mod tidy
kratos run

再访问http://localhost:8000/helloworld/kratos 可以看到日志写到了cmd/kratos-demo/log/main.log文件中,其内容为:

INFO ts=2024-03-31T18:08:37+08:00 caller=http/server.go:302 service.id=xxxxxx service.name= service.version= trace.id= span.id= msg=[HTTP] server listening on: [::]:8000
INFO ts=2024-03-31T18:08:37+08:00 caller=grpc/server.go:205 service.id=xxxxxx service.name= service.version= trace.id= span.id= msg=[gRPC] server listening on: [::]:9000
INFO ts=2024-03-31T18:08:51+08:00 caller=biz/greeter.go:44 service.id=xxxxxx service.name= service.version= trace.id= span.id= msg=CreateGreeter: kratos

为了验证日志分割的效果,我们修改biz/greeter.go文件的内容,在CreateGreeter方法中调用testLog 方法,testLog方法一次性输出10万行日志:

package biz

import (
   "context"

   v1 "kratos-demo/api/helloworld/v1"

   "github.com/go-kratos/kratos/v2/errors"
   "github.com/go-kratos/kratos/v2/log"
)

var (
   // ErrUserNotFound is user not found.
   ErrUserNotFound = errors.NotFound(v1.ErrorReason_USER_NOT_FOUND.String(), "user not found")
)

// Greeter is a Greeter model.
type Greeter struct {
   Hello string
}

// GreeterRepo is a Greater repo.
type GreeterRepo interface {
   Save(context.Context, *Greeter) (*Greeter, error)
   Update(context.Context, *Greeter) (*Greeter, error)
   FindByID(context.Context, int64) (*Greeter, error)
   ListByHello(context.Context, string) ([]*Greeter, error)
   ListAll(context.Context) ([]*Greeter, error)
}

// GreeterUsecase is a Greeter usecase.
type GreeterUsecase struct {
   repo GreeterRepo
   log  *log.Helper
}

// NewGreeterUsecase new a Greeter usecase.
func NewGreeterUsecase(repo GreeterRepo, logger log.Logger) *GreeterUsecase {
   return &GreeterUsecase{repo: repo, log: log.NewHelper(logger)}
}

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

func (uc *GreeterUsecase) testLog() {
   for i := 0; i < 100000; i++ {
      uc.log.Infof("test log, num: %d", i)
   }
}

重新运行kratos run,再访问http://localhost:8000/helloworld/kratos,可以看到cmd/kratos-demo/log/目录下有很多分割后的日志文件: kratos日志文件分割

如果解压上图中的任意一个*.log.gz文件,可以看到其大于刚好是1MB。

四、日志同时输出到控制台和文件

在本地调试时,一般需要将日志输出到控制台,否则很不方便。我们也可以修改main.go,使用io.MultiWriter同时将日志输出到控制台和本地文件:

package main

import (
   "flag"
   "github.com/go-kratos/kratos/v2/middleware/tracing"
   "io"
   "os"

   "kratos-demo/internal/conf"

   "github.com/go-kratos/kratos/v2"
   "github.com/go-kratos/kratos/v2/config"
   "github.com/go-kratos/kratos/v2/config/file"
   "github.com/go-kratos/kratos/v2/log"
   "github.com/go-kratos/kratos/v2/transport/grpc"
   "github.com/go-kratos/kratos/v2/transport/http"
   _ "go.uber.org/automaxprocs"
   lumberjack "gopkg.in/natefinch/lumberjack.v2"
)

// go build -ldflags "-X main.Version=x.y.z"
var (
   // Name is the name of the compiled software.
   Name string
   // Version is the version of the compiled software.
   Version string
   // flagconf is the config flag.
   flagconf string

   id, _ = os.Hostname()
)

func init() {
   flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml")
}

func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server) *kratos.App {
   return kratos.New(
      kratos.ID(id),
      kratos.Name(Name),
      kratos.Version(Version),
      kratos.Metadata(map[string]string{}),
      kratos.Logger(logger),
      kratos.Server(
         gs,
         hs,
      ),
   )
}

func main() {
   flag.Parse()
   fileWriter := &lumberjack.Logger{
      Filename: "./log/main.log",
      MaxSize:  1,    // megabytes
      MaxAge:   28,   //days
      Compress: true, // disabled by default
   }
   logger := log.With(log.NewStdLogger(io.MultiWriter(fileWriter, os.Stdout)),
      "ts", log.DefaultTimestamp,
      "caller", log.DefaultCaller,
      "service.id", id,
      "service.name", Name,
      "service.version", Version,
      "trace.id", tracing.TraceID(),
      "span.id", tracing.SpanID(),
   )
   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)
   }
}

重启运行kratos run,可以看到日志同时也输出到控制台中。

转载自:https://juejin.cn/post/7352387633765089314
评论
请登录