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
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
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.
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">
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.
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
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>
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; } } } }
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(); } }
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); } }
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("重写日志级别成功...."); }
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(); }
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; } }
Eight et exécutez
Trouvez le programme cible
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
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
Effet
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!