Kratos源码-Logging

这篇具有很好参考价值的文章主要介绍了Kratos源码-Logging。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、log初始化

上代码:

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", 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自动生成的项目中的main方法。
这里我们追踪两个方法:WithNewStdLogger

首先看一下接口和结构体:

// Logger is a logger interface.
type Logger interface {
	Log(level Level, keyvals ...interface{}) error
}

type logger struct {
	logger    Logger
	prefix    []interface{}
	hasValuer bool
	ctx       context.Context
}

Kratos的接口设计的原则是:越少暴露方法,后期适配具体框架需要改动的越少。
本质上就是低耦合的思想。

NewStdLogger创建了一个stdLogger的实例,stdLogger实现了Logger接口的Log方法,所以stdLogger的实例可以作为Logger接口的实现来返回。

type stdLogger struct {
	log  *log.Logger // 这是标准库中的Logger
	pool *sync.Pool
}

// NewStdLogger new a logger with writer.
func NewStdLogger(w io.Writer) Logger {
	return &stdLogger{
		log: log.New(w, "", 0),
		pool: &sync.Pool{
			New: func() interface{} {
				return new(bytes.Buffer)
			},
		},
	}
}

其中log.New是go标准库中的方法,如下。

// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
	mu        sync.Mutex  // ensures atomic writes; protects the following fields
	prefix    string      // prefix on each line to identify the logger (but see Lmsgprefix)
	flag      int         // properties
	out       io.Writer   // destination for output
	buf       []byte      // for accumulating text to write
	isDiscard atomic.Bool // whether out == io.Discard
}
// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line, or
// after the log header if the Lmsgprefix flag is provided.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {
	l := &Logger{out: out, prefix: prefix, flag: flag}
	if out == io.Discard {
		l.isDiscard.Store(true)
	}
	return l
}

With方法,接受一个Logger,经过封装,再返回一个Logger。

// With with logger fields.
func With(l Logger, kv ...interface{}) Logger {
	c, ok := l.(*logger)
	if !ok {
		return &logger{logger: l, prefix: kv, hasValuer: containsValuer(kv), ctx: context.Background()}
	}
	kvs := make([]interface{}, 0, len(c.prefix)+len(kv))
	kvs = append(kvs, c.prefix...)
	kvs = append(kvs, kv...)
	return &logger{
		logger:    c.logger,
		prefix:    kvs,
		hasValuer: containsValuer(kvs),
		ctx:       c.ctx,
	}
}

到这一步,我们可以把logger理解成一个大logger套小logger的俄罗斯套娃,那么我们再回来看看,接口中的唯一一个方法怎么实现的。

func (c *logger) Log(level Level, keyvals ...interface{}) error {
	kvs := make([]interface{}, 0, len(c.prefix)+len(keyvals))
	kvs = append(kvs, c.prefix...)
	if c.hasValuer {
		bindValues(c.ctx, kvs)
	}
	kvs = append(kvs, keyvals...)
	return c.logger.Log(level, kvs...)
}

logger经过一些预处理,调用子logger的Log,所以如开头的代码所示,最终会调用到stdLogger的Log实现,上代码:

// Log print the kv pairs log.
func (l *stdLogger) Log(level Level, keyvals ...interface{}) error {
	if len(keyvals) == 0 {
		return nil
	}
	if (len(keyvals) & 1) == 1 {
		keyvals = append(keyvals, "KEYVALS UNPAIRED")
	}
	buf := l.pool.Get().(*bytes.Buffer)
	buf.WriteString(level.String())
	for i := 0; i < len(keyvals); i += 2 {
		_, _ = fmt.Fprintf(buf, " %s=%v", keyvals[i], keyvals[i+1])
	}
	_ = l.log.Output(4, buf.String()) //nolint:gomnd
	buf.Reset()
	l.pool.Put(buf)
	return nil
}

到这里就好理解了,经过层层调用,进入了go标准库的go/src/log/log.go的Output方法。

_ = l.log.Output(4, buf.String()) //nolint:gomnd 不要做魔法数检查,4表示调用深度

二、log的调用

1.logger注入

上一节有一行代码:

app, cleanup, err := wireApp(bc.Server, bc.Data, logger)

这行代码完成了app的初始化,并将之前生成的logger注入其中。这里用了wire实现了依赖注入,如果是Java转过来,可能更容易理解,但是不管是否理解依赖注入,反正,我们知道,后续的logger是上一步生成的就可以了。

2.引入Helper

// 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.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
	return uc.repo.Save(ctx, g)
}

经过连续的注入,这一步进入了HTTP的handler中,首先看到NewGreeterUsecase方法通过NewHelper方法,将logger转成了log.Helper。

// Helper is a logger helper.
type Helper struct {
	logger  Logger
	msgKey  string
	sprint  func(...interface{}) string
	sprintf func(format string, a ...interface{}) string
}

