Heim > Java > javaLernprogramm > Hauptteil

Java ASM verwendet die dynamische Umschaltmethode auf Logback-Protokollebene

WBOY
Freigeben: 2023-05-09 13:25:07
nach vorne
1712 Leute haben es durchsucht

    Hintergrund

    Alles hat Ursache und Wirkung und alles ist ereignisgesteuert. Die Protokollebenenumschaltung dieser Lösung wird vor diesem Hintergrund generiert:

    • Es gibt Hunderte und fast tausend Microservices in einer einzigen Produktionsumgebung

      #🎜 🎜#
    • Das Wechseln der Protokollebene erfordert keinen Neustart des Dienstes und erfordert sofortige Auswirkungen.

    • Es liegt an den Geschäftsentwicklern, den Code zu ändern oder entsprechende hinzuzufügen Abhängigkeitskonfigurationen usw. Es betrifft eine Vielzahl von Bereichen und der Fortschritt ist langsam

    • Spätere dynamische Echtzeitfilterung von Junk-Protokollen, um E/A- und Speicherplatzkosten zu reduzieren# 🎜🎜#

      #🎜 🎜#logbackIntroduction
    Bevor Sie einen Krieg mit dem Feind beginnen, können Sie in jeder Schlacht nur dann den Sieg erringen, wenn Sie zunächst die Situation des Feindes verstehen. Wenn Sie die Protokollebene von Logback dynamisch ändern möchten, müssen Sie zunächst zumindest ein vorläufiges Verständnis von Logback haben und prüfen, ob es eine vorgefertigte Implementierungslösung bietet. Nachfolgend finden Sie eine kurze Einführung in die Protokollierung im Zusammenhang mit dieser Anforderung.

    logback ist eine Open-Source-Protokollkomponente für Java, geschrieben vom Gründer von log4j. Sie ist derzeit in 3 Module unterteilt

    logback- core: Kerncodemodul
    • logback-classic: eine verbesserte Version von log4j und implementiert auch die
    • Schnittstelle
    • #🎜 🎜# slf4jlogback-access: Das Zugriffsmodul ist in den Servlet-Container integriert, um die Funktion des Zugriffs auf Protokolle über Http bereitzustellen

    • ContextInitializer-Klasse ist die logische Implementierung des automatischen Logback-Konfigurationsprozesses#🎜 🎜#

    • Protokollebenen werden vom Logger verwaltet und verwendet. Die Mitgliedsvariable Level wird von Logger verwaltet.

      setLevel in Logger dient dazu, die Protokollebene aufrechtzuerhalten.
    • Lösung#🎜 🎜#
    • Bevor Sie hart arbeiten, sollten Sie zunächst die Lösungen auf dem Markt verstehen. Auf diese Weise suchen Designer und sogar Produktchefs nach optimalen Lösungen.

      Option 1: Logback scannt und aktualisiert automatisch
    • Diese Lösung ist eine vorgefertigte Implementierung von Logback, das sogenannte dynamische Switching von Protokollebenen erreicht werden. Konfigurationsmethode: Fügen Sie in der Logback-Konfigurationsdatei einfach einen geplanten Scanner hinzu, z. B.:

      <configuration scan="true" scanPeriod="30 seconds" debug="false">
      Nach dem Login kopieren

      Für diese Lösung sind keine Forschungs- und Entwicklungskosten erforderlich, und das Betriebs- und Wartungspersonal kann sie selbst konfigurieren und verwenden.
    • Der Nachteil ist:

    Der Dienst muss jedes Mal neu gestartet werden, wenn das Scanintervall angepasst wird

    Java ASM verwendet die dynamische Umschaltmethode auf Logback-Protokollebene#🎜 🎜 #

    Mehr als 90 % der Scans sind vergeblich, da die Protokollebene in der Produktion nicht häufig umgeschaltet werden kann und dies auch nicht in Echtzeit möglich ist, wenn der Scan auf jede Minute oder Minute eingestellt ist Nach einigen Minuten wird die Anpassung der Protokollebene nicht sofort wirksam, dies kann jedoch ignoriert werden Verwerfen der Protokollausgabe basierend auf bestimmten Schlüsselwörtern. Aus diesem historischen Grund werden viele Junk-Protokolle gedruckt. Angesichts des Zeitaufwands ist es für die Unternehmensforschung und -entwicklung unmöglich, diese zu optimieren.

    Option 2: ASM ändert den Bytecode dynamisch

    Natürlich gibt es auch andere Optionen, wie zum Beispiel die Definition einer eigenen Schnittstellen-API. So rufen Sie die setLevel-Methode in Logger direkt auf, um die Springboot-Integration anzupassen.

    Diese Pläne können die Teilnahme von Personen, die sich auf Geschäftsentwicklungsfunktionen spezialisiert haben, nicht vermeiden.

    Durch ASM-Anweisungen zur dynamischen Änderung kann diese Lösung nicht nur die Protokollebene so anpassen, dass sie sofort wirksam wird. Es kann auch die Anforderungen zum Filtern von Protokollen erfüllen

    Die spezifische Implementierung ist wie folgt. Ich werde ASM hier nicht vorstellen, um sich mit den Befehlen von ASM, dem Java-Agenten, vertraut zu machen und jvm zuerst:
    • 1. Idee zum Erstellen eines Maven-Projekts

    • 2. Maven führt Abhängigkeiten ein

      <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>
      Nach dem Login kopieren
      # 🎜🎜# 3. Schreiben Sie die Attrach-Startup-Klasse
    • 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;
                  }
              }
          }
      }
      Nach dem Login kopieren
    • 4. Implementieren Sie den Bytecode-Konvertierungsprozessor

      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();
          }
      }
      Nach dem Login kopieren

      5. Implementieren Sie den Besucher des Logger-Elements
    • 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);
          }
      }
      Nach dem Login kopieren
      #🎜 🎜# 6. Implementieren Sie abschließend den Schlüssel für den Besucher der Logger-Methode.
    • Dieser Besucher (Klasse) implementiert die Umschaltung der Protokollebene und erfordert eine Änderung der Anweisungen für die drei Protokollfiltermethoden von Logger. Das Prinzip besteht darin, den Wert des in der Befehlszeile eingegebenen Protokollebenenparameters mit dem Wert seiner Mitgliedsvariablen „effectiveLevelInt“ zu überschreiben. Aufgrund der großen Länge wird nur der Kernteil des Codes veröffentlicht:

      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("重写日志级别成功....");
          }
      Nach dem Login kopieren
    • Unter diesen entspricht die Änderung der Methodenanweisungen filterAndLog_1, filterAndLog_2 und filterAndLog_0_Or3Plus. Nachfolgend wird nur die Implementierung von „modifyLogLevel_1“ veröffentlicht ##🎜🎜 #
    Zielprogramm finden

    JAR ausführen

    /**
         * 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();
        }
    Nach dem Login kopieren
    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;
        }
    }
    Nach dem Login kopieren
    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
    Nach dem Login kopieren

    Java ASM verwendet die dynamische Umschaltmethode auf Logback-Protokollebene

    Effekt

    Java ASM verwendet die dynamische Umschaltmethode auf Logback-Protokollebene

    Java ASM verwendet die dynamische Umschaltmethode auf Logback-Protokollebene

    PS: Wenn die Verifizierung fehlschlägt (verursacht durch: java.lang.verifyerror), fügen Sie bitte den JVM-Parameter hinzu: -noverify

    Das obige ist der detaillierte Inhalt vonJava ASM verwendet die dynamische Umschaltmethode auf Logback-Protokollebene. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Verwandte Etiketten:
    Quelle:yisu.com
    Erklärung dieser Website
    Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
    Beliebte Tutorials
    Mehr>
    Neueste Downloads
    Mehr>
    Web-Effekte
    Quellcode der Website
    Website-Materialien
    Frontend-Vorlage