在應用的 service_stdout.log
裏一直輸出下面的日誌,直接把磁盤打滿了:html
23:07:34.441 [TAIRCLIENT-1-thread-1] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 14 times in a row. 23:07:34.460 [TAIRCLIENT-1-thread-3] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row. 23:07:34.461 [TAIRCLIENT-1-thread-4] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row. 23:07:34.462 [TAIRCLIENT-1-thread-5] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row.
service_stdout.log
是進程標準輸出的重定向,能夠初步斷定是tair插件把日誌輸出到了stdout裏。java
儘管有了初步的判斷,可是具體logger爲何會打到stdout裏,還須要進一步排查,常見的方法多是本地debug。git
下面介紹利用arthas直接在線上定位問題的過程,主要使用sc
和getstatic
命令。github
日誌是io.netty.channel.nio.NioEventLoop
輸出的,到netty的代碼裏查看,發現是DEBUG級別的輸出:web
而後用arthas的sc
命令來查看具體的io.netty.channel.nio.NioEventLoop
是從哪裏加載的。apache
class-info io.netty.channel.nio.NioEventLoop code-source file:/opt/app/plugins/tair-plugin/lib/netty-all-4.0.35.Final.jar!/ name io.netty.channel.nio.NioEventLoop isInterface false isAnnotation false isEnum false isAnonymousClass false isArray false isLocalClass false isMemberClass false isPrimitive false isSynthetic false simple-name NioEventLoop modifier final,public annotation interfaces super-class +-io.netty.channel.SingleThreadEventLoop +-io.netty.util.concurrent.SingleThreadEventExecutor +-io.netty.util.concurrent.AbstractScheduledEventExecutor +-io.netty.util.concurrent.AbstractEventExecutor +-java.util.concurrent.AbstractExecutorService +-java.lang.Object class-loader +-tair-plugin's ModuleClassLoader classLoaderHash 73ad2d6
可見,的確是從tair插件里加載的。api
查看NioEventLoop的代碼,能夠發現它有一個logger
的field:app
public final class NioEventLoop extends SingleThreadEventLoop { private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioEventLoop.class);
使用arthas的getstatic
命令來查看這個logger
具體實現類是什麼(使用-c
參數指定classloader):函數
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'getClass().getName()' field: logger @String[io.netty.util.internal.logging.Slf4JLogger]
能夠發現是Slf4JLogger
。oop
再查看io.netty.util.internal.logging.Slf4JLogger的實現,發現它內部有一個logger的field:
package io.netty.util.internal.logging; import org.slf4j.Logger; /** * <a href="http://www.slf4j.org/">SLF4J</a> logger. */ class Slf4JLogger extends AbstractInternalLogger { private static final long serialVersionUID = 108038972685130825L; private final transient Logger logger;
那麼使用arthas的getstatic
命令來查看這個logger
屬性的值:
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger' field: logger @Logger[ serialVersionUID=@Long[5454405123156820674], FQCN=@String[ch.qos.logback.classic.Logger], name=@String[io.netty.channel.nio.NioEventLoop], level=null, effectiveLevelInt=@Integer[10000], parent=@Logger[Logger[io.netty.channel.nio]], childrenList=null, aai=null, additive=@Boolean[true], loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]], ]
可見,logger的最本質實現類是:ch.qos.logback.classic.Logger
。
再次用getstatic
命令來肯定jar包的location:
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger.getClass().getProtectionDomain().getCodeSource().getLocation()' field: logger @URL[ BUILTIN_HANDLERS_PREFIX=@String[sun.net.www.protocol], serialVersionUID=@Long[-7627629688361524110], protocolPathProp=@String[java.protocol.handler.pkgs], protocol=@String[jar], host=@String[], port=@Integer[-1], file=@String[file:/opt/app/plugins/tair-plugin/lib/logback-classic-1.2.3.jar!/], query=null, authority=@String[], path=@String[file:/opt/app/plugins/tair-plugin/lib/logback-classic-1.2.3.jar!/], userInfo=null, ref=null, hostAddress=null, handler=@Handler[com.taobao.pandora.loader.jar.Handler@1a0c361e], hashCode=@Integer[126346621], tempState=null, factory=@TomcatURLStreamHandlerFactory[org.apache.catalina.webresources.TomcatURLStreamHandlerFactory@3edd7b7], handlers=@Hashtable[isEmpty=false;size=4], streamHandlerLock=@Object[java.lang.Object@488ccac9], serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=7], ]
可見這個ch.qos.logback.classic.Logger
的確是tair插件里加載的。
上面已經定位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的屬性:
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable { /** * The parent of this category. All categories have at least one ancestor * which is the root category. */ transient private Logger parent;
因此,咱們能夠經過getstatic
來獲取到這個parent屬性的內容。而後經過多個parent操做,能夠發現level都是null,最終發現ROOT level是DEBUG 。
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger.parent.parent.parent.parent.parent' field: logger @Logger[ serialVersionUID=@Long[5454405123156820674], FQCN=@String[ch.qos.logback.classic.Logger], name=@String[ROOT], level=@Level[DEBUG], effectiveLevelInt=@Integer[10000], parent=null, childrenList=@CopyOnWriteArrayList[isEmpty=false;size=2], aai=@AppenderAttachableImpl[ch.qos.logback.core.spi.AppenderAttachableImpl@1ecf9bae], additive=@Boolean[true], loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]], ]
因此 io.netty.channel.nio.NioEventLoop
的logger的level取的是ROOT logger的配置,即默認值DEBUG
。
具體實現能夠查看ch.qos.logback.classic.LoggerContext
public LoggerContext() { super(); this.loggerCache = new ConcurrentHashMap<String, Logger>(); this.loggerContextRemoteView = new LoggerContextVO(this); this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this); this.root.setLevel(Level.DEBUG); loggerCache.put(Logger.ROOT_LOGGER_NAME, root); initEvaluatorMap(); size = 1; this.frameworkPackages = new ArrayList<String>(); }
上面咱們獲得結論
那麼咱們能夠猜想:
那麼實現上tair裏是使用了logger-api
,它經過LoggerFactory.getLogger
函數獲取到了本身package的logger,而後設置level爲INFO
,並設置了appender。
換而言之,tair插件裏的logback沒有設置ROOT logger,因此它的默認level是DEBUG,而且默認的appender會輸出到stdout裏。
因此tair插件能夠增長設置ROOT logger level爲INFO
來修復這個問題。
private static com.taobao.middleware.logger.Logger logger = com.taobao.middleware.logger.LoggerFactory.getLogger("com.taobao.tair"); public static com.taobao.middleware.logger.Logger infolog = com.taobao.middleware.logger.LoggerFactory.getLogger("com.taobao.tair.custom-infolog"); public static int JM_LOG_RETAIN_COUNT = 3; public static String JM_LOG_FILE_SIZE = "200MB"; static { try { String tmp = System.getProperty("JM.LOG.RETAIN.COUNT", "3"); JM_LOG_RETAIN_COUNT = Integer.parseInt(tmp); } catch (NumberFormatException e) { } JM_LOG_FILE_SIZE = System.getProperty("JM.LOG.FILE.SIZE", "200MB"); logger.setLevel(Level.INFO); logger.activateAppenderWithSizeRolling("tair-client", "tair-client.log", "UTF-8", TairUtil.splitSize(JM_LOG_FILE_SIZE, 0.8 / (JM_LOG_RETAIN_COUNT + 1)), JM_LOG_RETAIN_COUNT); logger.setAdditivity(false); logger.activateAsync(500, 100); logger.info("JM_LOG_RETAIN_COUNT " + JM_LOG_RETAIN_COUNT + " JM_LOG_FILE_SIZE " + JM_LOG_FILE_SIZE); infolog.setLevel(Level.INFO); infolog.activateAppenderWithSizeRolling("tair-client", "tair-client-info.log", "UTF-8", "10MB", 1); infolog.setAdditivity(false); infolog.activateAsync(500, 100);
DEBUG
,輸出是stdoutsc
命令定位具體的類getstatic
獲取static filed的值