Maison > Java > javaDidacticiel > le corps du texte

Java ASM utilise la méthode de commutation dynamique au niveau du journal de connexion

WBOY
Libérer: 2023-05-09 13:25:07
avant
1712 Les gens l'ont consulté

    Contexte

    Tout a une cause et un effet, et tout est piloté par des événements. La commutation de niveau de journalisation de cette solution est générée dans ce contexte :

    • Sur un seul environnement de production, il existe des centaines et près d'un millier de microservices

    • La commutation de niveau de journalisation ne nécessite pas de redémarrage du service et nécessite des effets immédiats

    • Il appartient aux développeurs métier de modifier le code ou d'ajouter des configurations de dépendances associées, etc., ce qui implique un large éventail de problèmes et les progrès sont lents

    • Le filtrage dynamique en temps réel ultérieur des journaux indésirables réduit les E/S et coûts d'espace disque

    Introduction à la connexion

    Initiation contre l'ennemi Avant une guerre, ce n'est qu'en comprenant d'abord la situation de l'ennemi que nous pouvons remporter la victoire dans chaque bataille. Si vous souhaitez changer dynamiquement le niveau de journalisation de la connexion, vous devez d'abord avoir au moins une compréhension préliminaire de la connexion et voir si elle fournit une solution d'implémentation prête à l'emploi. Vous trouverez ci-dessous une brève introduction à la reconnexion liée à cette exigence.

    Logback est un composant de journal open source pour Java, écrit par le fondateur de log4j. Il est actuellement divisé en 3 modules

    • logback-core : module de code de base

    • logback-classic : une version améliorée de log4j. , et en même temps Implémentation de l'slf4j interface

    • logback-access : Le module d'accès est intégré au conteneur Servlet pour fournir la fonction d'accès aux journaux via Http

    • La classe ContextInitializer est l'implémentation logique de la reconnexion automatique processus de configuration

    • Le niveau de journalisation est maintenu par le Logger et son utilisation. Sa variable membre Level est maintenue par Logger

    • Logger a filterAndLog_0_Or3Plus, filterAndLog_1, filterAndLog_2 trois méthodes de sortie de journal de filtre de paramètres différentes

    • setLevel dans Logger consiste à maintenir le niveau de journalisation

    Java ASM utilise la méthode de commutation dynamique au niveau du journal de connexion

    Solutions

    Avant en travaillant dur, comprenez d'abord les solutions disponibles sur le marché. C'est ainsi que les concepteurs et même les chefs de produits recherchent des solutions optimales.

    Option 1 : La connexion analyse et met à jour automatiquement

    Cette solution est une implémentation prête à l'emploi fournie avec la connexion. Tant que la configuration est activée, la commutation dite dynamique des niveaux de journalisation peut être réalisée. Méthode de configuration : Dans le fichier de configuration de la connexion, ajoutez simplement un scanner programmé, tel que :

    <configuration scan="true" scanPeriod="30 seconds" debug="false">
    Copier après la connexion

    Cette solution ne nécessite pas de coûts de R&D, et le personnel d'exploitation et de maintenance peut la configurer et l'utiliser eux-mêmes.

    Ses inconvénients sont :

    • Chaque fois que vous ajustez l'intervalle d'analyse, vous devez redémarrer le service

    • Plus de 90 % des analyses sont inutiles, car le niveau de journalisation en production ne peut pas être fréquemment modifié, ni cela a permis de faire cela

    • ne prend pas effet en temps réel. Si vous définissez l'analyse une fois toutes les minutes ou quelques minutes, l'ajustement du niveau de journalisation ne prendra pas effet immédiatement, mais cela peut être ignoré

    • .

      Cette solution ne peut pas satisfaire nos exigences en matière de suppression des journaux, telles que la suppression des sorties de journaux en fonction de certains mots-clés. Pour cette raison historique, de nombreux journaux indésirables sont imprimés. Compte tenu du coût en temps, il est impossible pour la recherche et le développement des entreprises de l'optimiser.

    Option 2 : ASM modifie dynamiquement le bytecode

    Bien sûr, il existe d'autres options, comme définir votre propre API d'interface. Pour appeler directement la méthode setLevel dans Logger pour ajuster le niveau d'intégration de Springboot ;

    Ces plans ne peuvent éviter la participation de ceux qui se spécialisent dans les rôles de développement des affaires.

    En modifiant dynamiquement les instructions via asm, cette solution peut non seulement ajuster le niveau de journalisation, mais prendre effet immédiatement. Il peut également répondre aux besoins de filtrage des logs

    L'implémentation spécifique est la suivante. Je ne présenterai pas asm ici. Les étudiants qui ne le connaissent pas doivent d'abord se familiariser avec les instructions d'asm, de l'agent java et de jvm :

    . 1. Créer un projet maven avec idea

    Java ASM utilise la méthode de commutation dynamique au niveau du journal de connexion

    2. Maven introduit des dépendances

    <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>
    Copier après la connexion

    3. Écrire la classe de démarrage 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;
                }
            }
        }
    }
    Copier après la connexion

    4. Implémenter le processeur de conversion de bytecode

    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();
        }
    }
    Copier après la connexion

    5. Implémenter le visiteur de l'élément 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);
        }
    }
    Copier après la connexion

    6. Enfin implémenter la méthode clé de Logger Le visiteur

    Ce visiteur (classe), pour implémenter le changement de niveau de journal, nécessite une modification des instructions pour les trois méthodes de filtrage des journaux du Logger. Le principe est d'écraser la valeur du paramètre de niveau de journalisation saisie dans la ligne de commande par la valeur de sa variable membre effectiveLevelInt. En raison de la grande longueur, seule la partie principale du code est publiée ci-dessous :

    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("重写日志级别成功....");
        }
    Copier après la connexion

    Parmi. les modifierLogLevel_1();modifierLogLevel_2();modifyLogLevel_3() ;Correspondant respectivement à la modification des instructions de la méthode filterAndLog_1, filterAndLog_2, filterAndLog_0_Or3Plus. Seule l'implémentation de modifierLogLevel_1 est publiée ci-dessous

    /**
         * 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();
        }
    Copier après la connexion

    Seven Enfin, écrivez la classe de chargement pour charger le package 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;
        }
    }
    Copier après la connexion

    Eight et exécutez

    • Trouvez le programme cible

    Java ASM utilise la méthode de commutation dynamique au niveau du journal de connexion

    • Exécuter. 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
    Copier après la connexion
    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
    Copier après la connexion
    rrree
    • Effet

    Java ASM utilise la méthode de commutation dynamique au niveau du journal de connexion

    Java ASM utilise la méthode de commutation dynamique au niveau du journal de connexion

    PS : Si la vérification échoue (causée par : java.lang.verifyerror), veuillez ajouter le paramètre jvm : -noverify

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Étiquettes associées:
    source:yisu.com
    Déclaration de ce site Web
    Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
    Tutoriels populaires
    Plus>
    Derniers téléchargements
    Plus>
    effets Web
    Code source du site Web
    Matériel du site Web
    Modèle frontal