diff --git a/common/src/main/java/com/taobao/arthas/common/IOUtils.java b/common/src/main/java/com/taobao/arthas/common/IOUtils.java index 9ea3a4780..6ea160a9b 100644 --- a/common/src/main/java/com/taobao/arthas/common/IOUtils.java +++ b/common/src/main/java/com/taobao/arthas/common/IOUtils.java @@ -40,6 +40,18 @@ public class IOUtils { } } + /** + * @return a byte[] containing the information contained in the specified + * InputStream. + * @throws java.io.IOException + */ + public static byte[] getBytes(InputStream input) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + copy(input, result); + result.close(); + return result.toByteArray(); + } + public static IOException close(InputStream input) { return close((Closeable) input); } diff --git a/common/src/main/java/com/taobao/arthas/common/ReflectException.java b/common/src/main/java/com/taobao/arthas/common/ReflectException.java new file mode 100644 index 000000000..a33ee7e21 --- /dev/null +++ b/common/src/main/java/com/taobao/arthas/common/ReflectException.java @@ -0,0 +1,16 @@ +package com.taobao.arthas.common; + +public class ReflectException extends RuntimeException { + + private static final long serialVersionUID = 1L; + private Throwable cause; + + public ReflectException(Throwable cause) { + super(cause.getClass().getName() + "-->" + cause.getMessage()); + this.cause = cause; + } + + public Throwable getCause() { + return this.cause; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/taobao/arthas/common/ReflectUtils.java b/common/src/main/java/com/taobao/arthas/common/ReflectUtils.java new file mode 100644 index 000000000..bb7e3d155 --- /dev/null +++ b/common/src/main/java/com/taobao/arthas/common/ReflectUtils.java @@ -0,0 +1,511 @@ +package com.taobao.arthas.common; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * from spring + * @version $Id: ReflectUtils.java,v 1.30 2009/01/11 19:47:49 herbyderby Exp $ + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class ReflectUtils { + + private ReflectUtils() { + } + + private static final Map primitives = new HashMap(8); + + private static final Map transforms = new HashMap(8); + + private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader(); + + // SPRING PATCH BEGIN + private static final Method privateLookupInMethod; + + private static final Method lookupDefineClassMethod; + + private static final Method classLoaderDefineClassMethod; + + private static final ProtectionDomain PROTECTION_DOMAIN; + + private static final Throwable THROWABLE; + + private static final List OBJECT_METHODS = new ArrayList(); + + static { + Method privateLookupIn; + Method lookupDefineClass; + Method classLoaderDefineClass; + ProtectionDomain protectionDomain; + Throwable throwable = null; + try { + privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + try { + return MethodHandles.class.getMethod("privateLookupIn", Class.class, + MethodHandles.Lookup.class); + } catch (NoSuchMethodException ex) { + return null; + } + } + }); + lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + try { + return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class); + } catch (NoSuchMethodException ex) { + return null; + } + } + }); + classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, + Integer.TYPE, ProtectionDomain.class); + } + }); + protectionDomain = getProtectionDomain(ReflectUtils.class); + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + Method[] methods = Object.class.getDeclaredMethods(); + for (Method method : methods) { + if ("finalize".equals(method.getName()) + || (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) { + continue; + } + OBJECT_METHODS.add(method); + } + return null; + } + }); + } catch (Throwable t) { + privateLookupIn = null; + lookupDefineClass = null; + classLoaderDefineClass = null; + protectionDomain = null; + throwable = t; + } + privateLookupInMethod = privateLookupIn; + lookupDefineClassMethod = lookupDefineClass; + classLoaderDefineClassMethod = classLoaderDefineClass; + PROTECTION_DOMAIN = protectionDomain; + THROWABLE = throwable; + } + // SPRING PATCH END + + private static final String[] CGLIB_PACKAGES = { "java.lang", }; + + static { + primitives.put("byte", Byte.TYPE); + primitives.put("char", Character.TYPE); + primitives.put("double", Double.TYPE); + primitives.put("float", Float.TYPE); + primitives.put("int", Integer.TYPE); + primitives.put("long", Long.TYPE); + primitives.put("short", Short.TYPE); + primitives.put("boolean", Boolean.TYPE); + + transforms.put("byte", "B"); + transforms.put("char", "C"); + transforms.put("double", "D"); + transforms.put("float", "F"); + transforms.put("int", "I"); + transforms.put("long", "J"); + transforms.put("short", "S"); + transforms.put("boolean", "Z"); + } + + public static ProtectionDomain getProtectionDomain(final Class source) { + if (source == null) { + return null; + } + return (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return source.getProtectionDomain(); + } + }); + } + + public static Constructor findConstructor(String desc) { + return findConstructor(desc, defaultLoader); + } + + public static Constructor findConstructor(String desc, ClassLoader loader) { + try { + int lparen = desc.indexOf('('); + String className = desc.substring(0, lparen).trim(); + return getClass(className, loader).getConstructor(parseTypes(desc, loader)); + } catch (ClassNotFoundException ex) { + throw new ReflectException(ex); + } catch (NoSuchMethodException ex) { + throw new ReflectException(ex); + } + } + + public static Method findMethod(String desc) { + return findMethod(desc, defaultLoader); + } + + public static Method findMethod(String desc, ClassLoader loader) { + try { + int lparen = desc.indexOf('('); + int dot = desc.lastIndexOf('.', lparen); + String className = desc.substring(0, dot).trim(); + String methodName = desc.substring(dot + 1, lparen).trim(); + return getClass(className, loader).getDeclaredMethod(methodName, parseTypes(desc, loader)); + } catch (ClassNotFoundException ex) { + throw new ReflectException(ex); + } catch (NoSuchMethodException ex) { + throw new ReflectException(ex); + } + } + + private static Class[] parseTypes(String desc, ClassLoader loader) throws ClassNotFoundException { + int lparen = desc.indexOf('('); + int rparen = desc.indexOf(')', lparen); + List params = new ArrayList(); + int start = lparen + 1; + for (;;) { + int comma = desc.indexOf(',', start); + if (comma < 0) { + break; + } + params.add(desc.substring(start, comma).trim()); + start = comma + 1; + } + if (start < rparen) { + params.add(desc.substring(start, rparen).trim()); + } + Class[] types = new Class[params.size()]; + for (int i = 0; i < types.length; i++) { + types[i] = getClass((String) params.get(i), loader); + } + return types; + } + + private static Class getClass(String className, ClassLoader loader) throws ClassNotFoundException { + return getClass(className, loader, CGLIB_PACKAGES); + } + + private static Class getClass(String className, ClassLoader loader, String[] packages) + throws ClassNotFoundException { + String save = className; + int dimensions = 0; + int index = 0; + while ((index = className.indexOf("[]", index) + 1) > 0) { + dimensions++; + } + StringBuffer brackets = new StringBuffer(className.length() - dimensions); + for (int i = 0; i < dimensions; i++) { + brackets.append('['); + } + className = className.substring(0, className.length() - 2 * dimensions); + + String prefix = (dimensions > 0) ? brackets + "L" : ""; + String suffix = (dimensions > 0) ? ";" : ""; + try { + return Class.forName(prefix + className + suffix, false, loader); + } catch (ClassNotFoundException ignore) { + } + for (int i = 0; i < packages.length; i++) { + try { + return Class.forName(prefix + packages[i] + '.' + className + suffix, false, loader); + } catch (ClassNotFoundException ignore) { + } + } + if (dimensions == 0) { + Class c = (Class) primitives.get(className); + if (c != null) { + return c; + } + } else { + String transform = (String) transforms.get(className); + if (transform != null) { + try { + return Class.forName(brackets + transform, false, loader); + } catch (ClassNotFoundException ignore) { + } + } + } + throw new ClassNotFoundException(save); + } + + public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + public static Object newInstance(Class type) { + return newInstance(type, EMPTY_CLASS_ARRAY, null); + } + + public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) { + return newInstance(getConstructor(type, parameterTypes), args); + } + + public static Object newInstance(final Constructor cstruct, final Object[] args) { + boolean flag = cstruct.isAccessible(); + try { + if (!flag) { + cstruct.setAccessible(true); + } + Object result = cstruct.newInstance(args); + return result; + } catch (InstantiationException e) { + throw new ReflectException(e); + } catch (IllegalAccessException e) { + throw new ReflectException(e); + } catch (InvocationTargetException e) { + throw new ReflectException(e.getTargetException()); + } finally { + if (!flag) { + cstruct.setAccessible(flag); + } + } + } + + public static Constructor getConstructor(Class type, Class[] parameterTypes) { + try { + Constructor constructor = type.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + return constructor; + } catch (NoSuchMethodException e) { + throw new ReflectException(e); + } + } + + public static String[] getNames(Class[] classes) { + if (classes == null) + return null; + String[] names = new String[classes.length]; + for (int i = 0; i < names.length; i++) { + names[i] = classes[i].getName(); + } + return names; + } + + public static Class[] getClasses(Object[] objects) { + Class[] classes = new Class[objects.length]; + for (int i = 0; i < objects.length; i++) { + classes[i] = objects[i].getClass(); + } + return classes; + } + + public static Method findNewInstance(Class iface) { + Method m = findInterfaceMethod(iface); + if (!m.getName().equals("newInstance")) { + throw new IllegalArgumentException(iface + " missing newInstance method"); + } + return m; + } + + public static Method[] getPropertyMethods(PropertyDescriptor[] properties, boolean read, boolean write) { + Set methods = new HashSet(); + for (int i = 0; i < properties.length; i++) { + PropertyDescriptor pd = properties[i]; + if (read) { + methods.add(pd.getReadMethod()); + } + if (write) { + methods.add(pd.getWriteMethod()); + } + } + methods.remove(null); + return (Method[]) methods.toArray(new Method[methods.size()]); + } + + public static PropertyDescriptor[] getBeanProperties(Class type) { + return getPropertiesHelper(type, true, true); + } + + public static PropertyDescriptor[] getBeanGetters(Class type) { + return getPropertiesHelper(type, true, false); + } + + public static PropertyDescriptor[] getBeanSetters(Class type) { + return getPropertiesHelper(type, false, true); + } + + private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) { + try { + BeanInfo info = Introspector.getBeanInfo(type, Object.class); + PropertyDescriptor[] all = info.getPropertyDescriptors(); + if (read && write) { + return all; + } + List properties = new ArrayList(all.length); + for (int i = 0; i < all.length; i++) { + PropertyDescriptor pd = all[i]; + if ((read && pd.getReadMethod() != null) || (write && pd.getWriteMethod() != null)) { + properties.add(pd); + } + } + return (PropertyDescriptor[]) properties.toArray(new PropertyDescriptor[properties.size()]); + } catch (IntrospectionException e) { + throw new ReflectException(e); + } + } + + public static Method findDeclaredMethod(final Class type, final String methodName, final Class[] parameterTypes) + throws NoSuchMethodException { + + Class cl = type; + while (cl != null) { + try { + return cl.getDeclaredMethod(methodName, parameterTypes); + } catch (NoSuchMethodException e) { + cl = cl.getSuperclass(); + } + } + throw new NoSuchMethodException(methodName); + } + + public static List addAllMethods(final Class type, final List list) { + if (type == Object.class) { + list.addAll(OBJECT_METHODS); + } else + list.addAll(java.util.Arrays.asList(type.getDeclaredMethods())); + + Class superclass = type.getSuperclass(); + if (superclass != null) { + addAllMethods(superclass, list); + } + Class[] interfaces = type.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + addAllMethods(interfaces[i], list); + } + + return list; + } + + public static List addAllInterfaces(Class type, List list) { + Class superclass = type.getSuperclass(); + if (superclass != null) { + list.addAll(Arrays.asList(type.getInterfaces())); + addAllInterfaces(superclass, list); + } + return list; + } + + public static Method findInterfaceMethod(Class iface) { + if (!iface.isInterface()) { + throw new IllegalArgumentException(iface + " is not an interface"); + } + Method[] methods = iface.getDeclaredMethods(); + if (methods.length != 1) { + throw new IllegalArgumentException("expecting exactly 1 method in " + iface); + } + return methods[0]; + } + + // SPRING PATCH BEGIN + public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception { + return defineClass(className, b, loader, null, null); + } + + public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain) + throws Exception { + + return defineClass(className, b, loader, protectionDomain, null); + } + + public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain, + Class contextClass) throws Exception { + + Class c = null; + + // Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches + if (contextClass != null && contextClass.getClassLoader() == loader && privateLookupInMethod != null + && lookupDefineClassMethod != null) { + try { + MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupInMethod.invoke(null, contextClass, + MethodHandles.lookup()); + c = (Class) lookupDefineClassMethod.invoke(lookup, b); + } catch (InvocationTargetException ex) { + Throwable target = ex.getTargetException(); + if (target.getClass() != LinkageError.class && target.getClass() != IllegalArgumentException.class) { + throw new ReflectException(target); + } + // in case of plain LinkageError (class already defined) + // or IllegalArgumentException (class in different package): + // fall through to traditional ClassLoader.defineClass below + } catch (Throwable ex) { + throw new ReflectException(ex); + } + } + + // Classic option: protected ClassLoader.defineClass method + if (c == null && classLoaderDefineClassMethod != null) { + if (protectionDomain == null) { + protectionDomain = PROTECTION_DOMAIN; + } + Object[] args = new Object[] { className, b, 0, b.length, protectionDomain }; + try { + if (!classLoaderDefineClassMethod.isAccessible()) { + classLoaderDefineClassMethod.setAccessible(true); + } + c = (Class) classLoaderDefineClassMethod.invoke(loader, args); + } catch (InvocationTargetException ex) { + throw new ReflectException(ex.getTargetException()); + } catch (Throwable ex) { + // Fall through if setAccessible fails with InaccessibleObjectException on JDK + // 9+ + // (on the module path and/or with a JVM bootstrapped with + // --illegal-access=deny) + if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) { + throw new ReflectException(ex); + } + } + } + + // Fallback option: JDK 9+ Lookup.defineClass API even if ClassLoader does not + // match + if (c == null && contextClass != null && contextClass.getClassLoader() != loader + && privateLookupInMethod != null && lookupDefineClassMethod != null) { + try { + MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupInMethod.invoke(null, contextClass, + MethodHandles.lookup()); + c = (Class) lookupDefineClassMethod.invoke(lookup, b); + } catch (InvocationTargetException ex) { + throw new ReflectException(ex.getTargetException()); + } catch (Throwable ex) { + throw new ReflectException(ex); + } + } + + // No defineClass variant available at all? + if (c == null) { + throw new ReflectException(THROWABLE); + } + + // Force static initializers to run. + Class.forName(className, true, loader); + return c; + } + // SPRING PATCH END + + public static int findPackageProtected(Class[] classes) { + for (int i = 0; i < classes.length; i++) { + if (!Modifier.isPublic(classes[i].getModifiers())) { + return i; + } + } + return 0; + } + +} diff --git a/core/pom.xml b/core/pom.xml index 0f608030f..b699518cc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -116,6 +116,11 @@ com.taobao.middleware logger.api + + log4j + log4j + 1.2.17 + com.alibaba fastjson diff --git a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java index 2376a9704..0268dde45 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java +++ b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java @@ -26,6 +26,7 @@ import com.taobao.arthas.core.command.klass100.OgnlCommand; import com.taobao.arthas.core.command.klass100.RedefineCommand; import com.taobao.arthas.core.command.klass100.SearchClassCommand; import com.taobao.arthas.core.command.klass100.SearchMethodCommand; +import com.taobao.arthas.core.command.logger.LoggerCommand; import com.taobao.arthas.core.command.monitor200.DashboardCommand; import com.taobao.arthas.core.command.monitor200.HeapDumpCommand; import com.taobao.arthas.core.command.monitor200.JvmCommand; @@ -93,6 +94,7 @@ public class BuiltinCommandPack implements CommandResolver { commands.add(Command.create(SystemPropertyCommand.class)); commands.add(Command.create(SystemEnvCommand.class)); commands.add(Command.create(VMOptionCommand.class)); + commands.add(Command.create(LoggerCommand.class)); commands.add(Command.create(HistoryCommand.class)); commands.add(Command.create(CatCommand.class)); commands.add(Command.create(PwdCommand.class)); 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 new file mode 100644 index 000000000..055318f76 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/Log4jHelper.java @@ -0,0 +1,156 @@ +package com.taobao.arthas.core.command.logger; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Appender; +import org.apache.log4j.AsyncAppender; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.FileAppender; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; + +/** + * + * @author hengyunabc 2019-09-06 + * + */ +public class Log4jHelper { + + private static boolean Log4j = false; + + static { + try { + Class loggerClass = Class.forName("org.apache.log4j.Logger"); + // 这里可能会加载到应用中依赖的log4j,因此需要判断classloader + if (loggerClass.getClassLoader().equals(Log4jHelper.class.getClassLoader())) { + LogManager.getLoggerRepository(); + Log4j = true; + } + } catch (Throwable t) { + } + } + + public static Boolean updateLevel(String name, String level) { + if (Log4j) { + Level l = Level.toLevel(level, Level.ERROR); + Logger logger = LogManager.getLoggerRepository().exists(name); + if (logger != null) { + logger.setLevel(l); + return true; + } else { + Logger root = LogManager.getLoggerRepository().getRootLogger(); + if (root.getName().equals(name)) { + root.setLevel(l); + return true; + } + } + return false; + } + return null; + } + + public static Map> getLoggers(String name, boolean includeNoAppender) { + Map> loggerInfoMap = new HashMap>(); + if (!Log4j) { + return loggerInfoMap; + } + + if (name != null && !name.trim().isEmpty()) { + Logger logger = LogManager.getLoggerRepository().exists(name); + if (logger != null) { + loggerInfoMap.put(name, doGetLoggerInfo(logger)); + } + } else { + // 获取所有logger时,如果没有appender则忽略 + @SuppressWarnings("unchecked") + Enumeration loggers = LogManager.getLoggerRepository().getCurrentLoggers(); + + if (loggers != null) { + while (loggers.hasMoreElements()) { + Logger logger = loggers.nextElement(); + Map info = doGetLoggerInfo(logger); + if (!includeNoAppender) { + List appenders = (List) info.get(LoggerHelper.appenders); + if (appenders != null && !appenders.isEmpty()) { + loggerInfoMap.put(logger.getName(), info); + } + } else { + loggerInfoMap.put(logger.getName(), info); + } + } + } + + Logger root = LogManager.getLoggerRepository().getRootLogger(); + if (root != null) { + Map info = doGetLoggerInfo(root); + if (!includeNoAppender) { + List appenders = (List) info.get(LoggerHelper.appenders); + if (appenders != null && !appenders.isEmpty()) { + loggerInfoMap.put(root.getName(), info); + } + } else { + loggerInfoMap.put(root.getName(), info); + } + } + } + + return loggerInfoMap; + } + + private static Map doGetLoggerInfo(Logger logger) { + Map info = new HashMap(); + info.put(LoggerHelper.name, logger.getName()); + info.put(LoggerHelper.clazz, logger.getClass()); + info.put(LoggerHelper.additivity, logger.getAdditivity()); + + Level level = logger.getLevel(), effectiveLevel = logger.getEffectiveLevel(); + if (level != null) { + info.put(LoggerHelper.level, level.toString()); + } + if (effectiveLevel != null) { + info.put(LoggerHelper.effectiveLevel, effectiveLevel.toString()); + } + + @SuppressWarnings("unchecked") + List> result = doGetLoggerAppenders(logger.getAllAppenders()); + info.put(LoggerHelper.appenders, result); + return info; + } + + private static List> doGetLoggerAppenders(Enumeration appenders) { + List> result = new ArrayList>(); + + while (appenders.hasMoreElements()) { + Map info = new HashMap(); + Appender appender = appenders.nextElement(); + + 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).getFile()); + } else if (appender instanceof ConsoleAppender) { + info.put(LoggerHelper.target, ((ConsoleAppender) appender).getTarget()); + } else if (appender instanceof AsyncAppender) { + @SuppressWarnings("unchecked") + List> asyncs = doGetLoggerAppenders(((AsyncAppender) appender).getAllAppenders()); + // 标明异步appender + List appenderRef = new ArrayList(); + for (Map a : asyncs) { + appenderRef.add((String) a.get(LoggerHelper.name)); + result.add(a); + } + info.put(LoggerHelper.appenderRef, appenderRef); + } + } + + return result; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/logger/LogbackHelper.java b/core/src/main/java/com/taobao/arthas/core/command/logger/LogbackHelper.java new file mode 100644 index 000000000..53fc16f03 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/LogbackHelper.java @@ -0,0 +1,164 @@ +package com.taobao.arthas.core.command.logger; + +import java.lang.reflect.Field; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.ILoggerFactory; + +import ch.qos.logback.classic.AsyncAppender; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.pattern.PatternLayoutBase; + +/** + * + * @author hengyunabc 2019-09-06 + * + */ +public class LogbackHelper { + + private static boolean Logback = false; + private static Field headField, lengthOptionField; + private static ILoggerFactory loggerFactoryInstance; + + static { + try { + Class loggerClass = Class.forName("ch.qos.logback.classic.Logger"); + // 这里可能会加载到应用中依赖的logback,因此需要判断classloader + if (loggerClass.getClassLoader().equals(LogbackHelper.class.getClassLoader())) { + ILoggerFactory loggerFactory = org.slf4j.LoggerFactory.getILoggerFactory(); + + if (loggerFactory instanceof LoggerContext) { + loggerFactoryInstance = loggerFactory; + + headField = PatternLayoutBase.class.getDeclaredField("head"); + headField.setAccessible(true); + + lengthOptionField = ThrowableProxyConverter.class.getDeclaredField("lengthOption"); + lengthOptionField.setAccessible(true); + + Logback = true; + } + } + } catch (Throwable t) { + // ignore + } + } + + public static Boolean updateLevel(String name, String level) { + if (Logback) { + try { + Level l = Level.toLevel(level, Level.ERROR); + LoggerContext loggerContext = (LoggerContext) loggerFactoryInstance; + + Logger logger = loggerContext.exists(name); + if (logger != null) { + logger.setLevel(l); + return true; + } + } catch (Throwable t) { + // ignore + } + return false; + } + return null; + } + + public static Map> getLoggers(String name, boolean includeNoAppender) { + Map> loggerInfoMap = new LinkedHashMap>(); + + if (Logback) { + LoggerContext loggerContext = (LoggerContext) loggerFactoryInstance; + if (name != null && !name.trim().isEmpty()) { + Logger logger = loggerContext.exists(name); + if (logger != null) { + loggerInfoMap.put(name, doGetLoggerInfo(logger)); + } + } else { + // 获取所有logger时,如果没有appender则忽略 + List loggers = loggerContext.getLoggerList(); + for (Logger logger : loggers) { + Map info = doGetLoggerInfo(logger); + + if (!includeNoAppender) { + List appenders = (List) info.get(LoggerHelper.appenders); + if (appenders != null && !appenders.isEmpty()) { + loggerInfoMap.put(logger.getName(), info); + } + } else { + loggerInfoMap.put(logger.getName(), info); + } + + } + } + } + + return loggerInfoMap; + } + + private static Map doGetLoggerInfo(Logger logger) { + Map info = new LinkedHashMap(); + 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.isAdditive()); + + Level level = logger.getLevel(), effectiveLevel = logger.getEffectiveLevel(); + if (level != null) { + info.put(LoggerHelper.level, level.toString()); + } + if (effectiveLevel != null) { + info.put(LoggerHelper.effectiveLevel, effectiveLevel.toString()); + } + + List> result = doGetLoggerAppenders(logger.iteratorForAppenders()); + info.put(LoggerHelper.appenders, result); + return info; + } + + @SuppressWarnings("rawtypes") + private static List> doGetLoggerAppenders(Iterator> appenders) { + List> result = new ArrayList>(); + + while (appenders.hasNext()) { + Map info = new LinkedHashMap(); + Appender appender = appenders.next(); + info.put(LoggerHelper.name, appender.getName()); + info.put(LoggerHelper.clazz, appender.getClass()); + if (appender instanceof FileAppender) { + info.put(LoggerHelper.file, ((FileAppender) appender).getFile()); + } else if (appender instanceof AsyncAppender) { + AsyncAppender aa = (AsyncAppender) appender; + Iterator> iter = aa.iteratorForAppenders(); + List> asyncs = doGetLoggerAppenders(iter); + + // 异步appender所 ref的 appender,参考: https://logback.qos.ch/manual/appenders.html + List appenderRef = new ArrayList(); + for (Map a : asyncs) { + appenderRef.add((String) a.get(LoggerHelper.name)); + result.add(a); + } + info.put(LoggerHelper.appenderRef, appenderRef); + } else if (appender instanceof ConsoleAppender) { + info.put(LoggerHelper.target, ((ConsoleAppender) appender).getTarget()); + } + result.add(info); + } + + return result; + } +} 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 new file mode 100644 index 000000000..d13609055 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java @@ -0,0 +1,337 @@ +package com.taobao.arthas.core.command.logger; + +import static com.taobao.text.ui.Element.label; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.taobao.arthas.common.IOUtils; +import com.taobao.arthas.common.ReflectUtils; +import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.shell.command.AnnotatedCommand; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.ClassLoaderUtils; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.middleware.cli.annotations.Description; +import com.taobao.middleware.cli.annotations.Name; +import com.taobao.middleware.cli.annotations.Option; +import com.taobao.middleware.cli.annotations.Summary; +import com.taobao.middleware.logger.Logger; +import com.taobao.text.Decoration; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +/** + * logger command + * + * TODO support log4j2 + * + * @author hengyunabc 2019-09-04 + * + */ +@Name("logger") +@Summary("Print logger info, and update the logger level") +@Description("\nExamples:\n" + " logger\n" + " logger -c 327a647b\n" + " logger --name ROOT --level debug\n" + + Constants.WIKI + Constants.WIKI_HOME + "logger") +public class LoggerCommand extends AnnotatedCommand { + private static final Logger logger = LogUtil.getArthasLogger(); + + private static byte[] LoggerHelperBytes; + private static byte[] Log4jHelperBytes; + private static byte[] LogbackHelperBytes; + + private static Map, byte[]> classToBytesMap = new HashMap, byte[]>(); + + static { + LoggerHelperBytes = loadClassBytes(LoggerHelper.class); + Log4jHelperBytes = loadClassBytes(Log4jHelper.class); + LogbackHelperBytes = loadClassBytes(LogbackHelper.class); + + classToBytesMap.put(LoggerHelper.class, LoggerHelperBytes); + classToBytesMap.put(Log4jHelper.class, Log4jHelperBytes); + classToBytesMap.put(LogbackHelper.class, LogbackHelperBytes); + } + + private String name; + + private String hashCode; + + private String level; + + /** + * include the logger don't have appender, default false. + */ + private boolean includeNoAppender; + + /** + * include the arthas logger, default false. + */ + private boolean includeArthasLogger; + + @Option(shortName = "n", longName = "name") + @Description("logger name") + public void setName(String name) { + this.name = name; + } + + @Option(shortName = "c", longName = "classloader") + @Description("classLoader hashcode") + public void setHashCode(String hashCode) { + this.hashCode = hashCode; + } + + @Option(shortName = "l", longName = "level") + @Description("set logger level") + public void setLevel(String level) { + this.level = level; + } + + @Option(longName = "include-no-appender", flag = true) + @Description("include the loggers don't have appender, default value false") + public void setHaveAppender(boolean includeNoAppender) { + this.includeNoAppender = includeNoAppender; + } + + @Option(longName = "include-arthas-logger", flag = true) + @Description("include the arthas loggers, default value false") + public void setIncludeArthasLogger(boolean includeArthasLogger) { + this.includeArthasLogger = includeArthasLogger; + } + + @Override + public void process(CommandProcess process) { + int status = 0; + try { + if (this.name != null && this.level != null) { + level(process); + } else { + loggers(process, name); + } + } finally { + process.end(status); + } + } + + public void level(CommandProcess process) { + Instrumentation inst = process.session().getInstrumentation(); + boolean result = false; + try { + Boolean updateResult = this.updateLevel(inst, Log4jHelper.class); + if (Boolean.TRUE.equals(updateResult)) { + result = true; + } + } catch (Throwable e) { + logger.error("arthas", "logger command update log4j level error", e); + } + + try { + Boolean updateResult = this.updateLevel(inst, LogbackHelper.class); + if (Boolean.TRUE.equals(updateResult)) { + result = true; + } + } catch (Throwable e) { + logger.error("arthas", "logger command update logback level error", e); + } + + if (result) { + process.write("update logger level success.\n"); + } else { + process.write("update logger level fail.\n"); + } + } + + public void loggers(CommandProcess process, String name) { + Map classLoaderLoggerMap = new LinkedHashMap(); + + for (Class clazz : process.session().getInstrumentation().getAllLoadedClasses()) { + String className = clazz.getName(); + ClassLoader classLoader = clazz.getClassLoader(); + + // skip the arthas classloader + if (this.includeArthasLogger == false && classLoader != null && this.getClass().getClassLoader().getClass() + .getName().equals(classLoader.getClass().getName())) { + continue; + } + + // if special classloader + if (this.hashCode != null && !this.hashCode.equals(StringUtils.classLoaderHash(clazz))) { + continue; + } + + if (classLoader != null) { + LoggerTypes loggerTypes = classLoaderLoggerMap.get(classLoader); + if (loggerTypes == null) { + loggerTypes = new LoggerTypes(); + classLoaderLoggerMap.put(classLoader, loggerTypes); + } + if ("org.apache.log4j.Logger".equals(className)) { + loggerTypes.addType(LoggerType.LOG4J); + } else if ("ch.qos.logback.classic.Logger".equals(className)) { + loggerTypes.addType(LoggerType.LOGBACK); + } + } + } + + for (Entry entry : classLoaderLoggerMap.entrySet()) { + ClassLoader classLoader = entry.getKey(); + LoggerTypes loggerTypes = entry.getValue(); + + if (loggerTypes.contains(LoggerType.LOG4J)) { + Map> loggerInfoMap = loggerInfo(classLoader, Log4jHelper.class); + String renderResult = renderLoggerInfo(loggerInfoMap, process.width()); + + process.write(renderResult); + } + + if (loggerTypes.contains(LoggerType.LOGBACK)) { + Map> loggerInfoMap = loggerInfo(classLoader, LogbackHelper.class); + String renderResult = renderLoggerInfo(loggerInfoMap, process.width()); + + process.write(renderResult); + } + + } + + } + + private String renderLoggerInfo(Map> loggerInfos, int width) { + StringBuilder sb = new StringBuilder(8192); + + for (Entry> entry : loggerInfos.entrySet()) { + Map info = entry.getValue(); + + TableElement table = new TableElement(2, 10).leftCellPadding(1).rightCellPadding(1); + TableElement appendersTable = new TableElement().rightCellPadding(1); + + Class clazz = (Class) info.get(LoggerHelper.clazz); + table.row(label(LoggerHelper.name).style(Decoration.bold.bold()), label("" + info.get(LoggerHelper.name))) + .row(label(LoggerHelper.clazz).style(Decoration.bold.bold()), label("" + clazz.getName())) + .row(label(LoggerHelper.classLoader).style(Decoration.bold.bold()), + label("" + clazz.getClassLoader())) + .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))) + .row(label(LoggerHelper.codeSource).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.codeSource))); + + @SuppressWarnings("unchecked") + List> appenders = (List>) info.get(LoggerHelper.appenders); + if (appenders != null && !appenders.isEmpty()) { + + for (Map appenderInfo : appenders) { + Class appenderClass = (Class) appenderInfo.get(LoggerHelper.clazz); + + appendersTable.row(label(LoggerHelper.name).style(Decoration.bold.bold()), + label("" + appenderInfo.get(LoggerHelper.name))); + appendersTable.row(label(LoggerHelper.clazz), label("" + appenderClass.getName())); + appendersTable.row(label(LoggerHelper.classLoader), label("" + appenderClass.getClassLoader())); + appendersTable.row(label(LoggerHelper.classLoaderHash), + label("" + StringUtils.classLoaderHash(appenderClass))); + if (appenderInfo.get(LoggerHelper.file) != null) { + appendersTable.row(label(LoggerHelper.file), label("" + appenderInfo.get(LoggerHelper.file))); + } + if (appenderInfo.get(LoggerHelper.target) != null) { + appendersTable.row(label(LoggerHelper.target), + label("" + appenderInfo.get(LoggerHelper.target))); + } + if (appenderInfo.get(LoggerHelper.appenderRef) != null) { + appendersTable.row(label(LoggerHelper.appenderRef), + label("" + appenderInfo.get(LoggerHelper.appenderRef))); + } + } + + table.row(label("appenders").style(Decoration.bold.bold()), appendersTable); + } + + sb.append(RenderUtil.render(table, width)).append('\n'); + } + return sb.toString(); + } + + @SuppressWarnings("unchecked") + private Map> loggerInfo(ClassLoader classLoader, Class helperClass) { + Map> loggers = Collections.emptyMap(); + try { + classLoader.loadClass(helperClass.getName()); + } catch (ClassNotFoundException e) { + try { + ReflectUtils.defineClass(helperClass.getName(), classToBytesMap.get(helperClass), classLoader); + } catch (Exception e1) { + // ignore + } + } + + try { + Class clazz = classLoader.loadClass(helperClass.getName()); + Method getLoggersMethod = clazz.getMethod("getLoggers", new Class[] { String.class, boolean.class }); + loggers = (Map>) getLoggersMethod.invoke(null, + new Object[] { name, includeNoAppender }); + } catch (Throwable e) { + // ignore + } + return loggers; + } + + private Boolean updateLevel(Instrumentation inst, Class helperClass) throws Exception { + ClassLoader classLoader = null; + if (hashCode == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } else { + classLoader = ClassLoaderUtils.getClassLoader(inst, hashCode); + } + + Class clazz = classLoader.loadClass(helperClass.getName()); + Method updateLevelMethod = clazz.getMethod("updateLevel", new Class[] { String.class, String.class }); + return (Boolean) updateLevelMethod.invoke(null, new Object[] { this.name, this.level }); + + } + + static enum LoggerType { + LOG4J, LOGBACK + } + + static class LoggerTypes { + Set types = new HashSet(); + + public Collection types() { + return types; + } + + public void addType(LoggerType type) { + types.add(type); + } + + public boolean contains(LoggerType type) { + return types.contains(type); + } + } + + private static byte[] loadClassBytes(Class clazz) { + try { + InputStream stream = LoggerCommand.class.getClassLoader() + .getResourceAsStream(clazz.getName().replace('.', '/') + ".class"); + + return IOUtils.getBytes(stream); + } catch (IOException e) { + // ignore + return null; + } + } +} 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 new file mode 100644 index 000000000..78aa8ff24 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerHelper.java @@ -0,0 +1,28 @@ +package com.taobao.arthas.core.command.logger; + +/** + * + * @author hengyunabc 2019-09-06 + * + */ +public interface LoggerHelper { + public static final String clazz = "class"; + public static final String classLoader = "classLoader"; + public static final String classLoaderHash = "classLoaderHash"; + public static final String codeSource = "codeSource"; + + // logger info + public static final String level = "level"; + public static final String effectiveLevel = "effectiveLevel"; + // type boolean + public static final String additivity = "additivity"; + public static final String appenders = "appenders"; + + // appender info + public static final String name = "name"; + public static final String file = "file"; + // type List + public static final String appenderRef = "appenderRef"; + public static final String target = "target"; + +} diff --git a/site/src/site/sphinx/en/logger.md b/site/src/site/sphinx/en/logger.md new file mode 100644 index 000000000..53208579c --- /dev/null +++ b/site/src/site/sphinx/en/logger.md @@ -0,0 +1,184 @@ +logger +=== + +> Print the logger information, update the logger level + +### Usage + +#### Print the logger information + +Take the following `logback.xml` as an example: + +```xml + + + + app.log + + mylog-%d{yyyy-MM-dd}.%i.txt + 100MB + 60 + 2GB + + + %logger{35} - %msg%n + + + + + + + + + + %-4relative [%thread] %-5level %logger{35} - %msg %n + + utf8 + + + + + + + + +``` + + +The result of the `logger` command: + +```bash +[arthas@2062]$ logger + name ROOT + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level INFO + effectiveLevel INFO + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + appenders name CONSOLE + class ch.qos.logback.core.ConsoleAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + target System.out + name APPLICATION + class ch.qos.logback.core.rolling.RollingFileAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + file app.log + name ASYNC + class ch.qos.logback.classic.AsyncAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + appenderRef [APPLICATION] +``` + +In the `appenders` section: + +* The target of `CONSOLE` logger is `System.out` +* `APPLICATION` logger is `RollingFileAppender`, the file is `app.log` +* `ASYNC` its `appenderRef` is `APPLICATION`, which means asynchronous output to the file + +#### View logger information for the special name + +```bash +[arthas@2062]$ logger -n org.springframework.web + name org.springframework.web + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level null + effectiveLevel INFO + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar +``` + +#### View logger information for the special classloader + +```bash +[arthas@2062]$ logger -c 2a139a55 + name ROOT + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level DEBUG + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + appenders name CONSOLE + class ch.qos.logback.core.ConsoleAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + target System.out + name APPLICATION + class ch.qos.logback.core.rolling.RollingFileAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + file app.log + name ASYNC + class ch.qos.logback.classic.AsyncAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + appenderRef [APPLICATION] +``` + +#### Update logger level + +```bash +[arthas@2062]$ logger --name ROOT --level debug +update logger level success. +``` + +#### View the logger information without appenders + + +By default, the `logger` command only prints information about the logger with appenders. If you want to see information about loggers without `appender`, you can use the parameter `--include-no-appender`. + +Note that the output will usually be very long. + +```bash +[arthas@2062]$ logger --include-no-appender + name ROOT + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level DEBUG + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + appenders name CONSOLE + class ch.qos.logback.core.ConsoleAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + target System.out + name APPLICATION + class ch.qos.logback.core.rolling.RollingFileAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + file app.log + name ASYNC + class ch.qos.logback.classic.AsyncAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + appenderRef [APPLICATION] + + name com + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level null + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + + name com.alibaba + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level null + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar +... +``` diff --git a/site/src/site/sphinx/logger.md b/site/src/site/sphinx/logger.md new file mode 100644 index 000000000..bf528d8d6 --- /dev/null +++ b/site/src/site/sphinx/logger.md @@ -0,0 +1,185 @@ +logger +=== + +> 查看logger信息,更新logger level + +### 使用参考 + +#### 查看所有logger信息 + +以下面的`logback.xml`为例: + +```xml + + + + app.log + + mylog-%d{yyyy-MM-dd}.%i.txt + 100MB + 60 + 2GB + + + %logger{35} - %msg%n + + + + + + + + + + %-4relative [%thread] %-5level %logger{35} - %msg %n + + utf8 + + + + + + + + +``` + + +使用`logger`命令打印的结果是: + +```bash +[arthas@2062]$ logger + name ROOT + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level INFO + effectiveLevel INFO + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + appenders name CONSOLE + class ch.qos.logback.core.ConsoleAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + target System.out + name APPLICATION + class ch.qos.logback.core.rolling.RollingFileAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + file app.log + name ASYNC + class ch.qos.logback.classic.AsyncAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + appenderRef [APPLICATION] +``` + +从`appenders`的信息里,可以看到 + +* `CONSOLE` logger的target是`System.out` +* `APPLICATION` logger是`RollingFileAppender`,它的file是`app.log` +* `ASYNC`它的`appenderRef`是`APPLICATION`,即异步输出到文件里 + + +#### 查看指定名字的logger信息 + +```bash +[arthas@2062]$ logger -n org.springframework.web + name org.springframework.web + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level null + effectiveLevel INFO + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar +``` + +#### 查看指定classloader的logger信息 + +```bash +[arthas@2062]$ logger -c 2a139a55 + name ROOT + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level DEBUG + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + appenders name CONSOLE + class ch.qos.logback.core.ConsoleAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + target System.out + name APPLICATION + class ch.qos.logback.core.rolling.RollingFileAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + file app.log + name ASYNC + class ch.qos.logback.classic.AsyncAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + appenderRef [APPLICATION] +``` + +#### 更新logger level + +```bash +[arthas@2062]$ logger --name ROOT --level debug +update logger level success. +``` + +#### 查看没有appender的logger的信息 + + +默认情况下,`logger`命令只打印有appender的logger的信息。如果想查看没有`appender`的logger的信息,可以加上参数`--include-no-appender`。 + +注意,通常输出结果会很长。 + +```bash +[arthas@2062]$ logger --include-no-appender + name ROOT + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level DEBUG + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + appenders name CONSOLE + class ch.qos.logback.core.ConsoleAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + target System.out + name APPLICATION + class ch.qos.logback.core.rolling.RollingFileAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + file app.log + name ASYNC + class ch.qos.logback.classic.AsyncAppender + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + appenderRef [APPLICATION] + + name com + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level null + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar + + name com.alibaba + class ch.qos.logback.classic.Logger + classLoader sun.misc.Launcher$AppClassLoader@2a139a55 + classLoaderHash 2a139a55 + level null + effectiveLevel DEBUG + additivity true + codeSource file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar +... +```