In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly explains the "Java ASM uses logback log level dynamic switching method", the article explains the content is simple and clear, easy to learn and understand, the following please follow the editor's ideas slowly in depth, together to study and learn "Java ASM use logback log level dynamic switching method"!
Background
Everything has cause and effect, and everything is event-driven. The log level switching of this scheme is generated under this background:
In a single production environment, there are hundreds of nearly a thousand microservices
Log level switching does not restart the service, and immediate effect is required.
It involves a wide range of areas, such as modifying the code or adding related dependency configurations by business developers, which promotes slow progress.
Dynamic real-time filtering of junk logs in the later stage to reduce io and disk space costs
Introduction to logback
Before waging war with the enemy, we can win a hundred battles only if we first dissolve the enemy's situation. If you want to dynamically switch the logging level of logback, you should at least have a preliminary understanding of logback and see if it provides a ready-made implementation. Here is a brief introduction to what logback has to do with this requirement.
Logback is an open source logging component of java, written by the founder of log4j, and is currently divided into three modules.
Logback-core: core code module
An improved version of logback-classic:log4j, which also implements the interface of slf4j
Logback-access: the access module integrates with the Servlet container to provide access to logs through Http
ContextInitializer class is the logical implementation of logback automatic configuration process.
Log levels are maintained and used by Logger. Its member variable Level is maintained by Logger
The method of filtering log output with three different parameters: filterAndLog_0_Or3Plus, filterAndLog_1 and filterAndLog_2 in Logger
SetLevel in Logger is the maintenance of log level.
Solution
Before you work hard, learn about the plans on the market. It is the idea for designers and even product bosses to seek the best solution.
Option 1: logback automatically scans for updates
This solution is an off-the-shelf implementation of logback, which can achieve the so-called log-level dynamic switching as long as the configuration is enabled. Configuration method: add a timing scanner to the configuration file of logback, such as:
The scheme can be equipped and used by the operation and maintenance personnel without the cost of research and development.
Its disadvantages are:
Restart the service each time the scan interval is adjusted
More than 90% of the scans are useless, because there is no need for frequent switching of log levels on production, and this is not allowed.
It does not take effect in real time. If it is set to scan every minute or several minutes, then it will not take effect immediately after the log level adjustment, but this can be ignored.
This scheme can not meet the needs of our junk log discarding, such as discarding log output according to certain keywords. In view of this historical reason to print a lot of junk logs, considering the time cost, it is impossible for business R & D to optimize.
Scheme 2: ASM dynamically modifies bytecode
Of course, there are other solutions, such as defining the interface api yourself. To directly call the setLevel method in Logger to achieve the purpose of adjusting the level; the integration of springboot.
None of these solutions can avoid the participation of the exclusive role of business development.
Through the dynamic modification instruction of asm, this scheme can not only meet the requirement of adjusting the log level to take effect immediately. It can also meet the needs of filtering logs.
The specific implementation is as follows. Asm will not be introduced here. Students who do not understand need to be familiar with the instructions of asm, java agent and jvm:
1. Idea creates maven project
Second, maven introduces dependency.
Org.ow2.asm asm 7.1 asm-commons org.ow2.asm 7.1 com.sun tools 1.8 system / Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools. Jar org.apache.maven.plugins maven-jar-plugin 3.2.0 agent.LogbackAgentMain True true maven-compiler-plugin 1.8 1.8 UTF-8 ${java.home} / lib/rt.jar
Third, write attrach startup class
Package agent;import java.lang.instrument.Instrumentation;import java.lang.instrument.UnmodifiableClassException;/** * @ author dengbp * @ ClassName LogbackAgentMain * @ Description attach initiator * @ date 6:27 on 3-25-22 PM * / public class LogbackAgentMain {private static String FILTER_CLASS = "ch.qos.logback.classic.Logger"; public static void agentmain (String agentArgs, Instrumentation inst) throws UnmodifiableClassException {System.out.println ("agentArgs:" + agentArgs) Inst.addTransformer (new LogBackFileTransformer (agentArgs), true); Class [] classes = inst.getAllLoadedClasses (); for (int I = 0; I
< classes.length; i++) { if (FILTER_CLASS.equals(classes[i].getName())) { System.out.println("----重新加载Logger开始----"); inst.retransformClasses(classes[i]); System.out.println("----重新加载Logger完毕----"); break; } } }} 四、实现字节码转换处理器 package agent;import jdk.internal.org.objectweb.asm.ClassReader;import jdk.internal.org.objectweb.asm.ClassVisitor;import jdk.internal.org.objectweb.asm.ClassWriter;import java.lang.instrument.ClassFileTransformer;import java.security.ProtectionDomain;/** * @author dengbp * @ClassName LogBackFileTransformer * @Description 字节码文件转换器 * @date 3/25/22 6:25 PM */public class LogBackFileTransformer implements ClassFileTransformer { private final String level; private static String CLASS_NAME = "ch/qos/logback/classic/Logger"; public LogBackFileTransformer(String level) { this.level = level; } @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (!CLASS_NAME.equals(className)) { return classfileBuffer; } ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); ClassVisitor cv1 = new LogBackClassVisitor(cw, level); /*ClassVisitor cv2 = new LogBackClassVisitor(cv1);*/ // asm框架使用到访问模式和责任链模式 // ClassReader 只需要 accept 责任链中的头节点处的 ClassVisitor即可 cr.accept(cv1, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); System.out.println("end..."); return cw.toByteArray(); }} 五、实现Logger元素的访问者 package agent;import jdk.internal.org.objectweb.asm.ClassVisitor;import jdk.internal.org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;/** * @author dengbp * @ClassName LogBackClassVisitor * @Description Logger类元素访问者 * @date 3/25/22 5:01 PM */public class LogBackClassVisitor extends ClassVisitor { private final String level; /** * asm版本 */ private static final int ASM_VERSION = Opcodes.ASM4; public LogBackClassVisitor(ClassVisitor classVisitor, String level) { super(ASM_VERSION, classVisitor); this.level = level; } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); return new LogFilterMethodVisitor(api, mv, access, name, descriptor, level); }} 六、最后实现Logger关键方法的访问者 该访问者(类),实现日志级别的切换,需要对Logger的三个日志过滤方法进行指令的修改。原理是把命令行入参的日志级别参数值覆盖其成员变量effectiveLevelInt的值,由于篇幅过大,只贴核心部分代码,请看下面: package agent;import jdk.internal.org.objectweb.asm.Label;import jdk.internal.org.objectweb.asm.MethodVisitor;import jdk.internal.org.objectweb.asm.commons.AdviceAdapter;import org.objectweb.asm.Opcodes;/** * @author dengbp * @ClassName LogFilterMethodVisitor * @Description Logger类日志过滤方法元素访问者 * @date 3/25/22 5:01 PM */public class LogFilterMethodVisitor extends AdviceAdapter { private String methodName; private final String level; private static final String filterAndLog_1 = "filterAndLog_1"; private static final String filterAndLog_2 = "filterAndLog_2"; private static final String filterAndLog_0_Or3Plus = "filterAndLog_0_Or3Plus"; protected LogFilterMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor, String level) { super(api, methodVisitor, access, name, descriptor); this.methodName = name; this.level = level; } /** * Description 在访问方法的头部时被访问 * @param * @return void * @Author dengbp * @Date 3:36 PM 4/1/22 **/ @Override public void visitCode() { System.out.println("visitCode method"); super.visitCode(); } @Override protected void onMethodEnter() { System.out.println("开始重写日志级别为:"+level); System.out.println("----准备修改方法----"); if (filterAndLog_1.equals(methodName)) { modifyLogLevel_1(); } if (filterAndLog_2.equals(methodName)) { modifyLogLevel_2(); } if (filterAndLog_0_Or3Plus.equals(methodName)) { modifyLogLevel_3(); } System.out.println("重写日志级别成功...."); } 其中modifyLogLevel_1(); modifyLogLevel_2();modifyLogLevel_3();分别对应filterAndLog_1、filterAndLog_2、filterAndLog_0_Or3Plus方法指令的修改。下面只贴modifyLogLevel_1的实现 /** * Description 修改目标方法:filterAndLog_1 * @param * @return void * @Author dengbp * @Date 2:20 PM 3/31/22 **/ private void modifyLogLevel_1(){ Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(390, l0); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitLdcInsn(level); mv.visitMethodInsn(Opcodes.INVOKESTATIC, "ch/qos/logback/classic/Level", "toLevel", "(Ljava/lang/String;)Lch/qos/logback/classic/Level;", false); mv.visitFieldInsn(Opcodes.GETFIELD, "ch/qos/logback/classic/Level", "levelInt", "I"); mv.visitFieldInsn(Opcodes.PUTFIELD, "ch/qos/logback/classic/Logger", "effectiveLevelInt", "I"); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(392, l1); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, "ch/qos/logback/classic/Logger", "loggerContext", "Lch/qos/logback/classic/LoggerContext;"); mv.visitVarInsn(Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 3); mv.visitVarInsn(Opcodes.ALOAD, 4); mv.visitVarInsn(Opcodes.ALOAD, 5); mv.visitVarInsn(Opcodes.ALOAD, 6); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "ch/qos/logback/classic/LoggerContext", "getTurboFilterChainDecision_1", "(Lorg/slf4j/Marker;Lch/qos/logback/classic/Logger;Lch/qos/logback/classic/Level;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Throwable;)Lch/qos/logback/core/spi/FilterReply;", false); mv.visitVarInsn(Opcodes.ASTORE, 7); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLineNumber(394, l2); mv.visitVarInsn(Opcodes.ALOAD, 7); mv.visitFieldInsn(Opcodes.GETSTATIC, "ch/qos/logback/core/spi/FilterReply", "NEUTRAL", "Lch/qos/logback/core/spi/FilterReply;"); Label l3 = new Label(); mv.visitJumpInsn(Opcodes.IF_ACMPNE, l3); Label l4 = new Label(); mv.visitLabel(l4); mv.visitLineNumber(395, l4); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, "ch/qos/logback/classic/Logger", "effectiveLevelInt", "I"); mv.visitVarInsn(Opcodes.ALOAD, 3); mv.visitFieldInsn(Opcodes.GETFIELD, "ch/qos/logback/classic/Level", "levelInt", "I"); Label l5 = new Label(); mv.visitJumpInsn(Opcodes.IF_ICMPLE, l5); Label l6 = new Label(); mv.visitLabel(l6); mv.visitLineNumber(396, l6); mv.visitInsn(Opcodes.RETURN); mv.visitLabel(l3); mv.visitLineNumber(398, l3); mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"ch/qos/logback/core/spi/FilterReply"}, 0, null); mv.visitVarInsn(Opcodes.ALOAD, 7); mv.visitFieldInsn(Opcodes.GETSTATIC, "ch/qos/logback/core/spi/FilterReply", "DENY", "Lch/qos/logback/core/spi/FilterReply;"); mv.visitJumpInsn(Opcodes.IF_ACMPNE, l5); Label l7 = new Label(); mv.visitLabel(l7); mv.visitLineNumber(399, l7); mv.visitInsn(Opcodes.RETURN); mv.visitLabel(l5); mv.visitLineNumber(402, l5); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitVarInsn(Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 3); mv.visitVarInsn(Opcodes.ALOAD, 4); mv.visitInsn(Opcodes.ICONST_1); mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); mv.visitInsn(Opcodes.DUP); mv.visitInsn(Opcodes.ICONST_0); mv.visitVarInsn(Opcodes.ALOAD, 5); mv.visitInsn(Opcodes.AASTORE); mv.visitVarInsn(Opcodes.ALOAD, 6); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "ch/qos/logback/classic/Logger", "buildLoggingEventAndAppend", "(Ljava/lang/String;Lorg/slf4j/Marker;Lch/qos/logback/classic/Level;Ljava/lang/String;[Ljava/lang/Object;Ljava/lang/Throwable;)V", false); Label l8 = new Label(); mv.visitLabel(l8); mv.visitLineNumber(403, l8); mv.visitInsn(Opcodes.RETURN); Label l9 = new Label(); mv.visitLabel(l9); mv.visitLocalVariable("this", "Lch/qos/logback/classic/Logger;", null, l0, l9, 0); mv.visitLocalVariable("localFQCN", "Ljava/lang/String;", null, l0, l9, 1); mv.visitLocalVariable("marker", "Lorg/slf4j/Marker;", null, l0, l9, 2); mv.visitLocalVariable("level", "Lch/qos/logback/classic/Level;", null, l0, l9, 3); mv.visitLocalVariable("msg", "Ljava/lang/String;", null, l0, l9, 4); mv.visitLocalVariable("param", "Ljava/lang/Object;", null, l0, l9, 5); mv.visitLocalVariable("t", "Ljava/lang/Throwable;", null, l0, l9, 6); mv.visitLocalVariable("decision", "Lch/qos/logback/core/spi/FilterReply;", null, l2, l9, 7); mv.visitMaxs(9, 8); mv.visitEnd(); } 七、最后再编写加载attach Agent的加载类 import com.sun.tools.attach.VirtualMachine;import java.io.IOException;import java.io.UnsupportedEncodingException;/** * @author dengbp * @ClassName MyAttachMain * @Description jar 执行命令: * @date 3/25/22 4:12 PM */public class MyAttachMain { private static final int ARGS_SIZE = 2; public static void main(String[] args) { if (args == null || args.length != ARGS_SIZE) { System.out.println("请输入进程id和日志级别(ALL、TRACE、DEBUG、INFO、WARN、ERROR、OFF),如:31722 info"); return; } VirtualMachine vm = null; try { System.out.println("修改的进程id:" + args[0]); vm = VirtualMachine.attach(args[0]); System.out.println("调整日志级别为:" + args[1]); vm.loadAgent(getJar(), args[1]); } catch (Exception e) { e.printStackTrace(); } finally { if (vm != null) { try { vm.detach(); } catch (IOException e) { e.printStackTrace(); } } } } private static String getJar() throws UnsupportedEncodingException { String jarFilePath = MyAttachMain.class.getProtectionDomain().getCodeSource().getLocation().getFile(); jarFilePath = java.net.URLDecoder.decode(jarFilePath, "UTF-8"); int beginIndex = 0; int endIndex = jarFilePath.length(); if (jarFilePath.contains(".jar")) { endIndex = jarFilePath.indexOf(".jar") + 4; } if (jarFilePath.startsWith("file:")) { beginIndex = jarFilePath.indexOf("file:") + 5; } jarFilePath = jarFilePath.substring(beginIndex, endIndex); System.out.println("jar path:" + jarFilePath); return jarFilePath; }} 八、打包执行 寻找目标程序Execute jar
Java-Xbootclasspath/a:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar-cp change-log-agent-1.0.1.jar MyAttachMain 52433 DEBUGjava-Xbootclasspath/a:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar-cp change-log-agent-1.0.1.jar MyAttachMain 52433 ERRORjava-Xbootclasspath/a:/Library/Java/JavaVirtualMachines/jdk1.8. 0_191.jdk/Contents/Home/lib/tools.jar-cp change-log-agent-1.0.1.jar MyAttachMain 52433 INFO
Effect.
PS: if there is a verification failure (caused by: java.lang.verifyerror), please configure the jvm parameter:-noverify
Thank you for your reading, the above is the content of "Java ASM uses logback log level dynamic switching method". After the study of this article, I believe you have a deeper understanding of the problem of Java ASM using logback log level dynamic switching method, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.