kratos日志文件分割
一、背景和意义
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/
目录下有很多分割后的日志文件:
如果解压上图中的任意一个*.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