Kratos源码-Logging

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

文章目录

  • 前言
  • 一、log初始化
  • 二、log的调用
    • 1.logger注入
    • 2.引入Helper
  • 三、集成三方框架
  • 总结
    • 三要:
    • 五不要


前言

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

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


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

一、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.Bootstrapif 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 signalif 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    Loggerprefix    []interface{}hasValuer boolctx       context.Context
}

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

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

type stdLogger struct {log  *log.Logger // 这是标准库中的Loggerpool *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 fieldsprefix    string      // prefix on each line to identify the logger (but see Lmsgprefix)flag      int         // propertiesout       io.Writer   // destination for outputbuf       []byte      // for accumulating text to writeisDiscard 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:gomndbuf.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 GreeterRepolog  *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  LoggermsgKey  stringsprint  func(...interface{}) stringsprintf 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的入参和返回值
  • 程序异常原因
  • 特殊条件分支

五不要

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

好的日志包括什么:
级别-内容-时间-进程名称-类方法名-行号-异常堆栈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/59809.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot】最基础的项目架构(SpringBoot+Mybatis-plus+lombok+knife4j+hutool)

汝之观览&#xff0c;吾之幸也&#xff01; 从本文开始讲下项目中用到的一些框架和技术&#xff0c;最基本的框架使用的是SpringBoot(2.5.10)Mybatis-plus(3.5.3.2)lombok(1.18.28)knife4j(3.0.3)hutool(5.8.21),可以做到代码自动生成&#xff0c;满足最基本的增删查改。 一、新…

java+ssm+mysql农场信息管理系统

项目介绍&#xff1a; 本系统为基于jspssmmysql的农场信息管理系统&#xff0c;功能如下&#xff1a; 用户&#xff1a;注册登录系统&#xff0c;菜地信息管理&#xff0c;农作物信息管理&#xff0c;种植信息管理&#xff0c;客户信息管理&#xff0c;商家信息管理&#xff…

一个简单的vim例子

一.欢迎来到我的酒馆 在本章节介绍vim工具。 目录 一.欢迎来到我的酒馆二.什么是vim 二.什么是vim 2.1什么是vim vim是一种Linux命令行类型 的文本编辑器。vim指的是"vi improved"&#xff0c;意思是vi工具的升级版。vim是基于vi实现的&#xff0c;它提供了…

电工-照明电路施工图

照明电路施工图 上面介绍的电气照明基本电路用作施工的依据是不够的&#xff0c;这是因为图上并没有注明电气元件的规格、型号、安装要求、线路敷设方式以及其他一些特征。作为实际电路安装的依据&#xff0c;必须是根据国家颁布的有关电器技术标准和统一符号绘制的施工图。照…

iOS开发Swift-基本运算符

1.一元、二元、三元运算符 一元单一操作对象-a !b c!二元两个操作对象2 3三元三目运算符a ? b : c 2.赋值运算符() let a 10 var b 5 b a let (x, y) (1, 2)赋值运算符不返回任何值&#xff0c;所以 if x y { ... } 无效。 3.算术运算符 - * / 默认不允许数…

【1day】复现金和协同管理平台任意文件读取漏洞

目录 一、漏洞描述 二、影响版本 三、资产测绘 四、漏洞复现 一、漏洞描述 金和OA是一款企业级办公自动化软件,旨在提供高效的办公流程管理和协作解决方案。它提供了一系列功能和工具,帮助企业实现办公自动化、信息共享和团队协作。金和OA系统存在任意文件读取漏洞,攻…

文本匹配实战系列

引言 本系列文章开始介绍深度学习在文本匹配领域的应用&#xff0c;并且会尝试得到各种模型在给定的数据集上的表现。 深度文本匹配发展比较久&#xff0c;积累了很多文本匹配方法。也有很多的分类方式&#xff0c;一种分类方式是表示型和交互型。 表示型方法 表示型(repre…

kafka架构和原理详解

Apache Kafka 是一个分布式流数据平台,用于高吞吐量、持久性、可扩展的发布和订阅消息。它具有高度的可靠性,被广泛用于构建实时数据流处理、日志收集和数据管道等应用。 基本架构 1. 主题(Topic): 主题是消息的逻辑分类生产者将消息发布到特定的主题中,而消费者可以订阅…

LAMP 配置与应用

LAMP 架构的组成 LAM(M)P&#xff1a; L&#xff1a;linux A&#xff1a;apache (httpd) M&#xff1a;mysql, mariadb P&#xff1a;php, perl, python apache的功能&#xff1a; 第一&#xff1a;处理http的请求、构建响应报文等自身服务&#xff1b; 第二&#xff1a…

Java之API详解之Biginteger类的详解

6 BigInteger类 6.1 引入 平时在存储整数的时候&#xff0c;Java中默认是int类型&#xff0c;int类型有取值范围&#xff1a;-2147483648 ~ 2147483647。如果数字过大&#xff0c;我们可以使用long类型&#xff0c;但是如果long类型也表示不下怎么办呢&#xff1f; 就需要用…

mysql 错误码

一、 #22001 Caused by: java.sql.BatchUpdateException: Data truncation: #22001检查一下数据库表字段&#xff0c;特别是VARCHAR的长度是否够。 Caused by: java.sql.BatchUpdateException: #HY000检查是不是违反了非空约束&#xff0c;NOT NULL字段有没有没传的 二、

voc 转coco

import os import random import shutil import sys import json import glob import xml.etree.ElementTree as ET""" 修改下面3个参数 1.val_files_num : 验证集的数量 2.test_files_num &#xff1a;测试集的数量 3.voc_annotations : voc的annotations路径 …

低代码是什么?能做什么?

2014 年全球权威咨询机构 Forrester 在报告中首次引入了低代码的概念&#xff0c;放眼彼时的中国市场&#xff0c;低代码这一名词还鲜为人知。随着国家积极推动数字化发展&#xff0c;越来越多的企业投入到了数字化经济的建设中&#xff0c;低代码也在这样的大环境中快速成长。…

Log4j2 配置日志记录发送到 kafka 中

前言 log4j2 在 2.11.0 之后的版本&#xff0c;已经内置了 KafkaAppender 支持可以将打印的日志直接发送到 kafka 中&#xff0c;在这之前如果想要集中收集应用的日志&#xff0c;就需要自定义一个 Layout 来实现&#xff0c;相对来说还是比较麻烦的。 官网文档&#xff1a;L…

gitcode中删除已有的项目

镜像地址&#xff1a; https://www.jianshu.com/p/504c1418adb7?v1693021320653 扩展阅读 如何在GitLab中删除一个项目 https://www.codenong.com/cs106866762/ 简介&#xff1a; 如何在GitLab中删除一个项目 最近GIT上建了太多项目。想清一下&#xff0c;就在网上查了查…

vue实现把字符串中的所有@内容,替换成带标签的

前言&#xff1a; 目前有个需求是&#xff0c;要把输入框里面的还有姓名高亮。 要求&#xff1a; 1、必须用 v-html ,带标签的给他渲染 2、把字符串中的全部查找出来&#xff0c;替换掉&#xff0c;注意要过滤已经替换好的&#xff0c;不然就是无限循环了 实现方法&#xff1a…

面向对象的设计原则

设计模式 Python 设计模式&#xff1a;对软件设计中普遍存在&#xff08;反复出现&#xff09;的各种问题&#xff0c;所提出的解决方案。每一个设计模式系统地命名、解释和评价了面向对象系统中一个重要的和重复出现的设计 面向对象 三大特性&#xff1a;封装、继承、多态 …

多线程应用——单例模式

单例模式 文章目录 单例模式一.什么是单例模式二.如何实现1.口头实现2.利用语法特性 三.实现方式&#xff08;饿汉式懒汉式&#xff09;1.饿汉式2.懒汉式3.线程安全的单例模式4.双重检查锁5.禁止指令重排序 一.什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff…

基于天鹰算法优化的BP神经网络(预测应用) - 附代码

基于天鹰算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于天鹰算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.天鹰优化BP神经网络2.1 BP神经网络参数设置2.2 天鹰算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

[Android]JNI的基础知识

目录 1.什么是JNI 2.配置JNI开发环境NDK 3.创建Native C类型的项目 4. 了解CMakeLists.txt 文件 5.了解native-lib.cpp 文件 6.在 Android 的 MainActivity 中调用 native-lib.cpp 中实现的本地方法 1.什么是JNI JNI&#xff08;Java Native Interface&#xff09;是一…