java asm使用logback日志级别动态切换的方法-mile米乐体育
java asm使用logback日志级别动态切换的方法
这篇文章主要讲解了“javaasm使用logback日志级别动态切换的方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“javaasm使用logback日志级别动态切换的方法”吧!
背景
一切皆有因果,所有事情,都有事件驱动。本方案的日志级别切换是由这样的背景下产生的:
单个生产环境上,有几百近千个微服务
日志级别切换不重启服务,要求即时生效果
由业务开发人员去修改代码或增加相关依赖配置等涉及面广,推动进度慢
后期动态实时过滤垃圾日志,减少io和磁盘空间成本
logback简介
在跟敌人发起战争之前,只有先发解敌方的情况,才能做到百战百胜。要想对logback的日志级别做动态切换,首先至少对logback做个初步的了解、和看看它有没有提供现成的实现方案。下面简单介绍一下logback跟这次需求有关的内容。
logback是java的日志开源组件,是log4j创始人写的,目前主要分为3个模块
logback-core:核心代码模块
logback-classic:log4j的一个改良版本,同时实现了
slf4j
的接口logback-access:访问模块与servlet容器集成提供通过http来访问日志的功能
contextinitializer类是logback自动配置流程的逻辑实现
日志级别由logger维护和使用。其成员变量level正是由logger维护
logger中有filterandlog_0_or3plus、filterandlog_1、filterandlog_2三个不同参数的过滤日志输出方法
logger中的setlevel就是对日志级别的维护
mile米乐体育的解决方案
在满头苦干之前,先了解市面上的方案。是设计师们乃至产品大佬们寻求最优mile米乐体育的解决方案的思路。
方案一:logback自动扫描更新
这个方案是logback自带现成的实现,只要开启配置就可以实现所谓的日志级别动态切换。配置方法:在logback的配置文件中,增加定时扫描器即可,如:
该方案可以不需要研发成本,运维人员自己配上并能使用。
它的缺点是:
每次调整扫描间隔时间都要重启服务
90%以上的扫描都是无用功,因为生产上的日志级别不可能经常有切换需求,也不允许这么做
生效不实时,如果设定在一分钟或几分钟扫描一次,那么让日志级别调整后生效就不是即时生效的,不过这个可以忽略
该方案满足不了我们的垃圾日志丢弃的需求,比如根据某些关键字丢弃日志的输出。针对这种历史原因打印很多垃圾日志的情况,考虑到时间成本,不可能让业务研发去优化。
方案二:asm动态修改字节码
当然,还有其它方案,如:自己定义接口api。来直接调用logger中的setlevel方法,达到调整级别的目的;springboot的集成。
这些方案都不避免不了专主于业务开发角色的参与。
通过asm动态修改指令,该方案除了能满足调整日志级别即时生效之外。还可以满足过滤日志的需求
具体实现如下,在这里就不对asm做介绍了,不了解的同学,需要先去熟悉asm、java agent和jvm的指令:
一、idea创建maven工程
二、maven引入依赖
三、编写attrach启动类
packageagent;importjava.lang.instrument.instrumentation;importjava.lang.instrument.unmodifiableclassexception;/***@authordengbp*@classnamelogbackagentmain*@descriptionattach启动器*@date3/25/226:27pm*/publicclasslogbackagentmain{privatestaticstringfilter_class="ch.qos.logback.classic.logger";publicstaticvoidagentmain(stringagentargs,instrumentationinst)throwsunmodifiableclassexception{system.out.println("agentargs:" agentargs);inst.addtransformer(newlogbackfiletransformer(agentargs),true);class[]classes=inst.getallloadedclasses();for(inti=0;i
四、实现字节码转换处理器
packageagent;importjdk.internal.org.objectweb.asm.classreader;importjdk.internal.org.objectweb.asm.classvisitor;importjdk.internal.org.objectweb.asm.classwriter;importjava.lang.instrument.classfiletransformer;importjava.security.protectiondomain;/***@authordengbp*@classnamelogbackfiletransformer*@description字节码文件转换器*@date3/25/226:25pm*/publicclasslogbackfiletransformerimplementsclassfiletransformer{privatefinalstringlevel;privatestaticstringclass_name="ch/qos/logback/classic/logger";publiclogbackfiletransformer(stringlevel){this.level=level;}@overridepublicbyte[]transform(classloaderloader,stringclassname,classclassbeingredefined,protectiondomainprotectiondomain,byte[]classfilebuffer){if(!class_name.equals(classname)){returnclassfilebuffer;}classreadercr=newclassreader(classfilebuffer);classwritercw=newclasswriter(cr,classwriter.compute_frames);classvisitorcv1=newlogbackclassvisitor(cw,level);/*classvisitorcv2=newlogbackclassvisitor(cv1);*///asm框架使用到访问模式和责任链模式//classreader只需要accept责任链中的头节点处的classvisitor即可cr.accept(cv1,classreader.skip_frames|classreader.skip_debug);system.out.println("end...");returncw.tobytearray();}}
五、实现logger元素的访问者
packageagent;importjdk.internal.org.objectweb.asm.classvisitor;importjdk.internal.org.objectweb.asm.methodvisitor;importorg.objectweb.asm.opcodes;/***@authordengbp*@classnamelogbackclassvisitor*@descriptionlogger类元素访问者*@date3/25/225:01pm*/publicclasslogbackclassvisitorextendsclassvisitor{privatefinalstringlevel;/***asm版本*/privatestaticfinalintasm_version=opcodes.asm4;publiclogbackclassvisitor(classvisitorclassvisitor,stringlevel){super(asm_version,classvisitor);this.level=level;}@overridepublicmethodvisitorvisitmethod(intaccess,stringname,stringdescriptor,stringsignature,string[]exceptions){methodvisitormv=super.visitmethod(access,name,descriptor,signature,exceptions);returnnewlogfiltermethodvisitor(api,mv,access,name,descriptor,level);}}
六、最后实现logger关键方法的访问者
该访问者(类),实现日志级别的切换,需要对logger的三个日志过滤方法进行指令的修改。原理是把命令行入参的日志级别参数值覆盖其成员变量effectivelevelint的值,由于篇幅过大,只贴核心部分代码,请看下面:
packageagent;importjdk.internal.org.objectweb.asm.label;importjdk.internal.org.objectweb.asm.methodvisitor;importjdk.internal.org.objectweb.asm.commons.adviceadapter;importorg.objectweb.asm.opcodes;/***@authordengbp*@classnamelogfiltermethodvisitor*@descriptionlogger类日志过滤方法元素访问者*@date3/25/225:01pm*/publicclasslogfiltermethodvisitorextendsadviceadapter{privatestringmethodname;privatefinalstringlevel;privatestaticfinalstringfilterandlog_1="filterandlog_1";privatestaticfinalstringfilterandlog_2="filterandlog_2";privatestaticfinalstringfilterandlog_0_or3plus="filterandlog_0_or3plus";protectedlogfiltermethodvisitor(intapi,methodvisitormethodvisitor,intaccess,stringname,stringdescriptor,stringlevel){super(api,methodvisitor,access,name,descriptor);this.methodname=name;this.level=level;}/***description在访问方法的头部时被访问*@param*@returnvoid*@authordengbp*@date3:36pm4/1/22**/@overridepublicvoidvisitcode(){system.out.println("visitcodemethod");super.visitcode();}@overrideprotectedvoidonmethodenter(){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("重写日志级别成功....");}
其中modifyloglevel_1(); modifyloglevel_2();modifyloglevel_3();分别对应filterandlog_1、filterandlog_2、filterandlog_0_or3plus方法指令的修改。下面只贴modifyloglevel_1的实现
/***description修改目标方法:filterandlog_1*@param*@returnvoid*@authordengbp*@date2:20pm3/31/22**/privatevoidmodifyloglevel_1(){labell0=newlabel();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");labell1=newlabel();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);labell2=newlabel();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;");labell3=newlabel();mv.visitjumpinsn(opcodes.if_acmpne,l3);labell4=newlabel();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");labell5=newlabel();mv.visitjumpinsn(opcodes.if_icmple,l5);labell6=newlabel();mv.visitlabel(l6);mv.visitlinenumber(396,l6);mv.visitinsn(opcodes.return);mv.visitlabel(l3);mv.visitlinenumber(398,l3);mv.visitframe(opcodes.f_append,1,newobject[]{"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);labell7=newlabel();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);labell8=newlabel();mv.visitlabel(l8);mv.visitlinenumber(403,l8);mv.visitinsn(opcodes.return);labell9=newlabel();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();}
七、最后再编写加载attach agent的加载类
importcom.sun.tools.attach.virtualmachine;importjava.io.ioexception;importjava.io.unsupportedencodingexception;/***@authordengbp*@classnamemyattachmain*@descriptionjar执行命令:*@date3/25/224:12pm*/publicclassmyattachmain{privatestaticfinalintargs_size=2;publicstaticvoidmain(string[]args){if(args==null||args.length!=args_size){system.out.println("请输入进程id和日志级别(all、trace、debug、info、warn、error、off),如:31722info");return;}virtualmachinevm=null;try{system.out.println("修改的进程id:" args[0]);vm=virtualmachine.attach(args[0]);system.out.println("调整日志级别为:" args[1]);vm.loadagent(getjar(),args[1]);}catch(exceptione){e.printstacktrace();}finally{if(vm!=null){try{vm.detach();}catch(ioexceptione){e.printstacktrace();}}}}privatestaticstringgetjar()throwsunsupportedencodingexception{stringjarfilepath=myattachmain.class.getprotectiondomain().getcodesource().getlocation().getfile();jarfilepath=java.net.urldecoder.decode(jarfilepath,"utf-8");intbeginindex=0;intendindex=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("jarpath:" jarfilepath);returnjarfilepath;}}
八、打包执行
寻找目标程序
执行jar
java-xbootclasspath/a:/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/lib/tools.jar-cpchange-log-agent-1.0.1.jarmyattachmain52433debug
java-xbootclasspath/a:/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/lib/tools.jar-cpchange-log-agent-1.0.1.jarmyattachmain52433error
java-xbootclasspath/a:/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/lib/tools.jar-cpchange-log-agent-1.0.1.jarmyattachmain52433info
效果
ps:如果出现校验失败(caused by: java.lang.verifyerror),请配上jvm参数:-noverify
感谢各位的阅读,以上就是“javaasm使用logback日志级别动态切换的方法”的内容了,经过本文的学习后,相信大家对javaasm使用logback日志级别动态切换的方法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是恰卡编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!