From 053ee2044fa5345b3999885c0efaaabd71ec3f38 Mon Sep 17 00:00:00 2001 From: hengyunabc Date: Fri, 20 Sep 2019 17:14:19 +0800 Subject: [PATCH] logger command support log4j2. #865 --- core/pom.xml | 9 + .../core/command/logger/Log4j2Helper.java | 187 ++++++++++++++++++ .../core/command/logger/Log4jHelper.java | 8 +- .../core/command/logger/LoggerCommand.java | 42 +++- .../core/command/logger/LoggerHelper.java | 4 + 5 files changed, 240 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/logger/Log4j2Helper.java diff --git a/core/pom.xml b/core/pom.xml index 00dd043f0..6c1b497ee 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -120,6 +120,15 @@ log4j log4j 1.2.17 + provided + true + + + org.apache.logging.log4j + log4j-core + 2.12.1 + provided + true com.alibaba diff --git a/core/src/main/java/com/taobao/arthas/core/command/logger/Log4j2Helper.java b/core/src/main/java/com/taobao/arthas/core/command/logger/Log4j2Helper.java new file mode 100644 index 000000000..b089daeff --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/Log4j2Helper.java @@ -0,0 +1,187 @@ +package com.taobao.arthas.core.command.logger; + +import java.lang.reflect.Field; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; + +/** + * + * @author hengyunabc 2019-09-20 + * + */ +public class Log4j2Helper { + private static boolean Log4j2 = false; + private static Field configField = null; + + static { + try { + Class loggerClass = Class.forName("org.apache.logging.log4j.Logger"); + // 这里可能会加载到其它上游ClassLoader的log4j2,因此需要判断是否当前classloader + if (loggerClass.getClassLoader().equals(Log4j2Helper.class.getClassLoader())) { + Log4j2 = true; + } + + try { + configField = LoggerConfig.class.getDeclaredField("config"); + configField.setAccessible(true); + } catch (Throwable e) { + // ignore + } + } catch (Throwable t) { + } + } + + public static boolean hasLength(String str) { + return (str != null && !str.isEmpty()); + } + + private static LoggerConfig getLoggerConfig(String name) { + if (!hasLength(name) || LoggerConfig.ROOT.equalsIgnoreCase(name)) { + name = LogManager.ROOT_LOGGER_NAME; + } + return getLoggerContext().getConfiguration().getLoggers().get(name); + } + + private static LoggerContext getLoggerContext() { + return (LoggerContext) LogManager.getContext(false); + } + + public static Boolean updateLevel(String loggerName, String logLevel) { + if (Log4j2) { + Level level = Level.getLevel(logLevel.toUpperCase()); + if (level == null) { + return null; + } + LoggerConfig loggerConfig = getLoggerConfig(loggerName); + if (loggerConfig == null) { + loggerConfig = new LoggerConfig(loggerName, level, true); + getLoggerContext().getConfiguration().addLogger(loggerName, loggerConfig); + } else { + loggerConfig.setLevel(level); + } + getLoggerContext().updateLoggers(); + return Boolean.TRUE; + } + return null; + } + + public static Map> getLoggers(String name, boolean includeNoAppender) { + Map> loggerInfoMap = new HashMap>(); + if (!Log4j2) { + return loggerInfoMap; + } + + Configuration configuration = getLoggerContext().getConfiguration(); + + if (name != null && !name.trim().isEmpty()) { + LoggerConfig loggerConfig = configuration.getLoggerConfig(name); + if (loggerConfig == null) { + return loggerInfoMap; + } + loggerInfoMap.put(name, doGetLoggerInfo(loggerConfig)); + } else { + // 获取所有logger时,如果没有appender则忽略 + Map loggers = configuration.getLoggers(); + if (loggers != null) { + for (Entry entry : loggers.entrySet()) { + LoggerConfig loggerConfig = entry.getValue(); + if (!includeNoAppender) { + if (!loggerConfig.getAppenders().isEmpty()) { + loggerInfoMap.put(entry.getKey(), doGetLoggerInfo(entry.getValue())); + } + } else { + loggerInfoMap.put(entry.getKey(), doGetLoggerInfo(entry.getValue())); + } + } + } + } + + return loggerInfoMap; + } + + private static Object getConfigField(LoggerConfig loggerConfig) { + try { + if(configField != null) { + return configField.get(loggerConfig); + } + } catch (Throwable e) { + // ignore + } + return null; + } + + private static Map doGetLoggerInfo(LoggerConfig loggerConfig) { + Map info = new HashMap(); + + String name = loggerConfig.getName(); + if (name == null || name.trim().isEmpty()) { + name = LoggerConfig.ROOT; + } + + info.put(LoggerHelper.name, name); + info.put(LoggerHelper.clazz, loggerConfig.getClass()); + CodeSource codeSource = loggerConfig.getClass().getProtectionDomain().getCodeSource(); + if (codeSource != null) { + info.put(LoggerHelper.codeSource, codeSource.getLocation()); + } + Object config = getConfigField(loggerConfig); + if (config != null) { + info.put(LoggerHelper.config, config); + } + + info.put(LoggerHelper.additivity, loggerConfig.isAdditive()); + + Level level = loggerConfig.getLevel(); + if (level != null) { + info.put(LoggerHelper.level, level.toString()); + } + + List> result = doGetLoggerAppenders(loggerConfig); + info.put(LoggerHelper.appenders, result); + return info; + } + + private static List> doGetLoggerAppenders(LoggerConfig loggerConfig) { + List> result = new ArrayList>(); + + Map appenders = loggerConfig.getAppenders(); + + for (Entry entry : appenders.entrySet()) { + Map info = new HashMap(); + Appender appender = entry.getValue(); + info.put(LoggerHelper.name, appender.getName()); + info.put(LoggerHelper.clazz, appender.getClass()); + + result.add(info); + if (appender instanceof FileAppender) { + info.put(LoggerHelper.file, ((FileAppender) appender).getFileName()); + } else if (appender instanceof ConsoleAppender) { + info.put(LoggerHelper.target, ((ConsoleAppender) appender).getTarget()); + } else if (appender instanceof AsyncAppender) { + + AsyncAppender asyncAppender = ((AsyncAppender) appender); + String[] appenderRefStrings = asyncAppender.getAppenderRefStrings(); + + info.put(LoggerHelper.blocking, asyncAppender.isBlocking()); + info.put(LoggerHelper.appenderRef, Arrays.asList(appenderRefStrings)); + } + } + return result; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/logger/Log4jHelper.java b/core/src/main/java/com/taobao/arthas/core/command/logger/Log4jHelper.java index 1f1cf7bee..e762edc87 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/logger/Log4jHelper.java +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/Log4jHelper.java @@ -1,5 +1,6 @@ package com.taobao.arthas.core.command.logger; +import java.security.CodeSource; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; @@ -26,9 +27,8 @@ public class Log4jHelper { static { try { Class loggerClass = Class.forName("org.apache.log4j.Logger"); - // 这里可能会加载到应用中依赖的log4j,因此需要判断classloader + // 这里可能会加载到其它上游ClassLoader的log4j,因此需要判断是否当前classloader if (loggerClass.getClassLoader().equals(Log4jHelper.class.getClassLoader())) { - LogManager.getLoggerRepository(); Log4j = true; } } catch (Throwable t) { @@ -106,6 +106,10 @@ public class Log4jHelper { Map info = new HashMap(); info.put(LoggerHelper.name, logger.getName()); info.put(LoggerHelper.clazz, logger.getClass()); + CodeSource codeSource = logger.getClass().getProtectionDomain().getCodeSource(); + if (codeSource != null) { + info.put(LoggerHelper.codeSource, codeSource.getLocation()); + } info.put(LoggerHelper.additivity, logger.getAdditivity()); Level level = logger.getLevel(), effectiveLevel = logger.getEffectiveLevel(); diff --git a/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java index 7bfef8a7a..be027af63 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java @@ -36,8 +36,6 @@ import com.taobao.text.util.RenderUtil; /** * logger command * - * TODO support log4j2 - * * @author hengyunabc 2019-09-04 * */ @@ -51,6 +49,7 @@ public class LoggerCommand extends AnnotatedCommand { private static byte[] LoggerHelperBytes; private static byte[] Log4jHelperBytes; private static byte[] LogbackHelperBytes; + private static byte[] Log4j2HelperBytes; private static Map, byte[]> classToBytesMap = new HashMap, byte[]>(); @@ -58,10 +57,12 @@ public class LoggerCommand extends AnnotatedCommand { LoggerHelperBytes = loadClassBytes(LoggerHelper.class); Log4jHelperBytes = loadClassBytes(Log4jHelper.class); LogbackHelperBytes = loadClassBytes(LogbackHelper.class); + Log4j2HelperBytes = loadClassBytes(Log4j2Helper.class); classToBytesMap.put(LoggerHelper.class, LoggerHelperBytes); classToBytesMap.put(Log4jHelper.class, Log4jHelperBytes); classToBytesMap.put(LogbackHelper.class, LogbackHelperBytes); + classToBytesMap.put(Log4j2Helper.class, Log4j2HelperBytes); } private String name; @@ -145,6 +146,15 @@ public class LoggerCommand extends AnnotatedCommand { logger.error("arthas", "logger command update logback level error", e); } + try { + Boolean updateResult = this.updateLevel(inst, Log4j2Helper.class); + if (Boolean.TRUE.equals(updateResult)) { + result = true; + } + } catch (Throwable e) { + logger.error("arthas", "logger command update log4j2 level error", e); + } + if (result) { process.write("update logger level success.\n"); } else { @@ -180,6 +190,8 @@ public class LoggerCommand extends AnnotatedCommand { loggerTypes.addType(LoggerType.LOG4J); } else if ("ch.qos.logback.classic.Logger".equals(className)) { loggerTypes.addType(LoggerType.LOGBACK); + } else if ("org.apache.logging.log4j.Logger".equals(className)) { + loggerTypes.addType(LoggerType.LOG4J2); } } } @@ -202,6 +214,12 @@ public class LoggerCommand extends AnnotatedCommand { process.write(renderResult); } + if (loggerTypes.contains(LoggerType.LOG4J2)) { + Map> loggerInfoMap = loggerInfo(classLoader, Log4j2Helper.class); + String renderResult = renderLoggerInfo(loggerInfoMap, process.width()); + + process.write(renderResult); + } } } @@ -223,11 +241,19 @@ public class LoggerCommand extends AnnotatedCommand { .row(label(LoggerHelper.classLoaderHash).style(Decoration.bold.bold()), label("" + StringUtils.classLoaderHash(clazz))) .row(label(LoggerHelper.level).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.level))) - .row(label(LoggerHelper.effectiveLevel).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.effectiveLevel))) - .row(label(LoggerHelper.additivity).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.additivity))) + label("" + info.get(LoggerHelper.level))); + if (info.get(LoggerHelper.effectiveLevel) != null) { + table.row(label(LoggerHelper.effectiveLevel).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.effectiveLevel))); + } + + if (info.get(LoggerHelper.config) != null) { + table.row(label(LoggerHelper.config).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.config))); + } + + table.row(label(LoggerHelper.additivity).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.additivity))) .row(label(LoggerHelper.codeSource).style(Decoration.bold.bold()), label("" + info.get(LoggerHelper.codeSource))); @@ -307,7 +333,7 @@ public class LoggerCommand extends AnnotatedCommand { } static enum LoggerType { - LOG4J, LOGBACK + LOG4J, LOGBACK, LOG4J2 } static class LoggerTypes { diff --git a/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerHelper.java b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerHelper.java index e1180a08b..b64106dcb 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerHelper.java +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerHelper.java @@ -14,6 +14,10 @@ public interface LoggerHelper { // logger info public static final String level = "level"; public static final String effectiveLevel = "effectiveLevel"; + + // log4j2 only + public static final String config = "config"; + // type boolean public static final String additivity = "additivity"; public static final String appenders = "appenders";