进一步,CreateGreeter方法在打印日志时,调用Helper的WithContext方法,获取了一个带有Context的Helper。
代码如下:

// WithContext returns a shallow copy of h with its context changed
// to ctx. The provided ctx must be non-nil.
func (h *Helper) WithContext(ctx context.Context) *Helper {
	return &Helper{
		msgKey:  h.msgKey,
		logger:  WithContext(ctx, h.logger),
		sprint:  h.sprint,
		sprintf: h.sprintf,
	}
}

Infof方法,也是Helper结构体的方法,由于Helper还实现了Log方法,所以Helper也是Logger接口的一个实现,所以h.logger.Log会经过一系列调用最终变成调用go标准库的go/src/log/log.go的Output方法。注意,这里所说的是在这个示例程序,由于开始的时候调用NewStdLogger创建了标准logger,很容易想到,如果我们最初的log.With中传入的是三方库的接口,那么这里是不是就是别的实现了。

现在我们思考一个问题,Kratos源码-Java中的日志框架一文中我们看过Java的两种主流日志门面,那么Kratos的日志更接近哪一种呢?下一节我们继续分析。

// Log Print log by level and keyvals.
func (h *Helper) Log(level Level, keyvals ...interface{}) {
	_ = h.logger.Log(level, keyvals...)
}
// Infof logs a message at info level.
func (h *Helper) Infof(format string, a ...interface{}) {
	_ = h.logger.Log(LevelInfo, h.msgKey, h.sprintf(format, a...))
}

三、集成三方框架

适配实现 我们已经在contrib/log实现好了一些插件,用于适配目前常用的日志库,您也可以参考它们的代码来实现自己需要的日志库的适配:

std 标准输出,Kratos内置 fluent 输出到fluentd zap 适配了uber的zap日志库 aliyun 输出到阿里云日志

下面我们分析一下zap的实现吧。
先看使用:

func TestLogger(t *testing.T) {
	syncer := &testWriteSyncer{}
	encoderCfg := zapcore.EncoderConfig{
		MessageKey:     "msg",
		LevelKey:       "level",
		NameKey:        "logger",
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.StringDurationEncoder,
	}
	core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), syncer, zap.DebugLevel)
	zlogger := zap.New(core).WithOptions()
	logger := NewLogger(zlogger)

	defer func() { _ = logger.Close() }()

	zlog := log.NewHelper(logger)

	zlog.Debugw("log", "debug")
	zlog.Infow("log", "info")
	zlog.Warnw("log", "warn")
	zlog.Errorw("log", "error")
	zlog.Errorw("log", "error", "except warn")

	except := []string{
		"{\"level\":\"debug\",\"msg\":\"\",\"log\":\"debug\"}\n",
		"{\"level\":\"info\",\"msg\":\"\",\"log\":\"info\"}\n",
		"{\"level\":\"warn\",\"msg\":\"\",\"log\":\"warn\"}\n",
		"{\"level\":\"error\",\"msg\":\"\",\"log\":\"error\"}\n",
		"{\"level\":\"warn\",\"msg\":\"Keyvalues must appear in pairs: [log error except warn]\"}\n",
	}
	for i, s := range except {
		if s != syncer.output[i] {
			t.Logf("except=%s, got=%s", s, syncer.output[i])
			t.Fail()
		}
	}
}

其中可以看到熟悉的影子,NewLogger生成logger,NewHelper把logger封装成Helper。
马上我们可以想到,如果zap.Logger必然实现了Logger接口中的Log方法,上代码:

func (l *Logger) Log(level log.Level, keyvals ...interface{}) error {
	keylen := len(keyvals)
	if keylen == 0 || keylen%2 != 0 {
		l.log.Warn(fmt.Sprint("Keyvalues must appear in pairs: ", keyvals))
		return nil
	}

	data := make([]zap.Field, 0, (keylen/2)+1)
	for i := 0; i < keylen; i += 2 {
		data = append(data, zap.Any(fmt.Sprint(keyvals[i]), keyvals[i+1]))
	}

	switch level {
	case log.LevelDebug:
		l.log.Debug("", data...)
	case log.LevelInfo:
		l.log.Info("", data...)
	case log.LevelWarn:
		l.log.Warn("", data...)
	case log.LevelError:
		l.log.Error("", data...)
	case log.LevelFatal:
		l.log.Fatal("", data...)
	}
	return nil
}

到这里就清楚了,Log方法通过level决定了走zap.log的具体分支,也就是Debug、Info、Warn、Error、Fatal。


总结

到此,Kratos源码的Logging部分就分析完了。最后,插个题外话吧。打印日志应该遵循什么原则。

三要:

  • HTTP、RPC的入参和返回值
  • 程序异常原因
  • 特殊条件分支

五不要

  • 避免大量数据
  • 避免循环
  • 避免无意义
  • 避免什么也说明不了
  • 避免私密

