mirror of https://github.com/alibaba/arthas.git
add logger command. #736
parent
859777f92b
commit
89bd23b9de
@ -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;
|
||||
}
|
||||
}
|
@ -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<Method> OBJECT_METHODS = new ArrayList<Method>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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<Class<?>, byte[]> classToBytesMap = new HashMap<Class<?>, 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<ClassLoader, LoggerTypes> classLoaderLoggerMap = new LinkedHashMap<ClassLoader, LoggerTypes>();
|
||||
|
||||
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<ClassLoader, LoggerTypes> entry : classLoaderLoggerMap.entrySet()) {
|
||||
ClassLoader classLoader = entry.getKey();
|
||||
LoggerTypes loggerTypes = entry.getValue();
|
||||
|
||||
if (loggerTypes.contains(LoggerType.LOG4J)) {
|
||||
Map<String, Map<String, Object>> loggerInfoMap = loggerInfo(classLoader, Log4jHelper.class);
|
||||
String renderResult = renderLoggerInfo(loggerInfoMap, process.width());
|
||||
|
||||
process.write(renderResult);
|
||||
}
|
||||
|
||||
if (loggerTypes.contains(LoggerType.LOGBACK)) {
|
||||
Map<String, Map<String, Object>> loggerInfoMap = loggerInfo(classLoader, LogbackHelper.class);
|
||||
String renderResult = renderLoggerInfo(loggerInfoMap, process.width());
|
||||
|
||||
process.write(renderResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String renderLoggerInfo(Map<String, Map<String, Object>> loggerInfos, int width) {
|
||||
StringBuilder sb = new StringBuilder(8192);
|
||||
|
||||
for (Entry<String, Map<String, Object>> entry : loggerInfos.entrySet()) {
|
||||
Map<String, Object> 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<Map<String, Object>> appenders = (List<Map<String, Object>>) info.get(LoggerHelper.appenders);
|
||||
if (appenders != null && !appenders.isEmpty()) {
|
||||
|
||||
for (Map<String, Object> 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<String, Map<String, Object>> loggerInfo(ClassLoader classLoader, Class<?> helperClass) {
|
||||
Map<String, Map<String, Object>> 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<String, Map<String, Object>>) 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<LoggerType> types = new HashSet<LoggerType>();
|
||||
|
||||
public Collection<LoggerType> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String>
|
||||
public static final String appenderRef = "appenderRef";
|
||||
public static final String target = "target";
|
||||
|
||||
}
|
@ -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
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="APPLICATION" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>app.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<maxHistory>60</maxHistory>
|
||||
<totalSizeCap>2GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%logger{35} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="APPLICATION" />
|
||||
</appender>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n
|
||||
</pattern>
|
||||
<charset>utf8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="ASYNC" />
|
||||
</root>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
|
||||
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
|
||||
...
|
||||
```
|
Loading…
Reference in New Issue