现象
在应用的 service_stdout.log
里一直输出下面的日志,直接把磁盘打满了:
1 | 23:07:34.441 [TAIRCLIENT-1-thread-1] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 14 times in a row. |
service_stdout.log
是进程标准输出的重定向,可以初步判定是tair插件把日志输出到了stdout里。
尽管有了初步的判断,但是具体logger为什么会打到stdout里,还需要进一步排查,常见的方法可能是本地debug。
下面介绍利用arthas直接在线上定位问题的过程,主要使用sc
和getstatic
命令。
定位logger的具体实现
日志是io.netty.channel.nio.NioEventLoop
输出的,到netty的代码里查看,发现是DEBUG级别的输出:
然后用arthas的sc
命令来查看具体的io.netty.channel.nio.NioEventLoop
是从哪里加载的。
1 | class-info io.netty.channel.nio.NioEventLoop |
可见,的确是从tair插件里加载的。
查看NioEventLoop的代码,可以发现它有一个logger
的field:
1 | public final class NioEventLoop extends SingleThreadEventLoop { |
使用arthas的getstatic
命令来查看这个logger
具体实现类是什么(使用-c
参数指定classloader):
1 | $ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'getClass().getName()' |
可以发现是Slf4JLogger
。
再查看io.netty.util.internal.logging.Slf4JLogger的实现,发现它内部有一个logger的field:
1 | package io.netty.util.internal.logging; |
那么使用arthas的getstatic
命令来查看这个logger
属性的值:
1 | $ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger' |
可见,logger的最本质实现类是:ch.qos.logback.classic.Logger
。
再次用getstatic
命令来确定jar包的location:
1 | $ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger.getClass().getProtectionDomain().getCodeSource().getLocation()' |
可见这个ch.qos.logback.classic.Logger
的确是tair插件里加载的。
定位logger的level
上面已经定位logger的实现类是ch.qos.logback.classic.Logger
,但是为什么它会输出DEBUG
level的日志?
其实在上面的getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger'
输出里,已经打印出它的level是null了。如果对logger有所了解的话,可以知道当child logger的level为null时,它的level取决于parent logger的level。
我们再来看下ch.qos.logback.classic.Logger
的代码,它有一个parent logger的属性:
1 | public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable { |
所以,我们可以通过getstatic
来获取到这个parent属性的内容。然后通过多个parent操作,可以发现level都是null,最终发现ROOT level是DEBUG 。
1 | $ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger.parent.parent.parent.parent.parent' |
所以 io.netty.channel.nio.NioEventLoop
的logger的level取的是ROOT logger的配置,即默认值DEBUG
。
具体实现可以查看ch.qos.logback.classic.LoggerContext
1 | public LoggerContext() { |
为什么logback输出到了stdout里
上面我们得到结论
- tair插件里的logback默认的level是DEBUG,导致netty里的日志可以被打印出来
那么我们可以猜测:
- tair里的logback没有特殊配置,或者只配置了tair自己的package,导致ROOT logger的日志直接输出到stdout里
那么实现上tair里是使用了logger-api
,它通过LoggerFactory.getLogger
函数获取到了自己package的logger,然后设置level为INFO
,并设置了appender。
换而言之,tair插件里的logback没有设置ROOT logger,所以它的默认level是DEBUG,并且默认的appender会输出到stdout里。
所以tair插件可以增加设置ROOT logger level为INFO
来修复这个问题。
1 | private static com.taobao.middleware.logger.Logger logger |
总结
- tair插件里直接以api的方式设置了自己package下的logger
- tair插件里netty的logger的packge和tair并不一样,所以它最终取的是ROOT logger的配置
- logback默认的ROOT logger level是
DEBUG
,输出是stdout - 利用arthas的
sc
命令定位具体的类 - 利用arthas的
getstatic
获取static filed的值 - 利用logger parent层联的特性,可以向上一层层获取到ROOT logger的配置
链接
- Arthas开源:https://github.com/alibaba/arthas