好的日志包括什么:
级别-内容-时间-进程名称-类方法名-行号-异常堆栈文章来源地址https://www.toymoban.com/news/detail-683704.html

到了这里,关于Kratos源码-Logging的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • go-zero的服务发现源码阅读

    服务发现原理与grpc源码解析_wangxiaoangg的博客-CSDN博客   go-zero rpc demo官方文档:rpc编写与调用 | go-zero 目录 一 服务注册 1. 创建rpc服务 2. 启动rpc服务 3. registerEtcd做了什么 4. discov.NewPublisher 服务发布者 二 服务发现 1.定义注册resolver 2.解析etcd地址创建链接 3.update方法 在看rp

    2024年02月06日
    浏览(62)
  • Go语言网络编程:HTTP服务端之底层原理与源码分析——http.HandleFunc()、http.ListenAndServe()

    在 Golang只需要几行代码便能启动一个 http 服务,在上述代码中,完成了两件事: 调用 http.HandleFunc 方法,注册了对应于请求路径 /ping 的 handler 函数 调用 http.ListenAndServe,启动了一个端口为 8999 的 http 服务 2.1 server 结构 Addr :表示服务器监听的地址。如\\\":8080\\\"表示服务器在本地

    2024年02月08日
    浏览(62)
  • mac上 Kratos 配置 protoc

    protoc 是 protobuf 文件(.proto)的编译器,可以借助这个工具把 .proto 文件转译成各种编程语言对应的源码,包含数据类型定义、调用接口等。 protoc 在设计上把 protobuf 和不同的语言解耦了,底层用 c++ 来实现 protobuf 结构的存储,然后通过插件的形式来生成不同语言的源码。 可以把

    2024年02月10日
    浏览(37)
  • Go Ethereum源码学习笔记000

    这个专栏的内容是免费的,因为自己这边都是基于开源库和开源内容整理的学习笔记,在这个过程中进行增删改查,将自己的理解融入其中,所以这里用开源的精神分享给大家:Free software, free knowledge。当然,开源精神和软件付费/知识付费并不冲突,而是求同存异。大家觉

    2024年02月14日
    浏览(34)
  • go语言:腾讯终于开源trpc框架——对trpc-go源码分析

    根据以上,我们对trpc框架进行三个方面的讲解 trpc配置文件采用yaml格式,文件默认目录在 ./trpc_go.yaml下,所有自定义配置都需要写在这个yaml文件中,所有支持用户自定义的配置都可以参考结构体config 简单来说干了以下几件事情 1.获取配置文件的默认目录,并且解析目录到

    2024年02月03日
    浏览(50)
  • Go语言实现跳动的爱心(附带源码)

    在 Go 语言中,你可以使用 github.com/fogleman/gg 包来实现动态的爱心效果。以下是一个简单的例子: 在这个例子中,我们使用 github.com/fogleman/gg 包创建一个图形上下文,并在一个无限循环中绘制一个动态的爱心。每一帧都会保存为 PNG 图像文件,以便后续制作成动画。 要运行这

    2024年01月23日
    浏览(47)
  • 沉浸式go-cache源码阅读!

    大家好,我是豆小匠。 这期来阅读go-cache的源码,了解本地缓存的实现方式,同时掌握一些阅读源码的技巧~ 用Goland打开可以看到真正实现功能的也就两个go文件,cache.go 1162行,sharded.go 193行,共1355行,用来作为源码阅读的练手素材是非常合适的。 通过README.md文件,可以了解

    2024年02月04日
    浏览(37)
  • go中读写锁(rwmutex)源码解读实现原理

    1、RWMutex读写锁的概念 读写锁也就是我们所使用的RWMutex,其实是对于go本身的mutex做的一个拓展,当一个goroutine获得了读锁后,其他goroutine同样可以获得读锁,但是不能获得写锁。相反,当一个goroutine获得了写锁,其他goroutine既不能读也不能写,互斥的概念。 2、使用场景 适

    2024年02月11日
    浏览(42)
  • 从案例中详解go-errgroup-源码

    某次会议上发表了error group包,一个g失败,其他的g会同时失败的错误言论(看了一下源码中的一句话 The first call to return a non-nil error cancels the group ,没进一步看其他源码,片面理解了)。 后面想想好像不对,就再次深入源码,并进行验证! 官方介绍:包 errgroup 为处理公共任务

    2024年02月02日
    浏览(35)
  • Go语言实现空中飘雪花的效果(附带源码)

    在 Go 语言中,你可以使用 github.com/fogleman/gg 包来实现空中飘雪花的效果。首先,确保你已经安装了该包: 然后,可以使用以下 Go 代码: 这个程序使用 github.com/fogleman/gg 包创建了一个绘图上下文,模拟了飘雪花的效果。运行程序后,它将每一帧保存为 PNG 图像文件,并以 fr

    2024年01月25日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包