forked from MirrorHub/authlib-injector
Support intercepting logs from authlib
This commit is contained in:
parent
1d73a05413
commit
d6cf905a38
4 changed files with 239 additions and 3 deletions
|
@ -39,6 +39,7 @@ gradle
|
||||||
* `transform.skipped` 分析了却未执行字节码修改的类(用于性能分析)
|
* `transform.skipped` 分析了却未执行字节码修改的类(用于性能分析)
|
||||||
* `config` 有关配置获取的
|
* `config` 有关配置获取的
|
||||||
* `httpd` 有关本地 HTTP 服务器的(其负责在本地处理掉部分请求,而不是发送到 Yggdrasil 服务端)
|
* `httpd` 有关本地 HTTP 服务器的(其负责在本地处理掉部分请求,而不是发送到 Yggdrasil 服务端)
|
||||||
|
* `authlib` 打印从 authlib 处获取的日志(其记录了网络调用的详细信息)
|
||||||
|
|
||||||
可以指定多个类型,中间用 `,` 分隔。如果要打印以上所有调试信息,可以设置其为 `all`。
|
可以指定多个类型,中间用 `,` 分隔。如果要打印以上所有调试信息,可以设置其为 `all`。
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ import java.util.Base64;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import moe.yushi.authlibinjector.transform.AuthlibLogInterceptor;
|
||||||
import moe.yushi.authlibinjector.transform.ClassTransformer;
|
import moe.yushi.authlibinjector.transform.ClassTransformer;
|
||||||
import moe.yushi.authlibinjector.transform.DumpClassListener;
|
import moe.yushi.authlibinjector.transform.DumpClassListener;
|
||||||
import moe.yushi.authlibinjector.transform.SkinWhitelistTransformUnit;
|
import moe.yushi.authlibinjector.transform.SkinWhitelistTransformUnit;
|
||||||
|
@ -271,6 +273,10 @@ public final class AuthlibInjector {
|
||||||
transformer.listeners.add(new DumpClassListener(Paths.get("").toAbsolutePath()));
|
transformer.listeners.add(new DumpClassListener(Paths.get("").toAbsolutePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Logging.isDebugOnFor(Logging.PREFIX + ".authlib")) {
|
||||||
|
transformer.units.add(new AuthlibLogInterceptor());
|
||||||
|
}
|
||||||
|
|
||||||
if (!"true".equals(System.getProperty(PROP_DISABLE_HTTPD))) {
|
if (!"true".equals(System.getProperty(PROP_DISABLE_HTTPD))) {
|
||||||
transformer.units.add(new LocalYggdrasilApiTransformUnit(config));
|
transformer.units.add(new LocalYggdrasilApiTransformUnit(config));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
package moe.yushi.authlibinjector.transform;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.Opcodes.ASM6;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
import moe.yushi.authlibinjector.util.Logging;
|
||||||
|
|
||||||
|
public class AuthlibLogInterceptor implements TransformUnit {
|
||||||
|
|
||||||
|
private static Set<ClassLoader> interceptedClassloaders = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
|
|
||||||
|
public static void onClassLoading(ClassLoader classLoader) {
|
||||||
|
Class<?> classLogManager;
|
||||||
|
try {
|
||||||
|
classLogManager = classLoader.loadClass("org.apache.logging.log4j.LogManager");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader clLog4j = classLogManager.getClassLoader();
|
||||||
|
synchronized (interceptedClassloaders) {
|
||||||
|
if (!interceptedClassloaders.add(clLog4j)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
registerLogHandle(clLog4j);
|
||||||
|
Logging.TRANSFORM.info("Registered log handler on " + clLog4j);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Logging.TRANSFORM.log(Level.WARNING, "Failed to register log handler on " + clLog4j, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
private static void registerLogHandle(ClassLoader cl) throws ReflectiveOperationException {
|
||||||
|
final String appenderName = "AUTHLIB_INJECTOR_CONSOLE_APPENDER";
|
||||||
|
final String loggerName = "AUTHLIB_INJECTOR_AUTHLIB_LOGGER";
|
||||||
|
final String authlibPackageName = "com.mojang.authlib";
|
||||||
|
|
||||||
|
Class<?> classLayout = cl.loadClass("org.apache.logging.log4j.core.Layout");
|
||||||
|
Class<?> classAppender = cl.loadClass("org.apache.logging.log4j.core.Appender");
|
||||||
|
Class<?> classAppenderRef = cl.loadClass("org.apache.logging.log4j.core.config.AppenderRef");
|
||||||
|
Class<?> classLevel = cl.loadClass("org.apache.logging.log4j.Level");
|
||||||
|
Class<?> classFilter = cl.loadClass("org.apache.logging.log4j.core.Filter");
|
||||||
|
Class<?> classLoggerConfig = cl.loadClass("org.apache.logging.log4j.core.config.LoggerConfig");
|
||||||
|
Class<?> classConfiguration = cl.loadClass("org.apache.logging.log4j.core.config.Configuration");
|
||||||
|
Class<?> classPatternLayout = cl.loadClass("org.apache.logging.log4j.core.layout.PatternLayout");
|
||||||
|
Class<?> classConsoleAppender = cl.loadClass("org.apache.logging.log4j.core.appender.ConsoleAppender");
|
||||||
|
|
||||||
|
Object loggerContext = cl.loadClass("org.apache.logging.log4j.LogManager").getDeclaredMethod("getContext", boolean.class).invoke(null, false);
|
||||||
|
Object configuration = cl.loadClass("org.apache.logging.log4j.core.LoggerContext").getMethod("getConfiguration").invoke(loggerContext);
|
||||||
|
|
||||||
|
Object patternLayout;
|
||||||
|
try {
|
||||||
|
Object builder = classPatternLayout.getDeclaredMethod("newBuilder").invoke(null);
|
||||||
|
Class<?> classBuilder = cl.loadClass("org.apache.logging.log4j.core.layout.PatternLayout$Builder");
|
||||||
|
patternLayout = classBuilder.getMethod("build").invoke(builder);
|
||||||
|
} catch (NoSuchMethodException ex) {
|
||||||
|
// prior to https://github.com/apache/logging-log4j2/commit/7aabb11111c69f452d674b00845b66b82c80afa4
|
||||||
|
Map<String, Object> values = new HashMap<>();
|
||||||
|
values.put("alwaysWriteExceptions", true);
|
||||||
|
values.put("noConsoleNoAnsi", true);
|
||||||
|
patternLayout = invokeCreateMethod(classPatternLayout, "createLayout", configuration, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object appender;
|
||||||
|
try {
|
||||||
|
Object buider = classConsoleAppender.getDeclaredMethod("newBuilder").invoke(null);
|
||||||
|
Class<?> classBuilder = cl.loadClass("org.apache.logging.log4j.core.appender.ConsoleAppender$Builder");
|
||||||
|
classBuilder.getMethod("withLayout", classLayout).invoke(buider, patternLayout);
|
||||||
|
classBuilder.getMethod("withName", String.class).invoke(buider, appenderName);
|
||||||
|
appender = classBuilder.getMethod("build").invoke(buider);
|
||||||
|
} catch (NoSuchMethodException ex) {
|
||||||
|
Map<String, Object> values = new HashMap<>();
|
||||||
|
values.put("Layout", patternLayout);
|
||||||
|
values.put("name", appenderName);
|
||||||
|
values.put("follow", false);
|
||||||
|
values.put("direct", false);
|
||||||
|
values.put("ignoreExceptions", true);
|
||||||
|
appender = invokeCreateMethod(classConsoleAppender, "createAppender", configuration, values);
|
||||||
|
}
|
||||||
|
classAppender.getMethod("start").invoke(appender);
|
||||||
|
|
||||||
|
Object appenderRef;
|
||||||
|
{
|
||||||
|
Map<String, Object> values = new HashMap<>();
|
||||||
|
values.put("ref", appenderName);
|
||||||
|
appenderRef = invokeCreateMethod(classAppenderRef, "createAppenderRef", configuration, values);
|
||||||
|
}
|
||||||
|
Object appenderRefs = Array.newInstance(classAppenderRef, 1);
|
||||||
|
Array.set(appenderRefs, 0, appenderRef);
|
||||||
|
|
||||||
|
Object loggerConfig;
|
||||||
|
{
|
||||||
|
Map<String, Object> values = new HashMap<>();
|
||||||
|
values.put("additivity", true);
|
||||||
|
values.put("level", classLevel.getDeclaredField("ALL").get(null));
|
||||||
|
values.put("name", loggerName);
|
||||||
|
values.put("includeLocation", authlibPackageName);
|
||||||
|
values.put("AppenderRef", appenderRefs);
|
||||||
|
loggerConfig = invokeCreateMethod(classLoggerConfig, "createLogger", configuration, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
classLoggerConfig.getMethod("addAppender", classAppender, classLevel, classFilter).invoke(loggerConfig, appender, null, null);
|
||||||
|
try {
|
||||||
|
classConfiguration.getMethod("addAppender", classAppender).invoke(configuration, appender);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
((Map) classConfiguration.getMethod("getAppenders").invoke(configuration)).put(appenderName, appender);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
classConfiguration.getMethod("addLogger", String.class, classLoggerConfig).invoke(configuration, authlibPackageName, loggerConfig);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// prior to https://github.com/apache/logging-log4j2/commit/f9744033b93e2fe1f52a27c66f24f53cf44939a2
|
||||||
|
// bypass the check in https://github.com/apache/logging-log4j2/blob/log4j-2.0-beta9/log4j-core/src/main/java/org/apache/logging/log4j/core/config/BaseConfiguration.java#L554
|
||||||
|
Class<?> classBaseConfiguration = cl.loadClass("org.apache.logging.log4j.core.config.BaseConfiguration");
|
||||||
|
Field fieldLoggers = classBaseConfiguration.getDeclaredField("loggers");
|
||||||
|
fieldLoggers.setAccessible(true);
|
||||||
|
((Map) fieldLoggers.get(configuration)).put(authlibPackageName, loggerConfig);
|
||||||
|
Method methodSetParents = classBaseConfiguration.getDeclaredMethod("setParents");
|
||||||
|
methodSetParents.setAccessible(true);
|
||||||
|
methodSetParents.invoke(configuration);
|
||||||
|
}
|
||||||
|
cl.loadClass("org.apache.logging.log4j.core.LoggerContext").getMethod("updateLoggers", classConfiguration).invoke(loggerContext, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object invokeCreateMethod(Class<?> owner, String methodName, Object config, Map<String, Object> values) throws ReflectiveOperationException {
|
||||||
|
ClassLoader cl = owner.getClassLoader();
|
||||||
|
Class<?> classConfiguration = cl.loadClass("org.apache.logging.log4j.core.config.Configuration");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<? extends Annotation> classPluginAttribute = (Class<? extends Annotation>) cl.loadClass("org.apache.logging.log4j.core.config.plugins.PluginAttribute");
|
||||||
|
Method methodPluginAttributeValue = classPluginAttribute.getMethod("value");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<? extends Annotation> classPluginElement = (Class<? extends Annotation>) cl.loadClass("org.apache.logging.log4j.core.config.plugins.PluginElement");
|
||||||
|
Method methodPluginElementValue = classPluginElement.getMethod("value");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<? extends Annotation> classPluginFactory = (Class<? extends Annotation>) cl.loadClass("org.apache.logging.log4j.core.config.plugins.PluginFactory");
|
||||||
|
|
||||||
|
Method method = Stream.of(owner.getDeclaredMethods())
|
||||||
|
.filter(it -> it.getName().equals(methodName))
|
||||||
|
.filter(it -> it.getDeclaredAnnotation(classPluginFactory) != null)
|
||||||
|
.findFirst().orElseThrow(NoSuchMethodException::new);
|
||||||
|
|
||||||
|
Object[] input = new Object[method.getParameterCount()];
|
||||||
|
Parameter[] parameters = method.getParameters();
|
||||||
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
|
Class<?> type = parameters[i].getType();
|
||||||
|
String key = null;
|
||||||
|
|
||||||
|
Annotation annotation = parameters[i].getDeclaredAnnotation(classPluginAttribute);
|
||||||
|
if (annotation != null) {
|
||||||
|
key = (String) methodPluginAttributeValue.invoke(annotation);
|
||||||
|
} else {
|
||||||
|
annotation = parameters[i].getDeclaredAnnotation(classPluginElement);
|
||||||
|
if (annotation != null) {
|
||||||
|
key = (String) methodPluginElementValue.invoke(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
if (classConfiguration.isAssignableFrom(type)) {
|
||||||
|
input[i] = config;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object value = values.get(key);
|
||||||
|
if (value != null) {
|
||||||
|
if (type.isPrimitive() || type.isInstance(value)) {
|
||||||
|
input[i] = value;
|
||||||
|
} else if (type == String.class) {
|
||||||
|
input[i] = value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return method.invoke(null, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ClassVisitor> transform(String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||||
|
if (className.startsWith("com.mojang.authlib.")) {
|
||||||
|
return Optional.of(new ClassVisitor(ASM6, writer) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||||
|
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
if ("<clinit>".equals(name)) {
|
||||||
|
modifiedCallback.run();
|
||||||
|
mv.visitLdcInsn(Type.getType("L" + className.replace('.', '/') + ";"));
|
||||||
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false);
|
||||||
|
mv.visitMethodInsn(INVOKESTATIC, AuthlibLogInterceptor.class.getName().replace('.', '/'), "onClassLoading", "(Ljava/lang/ClassLoader;)V", false);
|
||||||
|
}
|
||||||
|
return mv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Authlib Log Interceptor";
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import java.util.logging.StreamHandler;
|
||||||
public final class Logging {
|
public final class Logging {
|
||||||
private Logging() {}
|
private Logging() {}
|
||||||
|
|
||||||
private static final String PREFIX = "moe.yushi.authlibinjector";
|
public static final String PREFIX = "moe.yushi.authlibinjector";
|
||||||
|
|
||||||
public static final Logger ROOT = Logger.getLogger(PREFIX);
|
public static final Logger ROOT = Logger.getLogger(PREFIX);
|
||||||
public static final Logger LAUNCH = Logger.getLogger(PREFIX + ".launch");
|
public static final Logger LAUNCH = Logger.getLogger(PREFIX + ".launch");
|
||||||
|
@ -26,7 +26,11 @@ public final class Logging {
|
||||||
public static final Logger TRANSFORM_SKIPPED = Logger.getLogger(PREFIX + ".transform.skipped");
|
public static final Logger TRANSFORM_SKIPPED = Logger.getLogger(PREFIX + ".transform.skipped");
|
||||||
public static final Logger HTTPD = Logger.getLogger(PREFIX + ".httpd");
|
public static final Logger HTTPD = Logger.getLogger(PREFIX + ".httpd");
|
||||||
|
|
||||||
|
private static Predicate<String> debugLoggerNamePredicate;
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
debugLoggerNamePredicate = createDebugLoggerNamePredicate();
|
||||||
|
|
||||||
initRootLogger();
|
initRootLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +94,10 @@ public final class Logging {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Filter createFilter() {
|
private static Filter createFilter() {
|
||||||
Predicate<String> namePredicate = createDebugLoggerNamePredicate();
|
return log -> log.getLevel().intValue() >= Level.INFO.intValue() || isDebugOnFor(log.getLoggerName());
|
||||||
return log -> log.getLevel().intValue() >= Level.INFO.intValue() || namePredicate.test(log.getLoggerName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isDebugOnFor(String loggerName) {
|
||||||
|
return debugLoggerNamePredicate.test(loggerName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue