Rumah  >  Artikel  >  Java  >  Java ASM menggunakan kaedah pensuisan dinamik tahap log balik

Java ASM menggunakan kaedah pensuisan dinamik tahap log balik

WBOY
WBOYke hadapan
2023-05-09 13:25:071651semak imbas

    Latar Belakang

    Semuanya ada sebab dan akibat, dan semuanya didorong oleh peristiwa. Penukaran tahap log penyelesaian ini datang dari latar belakang ini:

    • Pada satu persekitaran pengeluaran, terdapat ratusan dan hampir seribu perkhidmatan mikro

    • Menukar tahap log tidak memerlukan memulakan semula perkhidmatan, memerlukan kesan segera

    • Terpulang kepada pembangun perniagaan untuk mengubah suai kod atau menambah konfigurasi pergantungan yang berkaitan, dsb. Ia melibatkan pelbagai jenis isu dan kemajuannya perlahan

    • Penapisan dinamik dan masa nyata log sampah kemudian untuk mengurangkan kos ruang IO dan cakera

    Pengenalan log balik

    Sebelum melancarkan perang dengan musuh, hanya yang pertama Hanya dengan memahami situasi musuh kita boleh mencapai kemenangan dalam setiap pertempuran. Jika anda ingin menukar tahap log balik secara dinamik, anda mesti mempunyai sekurang-kurangnya pemahaman awal tentang log balik dan lihat jika ia menyediakan penyelesaian pelaksanaan siap sedia. Di bawah ialah pengenalan ringkas kepada log balik yang berkaitan dengan keperluan ini.

    Logback ialah komponen log sumber terbuka untuk Java, yang ditulis oleh pengasas log4j Ia kini dibahagikan kepada 3 modul

    • logback-core: modul kod teras <.>

    • logback-classic: versi log4j yang dipertingkatkan, yang turut melaksanakan antara muka

      slf4j

    • logback-access: modul akses dan Servlet integrasi kontena disediakan Fungsi mengakses log melalui Http

    • Kelas ContextInitializer ialah pelaksanaan logik proses konfigurasi automatik log balik

    • Tahap log dikekalkan dan digunakan oleh Logger. Tahap pembolehubah ahlinya dikekalkan oleh Logger

    • Logger mempunyai tiga kaedah keluaran log penapis dengan parameter berbeza: filterAndLog_0_Or3Plus, filterAndLog_1, filterAndLog_2

    • setLevel in Logger adalah untuk mengekalkan tahap log

    Java ASM menggunakan kaedah pensuisan dinamik tahap log balik

    Penyelesaian

    Sebelum bekerja keras, fahami pelan pasaran dahulu. Ini adalah cara pereka dan juga bos produk mencari penyelesaian yang optimum.

    Pilihan 1: Imbas dan kemas kini log balik secara automatik

    Penyelesaian ini ialah pelaksanaan log balik yang sedia dibuat Selagi konfigurasi dihidupkan, apa yang dipanggil pensuisan dinamik tahap log boleh dicapai. Kaedah konfigurasi: Dalam fail konfigurasi log balik, cuma tambahkan pengimbas berjadual, seperti:

    <configuration scan="true" scanPeriod="30 seconds" debug="false">

    Penyelesaian ini tidak memerlukan kos R&D, dan kakitangan operasi dan penyelenggaraan boleh mengkonfigurasi dan menggunakannya sendiri.

    Kelemahannya ialah:

    • Perkhidmatan perlu dimulakan semula setiap kali selang imbasan dilaraskan

    • Lebih daripada 90 % daripada imbasan Semuanya sia-sia, kerana tahap log dalam pengeluaran tidak boleh ditukar dengan kerap, dan ia tidak dibenarkan untuk melakukannya

    • Kesannya bukan masa nyata ia ditetapkan untuk mengimbas setiap minit atau setiap beberapa minit, Kemudian membuat pelarasan tahap log berkuat kuasa tidak akan berkuat kuasa serta-merta, tetapi ini boleh diabaikan

    • Penyelesaian ini tidak dapat memenuhi log sampah kami membuang keperluan, seperti membuang log berdasarkan keluaran kata kunci tertentu. Atas sebab sejarah ini, banyak log sampah dicetak Memandangkan kos masa, adalah mustahil untuk penyelidikan dan pembangunan perniagaan untuk mengoptimumkannya.

    Pilihan 2: ASM mengubah suai kod bait secara dinamik

    Sudah tentu, terdapat pilihan lain, seperti mentakrifkan api antara muka sendiri. Untuk terus memanggil kaedah setLevel dalam Logger untuk melaraskan integrasi springboot;

    Pelan ini tidak dapat mengelakkan penyertaan mereka yang pakar dalam peranan pembangunan perniagaan.

    Dengan mengubah suai arahan secara dinamik melalui asm, penyelesaian ini bukan sahaja boleh melaraskan tahap log untuk berkuat kuasa serta-merta. Ia juga boleh memenuhi keperluan log penapisan

    Pelaksanaan khusus adalah seperti berikut. Pelajar yang tidak tahu perlu membiasakan diri dengan arahan asm, ejen java dan jvm pertama:

    1. Idea mencipta projek maven

    Java ASM menggunakan kaedah pensuisan dinamik tahap log balik

    2. Maven memperkenalkan dependencies

    <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>

    3. Menulis kelas permulaan attrach

    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;
                }
            }
        }
    }

    4. Perkataan pelaksanaan Pemproses penukaran kod bahagian

    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();
        }
    }

    5. Pelawat yang melaksanakan elemen 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);
        }
    }

    6. Akhir sekali pelawat yang melaksanakan kaedah utama Logger

    Pelawat (kelas) ini, Untuk menukar tahap log, anda perlu mengubah suai arahan untuk tiga kaedah penapisan log Logger. Prinsipnya adalah untuk menimpa nilai parameter tahap log yang dimasukkan dalam baris arahan dengan nilai pembolehubah ahlinya effectiveLevelInt Disebabkan panjangnya yang besar, hanya bahagian teras kod tersebut disiarkan:

    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("重写日志级别成功....");
        }

    Antaranya modifyLogLevel_1(); modifyLogLevel_2() ;modifyLogLevel_3(); masing-masing sepadan dengan pengubahsuaian filterAndLog_1, filterAndLog_2, filterAndLog_0_Or3Plus. Hanya pelaksanaan modifyLogLevel_1 dipaparkan di bawah

    /**
         * 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();
        }

    7. Akhir sekali, tulis kelas pemuatan untuk memuatkan 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;
        }
    }

    8. Pelaksanaan pakej

    • Cari program sasaran

    Java ASM menggunakan kaedah pensuisan dinamik tahap log balik

    • 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
      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 ERROR
      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 INFO
    • Kesan

    Java ASM menggunakan kaedah pensuisan dinamik tahap log balik

    Java ASM menggunakan kaedah pensuisan dinamik tahap log balik

    PS: Jika pengesahan gagal (disebabkan oleh: java.lang.verifyerror), sila berikan parameter jvm :-noverify

    Atas ialah kandungan terperinci Java ASM menggunakan kaedah pensuisan dinamik tahap log balik. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam