栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

grpc 打印日志到日志文件

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

grpc 打印日志到日志文件

在使用grpc时,有报错往往是一件很头疼的事情。虽然grpc的每个方法报错都会返回一些错误信息,但很多时候这些错误信息并不能引导我们直接发现问题的根本(尤其是排查一些连接层面的偶现问题时)。好在grpc的源码里也是有日志的,通过grpc源码中的日志,我们不但可以更深入的了解grpc的运行过程,也可以在遇到报错时更快速的定位问题。本篇我们就来研究一下,如何开启grpc的日志,并将其写入我们服务本身的日志文件中。

如何开启grpc日志

对于grpc这种使用者众多的开源项目,有问题最好的办法就是先看readme文件~

grpc的readme告诉我们,grpc的日志是环境变量控制的,想要开启日志,就要修改下面这两个环境变量。:

export GRPC_GO_LOG_VERBOSITY_LEVEL=99
export GRPC_GO_LOG_SEVERITY_LEVEL=info

我们先试试看效果如何,以grpc源码中提供的helloworld项目为例:

 可以看到,在修改了两个环境变量之后,再次运行client.go,grpc源码中的日志都打印到了屏幕上,通过这些日志我们可以看到grpc从地址解析,到负载均衡,再到连接的创建与关闭的全过程。

日志是拿到了,可是打印到屏幕上,这显然不是我们想要的结果,所以我们还得再深入研究一下这个日志是怎么打印出来的。

首先,既然开启了上面两个环境变量就能使日志打印出来,那么grpc源码中和日志相关的代码肯定读取了这两个环境,因此我们先来搜索一下源码中包含这两个环境变量的地方:

// newLoggerV2 creates a loggerV2 to be used as default logger.
// All logs are written to stderr.
func newLoggerV2() LoggerV2 {
	errorW := ioutil.Discard
	warningW := ioutil.Discard
	infoW := ioutil.Discard

	logLevel := os.Getenv("GRPC_GO_LOG_SEVERITY_LEVEL")
	switch logLevel {
	case "", "ERROR", "error": // If env is unset, set level to ERROR.
		errorW = os.Stderr
	case "WARNING", "warning":
		warningW = os.Stderr
	case "INFO", "info":
		infoW = os.Stderr
	}

	var v int
	vLevel := os.Getenv("GRPC_GO_LOG_VERBOSITY_LEVEL")
	if vl, err := strconv.Atoi(vLevel); err == nil {
		v = vl
	}
	return NewLoggerV2WithVerbosity(infoW, warningW, errorW, v)
}

使用goland我们可以很容易的找到这个函数,这个函数在grpc源码的grpclog包里,我们再找找这个函数是啥时候被调用的:

func init() {
	SetLoggerV2(newLoggerV2())
}

唯一一处调用newLoggerV2函数的,是grpclog包的init函数,而init函数是在引用时调用的,也就是说,如果我们什么都不做,那么日志默认的打印方式就是newLoggerV2里的那段,根据这个函数的逻辑可以看出,如果GRPC_GO_LOG_SEVERITY_LEVEL这个环境变了没有设置的话,infoW/warningW这两个标准输出类型都会设置为Discard,也就是不打印,只有errorW被设置为了os.Stderr,怪不得我们平时看不到任何grpc的log。

回到newLoggerV2函数,这个函数本身没有入参,所以我们并不能通过这个函数修改grpc日志的输出方式,但是newLoggerV2函数里调用的NewLoggerV2WithVerbosity函数可以。然而只调用NewLoggerV2WithVerbosity肯定是不够的,因为我们不但要创建一个Logger对象,还要让grpc运行的过程中使用我们创建的这个Logger对象,咋办呢?我们查找grpclog.Logger赋值的地方,不难找到SetLoggerV2这个函数:

// SetLoggerV2 sets logger that is used in grpc to a V2 logger.
// Not mutex-protected, should be called before any gRPC functions.
func SetLoggerV2(l LoggerV2) {
	if _, ok := l.(*componentData); ok {
		panic("cannot use component logger as grpclog logger")
	}
	grpclog.Logger = l
	grpclog.DepthLogger, _ = l.(grpclog.DepthLoggerV2)
}

这个函数正是我们要找的,将一个创建好的Logger设置为grpc日志使用的logger,因此只要使用SetLoggerV2和NewLoggerV2WithVerbosity这两个函数,就可以达到我们控制grpc日志输出的目的了。

在使用之前我们有必要再认识一下NewLoggerV2WithVerbosity这个函数:

// NewLoggerV2WithVerbosity creates a loggerV2 with the provided writers and
// verbosity level.
func NewLoggerV2WithVerbosity(infoW, warningW, errorW io.Writer, v int) LoggerV2 {
	var m []*log.Logger
	m = append(m, log.New(infoW, severityName[infoLog]+": ", log.LstdFlags))
	m = append(m, log.New(io.MultiWriter(infoW, warningW), severityName[warningLog]+": ", log.LstdFlags))
	ew := io.MultiWriter(infoW, warningW, errorW) // ew will be used for error and fatal.
	m = append(m, log.New(ew, severityName[errorLog]+": ", log.LstdFlags))
	m = append(m, log.New(ew, severityName[fatalLog]+": ", log.LstdFlags))
	return &loggerT{m: m, v: v}
}

这个函数除了3个设置输出方式的入参,还有一个int类型的参数v,注释对其的解释是“verbosity level”,翻译过来就是“详细程度”,如何理解这个详细程度呢?我们还是看下这个变量v是哪里用到的:

func (g *loggerT) V(l int) bool {
	return l <= g.v
}

唯一用到v这个成员的是这个方法,这个方法里做了一个判断,如果入参

func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
	// TODO: handle s.ResolverState.ServiceConfig?
	if logger.V(2) {
		logger.Info("base.baseBalancer: got new ClientConn state: ", s)
	}

也就是说,grpc源码打印日志之前,会先判断一下日志的等级,只有小于设置的v的值,才会打印出来,到这里我们就可以理解何为“详细程度”了,grpc为了控制不要打印过多的日志,干扰我们排查问题,特意加了个控制方式。还记得readme文件里让我们设置的环境变量吗,其中GRPC_GO_LOG_VERBOSITY_LEVEL就是用来控制“详细程度”的,readme文件让我们设置为99,显然99就是最高的等级了,也就是说这样所有日志就都会打印出来。

到这里,我们就掌握了一切所需的知识,接下来就实践一把,把日志打印到指定的文件里。

如何将grpc日志写入指定文件

首先,我们写一个初始化grpc日志的函数,将日志的输出方式设置为写入到当前路径下名为filename的文件中。注意调用NewLoggerV2WithVerbosity时,最后一个参数,也就是“详细程度”设置为了99,这样排查问题时,就可以看到所有级别的日志了。

func initLog(filename string) {
	writer, err := os.OpenFile(filename, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
	if err != nil {
		log.Fatalf("failed to init log: #{err}")
		return
	}
	grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity(writer, writer, writer,99))
}

然后我们在程序初始化的时候调用一下这个函数就可以了。

还是用helloworld那个项目为例,在client.go中加上这个initLog函数,然后运行一下,就会发现日志打印到了client.log这个文件中:

欧克,到这里就大功告成了,还是很简单的哈哈~ 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/299954.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号