在使用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这个成员的是这个方法,这个方法里做了一个判断,如果入参 也就是说,grpc源码打印日志之前,会先判断一下日志的等级,只有小于设置的v的值,才会打印出来,到这里我们就可以理解何为“详细程度”了,grpc为了控制不要打印过多的日志,干扰我们排查问题,特意加了个控制方式。还记得readme文件里让我们设置的环境变量吗,其中GRPC_GO_LOG_VERBOSITY_LEVEL就是用来控制“详细程度”的,readme文件让我们设置为99,显然99就是最高的等级了,也就是说这样所有日志就都会打印出来。 到这里,我们就掌握了一切所需的知识,接下来就实践一把,把日志打印到指定的文件里。 首先,我们写一个初始化grpc日志的函数,将日志的输出方式设置为写入到当前路径下名为filename的文件中。注意调用NewLoggerV2WithVerbosity时,最后一个参数,也就是“详细程度”设置为了99,这样排查问题时,就可以看到所有级别的日志了。 然后我们在程序初始化的时候调用一下这个函数就可以了。 还是用helloworld那个项目为例,在client.go中加上这个initLog函数,然后运行一下,就会发现日志打印到了client.log这个文件中: 欧克,到这里就大功告成了,还是很简单的哈哈~ 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)
}
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))
}



