Java ASM uses logback log level dynamic switching method
Background
Everything has a cause and effect, and everything is event-driven. The log level switching of this solution comes from this background:
There are hundreds and nearly a thousand microservices in a single production environment
Log level switching does not require restarting the service, requiring immediate effects
It is up to business developers to modify the code or add related dependency configurations, etc., which involves a wide range of issues and slows progress
Later dynamic real-time filtering of junk logs to reduce IO and disk space costs
logback introduction
Before launching a war with the enemy, only the first Only by understanding the enemy's situation can we achieve victory in every battle. If you want to dynamically switch the log level of logback, you must first have at least a preliminary understanding of logback and see if it provides a ready-made implementation solution. Below is a brief introduction to logback related to this requirement.
Logback is an open source component for java logs. It was written by the founder of log4j. It is currently divided into 3 modules
-
logback-core: core code module
logback-classic: An improved version of log4j, which also implements the interface of
slf4j
logback-access: access module and Servlet container integration provides the function of accessing logs through Http
ContextInitializer class is the logical implementation of the logback automatic configuration process
The log level is maintained by Logger and use. Its member variable Level is maintained by Logger
Logger has three filter log output methods with different parameters: filterAndLog_0_Or3Plus, filterAndLog_1, filterAndLog_2
setLevel in Logger is to maintain the log level
Solution
Before working hard, first understand the market plan. This is the way designers and even product bosses seek optimal solutions.
Option 1: Automatically scan and update logback
This solution is a ready-made implementation of logback. As long as the configuration is turned on, the so-called dynamic switching of log levels can be achieved. Configuration method: In the logback configuration file, just add a scheduled scanner, such as:
<configuration scan="true" scanPeriod="30 seconds" debug="false">
This solution does not require R&D costs, and the operation and maintenance personnel can configure and use it themselves.
The disadvantage is:
Every time the scan interval is adjusted, the service must be restarted
More than 90% of scans It's all in vain, because the log level in production cannot be frequently switched, and it is not allowed to do so.
The effect is not real-time. If you set it to scan every minute or every few minutes, Then making the log level adjustment take effect will not take effect immediately, but this can be ignored
This solution cannot meet our junk log discarding needs, such as discarding logs based on certain keywords output. For this historical reason, a lot of junk logs are printed. Considering the time cost, it is impossible for business research and development to optimize it.
Option 2: ASM dynamically modifies the bytecode
Of course, there are other solutions, such as defining the interface API yourself. To directly call the setLevel method in Logger to adjust the level; springboot integration.
None of these plans can avoid the involvement of business development roles.
Through asm dynamic modification instructions, this solution can not only adjust the log level to take effect immediately. It can also meet the needs of filtering logs
The specific implementation is as follows. I will not introduce asm here. Students who don’t know it need to familiarize themselves with the instructions of asm, java agent and jvm first:
1. Idea creates a maven project
<dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>7.1</version> </dependency> <dependency> <artifactId>asm-commons</artifactId> <groupId>org.ow2.asm</groupId> <version>7.1</version> </dependency> <dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar</systemPath> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifestEntries> <!-- 主程序启动类 --> <Agent-Class> agent.LogbackAgentMain </Agent-Class> <!-- 允许重新定义类 --> <Can-Redefine-Classes>true</Can-Redefine-Classes> <!-- 允许转换并重新加载类 --> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <compilerArguments> <verbose /> <!-- 将jdk的依赖jar打入项目中--> <bootclasspath>${java.home}/lib/rt.jar</bootclasspath> </compilerArguments> </configuration> </plugin> </plugins> </build>
package agent; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; /** * @author dengbp * @ClassName LogbackAgentMain * @Description attach 启动器 * @date 3/25/22 6:27 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(); } }
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); } }
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("重写日志级别成功...."); }
/** * 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(); }
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; } }
- Find the target program
- ##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 DEBUG
Copy after loginjava -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 ERROR
Copy after loginjava -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
Copy after login- Effect
PS: If verification fails (caused by: java.lang.verifyerror), please provide jvm parameters :-noverify
The above is the detailed content of Java ASM uses logback log level dynamic switching method. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

In this article, we have kept the most asked Java Spring Interview Questions with their detailed answers. So that you can crack the interview.

Java 8 introduces the Stream API, providing a powerful and expressive way to process data collections. However, a common question when using Stream is: How to break or return from a forEach operation? Traditional loops allow for early interruption or return, but Stream's forEach method does not directly support this method. This article will explain the reasons and explore alternative methods for implementing premature termination in Stream processing systems. Further reading: Java Stream API improvements Understand Stream forEach The forEach method is a terminal operation that performs one operation on each element in the Stream. Its design intention is

PHP is a scripting language widely used on the server side, especially suitable for web development. 1.PHP can embed HTML, process HTTP requests and responses, and supports a variety of databases. 2.PHP is used to generate dynamic web content, process form data, access databases, etc., with strong community support and open source resources. 3. PHP is an interpreted language, and the execution process includes lexical analysis, grammatical analysis, compilation and execution. 4.PHP can be combined with MySQL for advanced applications such as user registration systems. 5. When debugging PHP, you can use functions such as error_reporting() and var_dump(). 6. Optimize PHP code to use caching mechanisms, optimize database queries and use built-in functions. 7

Guide to TimeStamp to Date in Java. Here we also discuss the introduction and how to convert timestamp to date in java along with examples.

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHP is suitable for web development, with simple syntax and high execution efficiency. 2. Python is suitable for data science and machine learning, with concise syntax and rich libraries.

Capsules are three-dimensional geometric figures, composed of a cylinder and a hemisphere at both ends. The volume of the capsule can be calculated by adding the volume of the cylinder and the volume of the hemisphere at both ends. This tutorial will discuss how to calculate the volume of a given capsule in Java using different methods. Capsule volume formula The formula for capsule volume is as follows: Capsule volume = Cylindrical volume Volume Two hemisphere volume in, r: The radius of the hemisphere. h: The height of the cylinder (excluding the hemisphere). Example 1 enter Radius = 5 units Height = 10 units Output Volume = 1570.8 cubic units explain Calculate volume using formula: Volume = π × r2 × h (4

PHP is suitable for web development, especially in rapid development and processing dynamic content, but is not good at data science and enterprise-level applications. Compared with Python, PHP has more advantages in web development, but is not as good as Python in the field of data science; compared with Java, PHP performs worse in enterprise-level applications, but is more flexible in web development; compared with JavaScript, PHP is more concise in back-end development, but is not as good as JavaScript in front-end development.

Java is a popular programming language that can be learned by both beginners and experienced developers. This tutorial starts with basic concepts and progresses through advanced topics. After installing the Java Development Kit, you can practice programming by creating a simple "Hello, World!" program. After you understand the code, use the command prompt to compile and run the program, and "Hello, World!" will be output on the console. Learning Java starts your programming journey, and as your mastery deepens, you can create more complex applications.
