ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

一个优秀的日志应该是什么样的

一个优秀的日志应该是什么样的 在上一篇一个合格的日志应该是什么样的我们打印的日志已经基本可用了。在线上接口行为和预期的不一致时INFO日志又不够详细看不出问题我们希望临时将日志级别修改为DEBUG要怎么做呢1. 修改日志级别1. 集成actuator基于spring-boot-starter的应用可以通过actuator修改日志级别。首先要添加actuator的依赖。dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency在application.properties中暴露端点management.endpoints.web.exposure.include*2. 查看日志级别1. 获取所有端点首先通过actuator获取可用的端点列表确认logger的端点存在curl-shttp://192.168.31.52:8080/actuator|jq.响应格式如下{_links:{self:{href:http://192.168.31.52:8080/actuator,templated:false},loggers:{href:http://192.168.31.52:8080/actuator/loggers,templated:false},loggers-name:{href:http://192.168.31.52:8080/actuator/loggers/{name},templated:true}}}2. 查看日志级别查看当前系统的所有Logger的级别curl-shttp://192.168.31.52:8080/actuator/loggers|jq.响应格式如下{levels:[OFF,ERROR,WARN,INFO,DEBUG,TRACE],loggers:{ROOT:{configuredLevel:INFO,effectiveLevel:INFO},com.keyniu:{effectiveLevel:INFO},com.keyniu.dis:{effectiveLevel:INFO}}}你也可以指定查看某个包的日志级别比如com.keyniu的curl-shttp://192.168.31.52:8080/actuator/loggers/com.keyniu|jq.响应格式如下{effectiveLevel:INFO}3. 设置日志级别通过POST请求修改日志级别curl-XPOST http://192.168.31.52:8080/actuator/loggers/com.keyniu-HContent-Type: application/json-d{configuredLevel: DEBUG}再次查询日志级别后可以发现级别已经更新为DEBUG4. 基于代码修改一般的公司是不允许在线上暴露actuator端点的所以我们经常会自定义修改日志级别的逻辑。代码修改的逻辑在不同的日志框架下是不同的我们以logback为例publicstaticvoidsetLogLevel(StringloggerName,Stringlevel){Loggerlogger(Logger)LoggerFactory.getLogger(loggerName);logger.setLevel(Level.toLevel(level));// 支持 OFF/ERROR/WARN/INFO/DEBUG/TRACE}Spring对此做了封装屏蔽了不同日志框架的差异我们可以通过LoggingSystem来完成日志级别修改。publicstaticvoidsetLogLevel(StringloggerName,LogLevellevel){LoggingSystemloggingSystemLoggingSystem.get(ClassLoader.getSystemClassLoader());loggingSystem.setLogLevel(loggerName,level);}2. 指定用户修改通过修改日志级别确实解决了问题不过日志级别的修改是全局的所有用户都受到影响而且也不可能在线上长期开启。所以往往我们会想要给特定的用户打印DEBUG日志比如内部人员、反馈问题的用户。通过logback本身提供的MDC我们能将指定请求标为要求打印DEBUG日志这段逻辑可以放在HandlerInterceptor中实现。1. 设置MDC标志publicclassDebugHandlerInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{Stringuidrequest.getHeader(user-id);booleanxdebugdebugUid.contains(uid)||1.equals(request.getParameter(xdebug));MDC.put(xdebug,xdebug);returntrue;}OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{MDC.clear();}}2. 实现DEBUG日志再定义一个工具类用来判断是否要打印DEBUG日志publicclassDebugHelper{publicstaticbooleanisDebugEnabled(Loggerlogger){return1.equals(MDC.get(debug))||logger.isDebugEnabled();}}打印日志的时候需要自行判断DebugHelper.isDebugEnabledif(DebugHelper.isDebugEnabled(log)){log.info(debug info goes here);}这个方案简单直观不过对用户来说不太友好必须自己调用DebugHelper.isDebugEnabled的判断需要通过log.info来打印DEBUG日志3. slf4j的内部实现我们使用slf4j的时候时候都是从这一行代码开始的基于lombok的Slf4j只是帮我们生成了这一行代码privateLoggerlogLoggerFactory.getLogger(HelloWorldController.class);LoggerFactory的实现流程如下:当我们调用LoggerFactory时LoggerFactory会用两种方式寻找ILoggerFacotry的实现通过环境变量slf4j.provider获取配置通过ServiceLoader读取provider调用ILoggerFacotry.getLogger获取对应的日志实现返回一个实现了 org.slf4j.Logger 实现在原理清晰的前提下扩展的方案基本也明确了就是提供ILoggerFacotry的实现返回我们自己的org.slf4j.Logger的实现这个类的debug方法要包含我们的MDC里的xdebug的判断。我尝试了两种方案自定义SLF4JServiceProviderKeyniuSLF4jServiceProvider 配置-Dslf4j.providercom.keyniu.dis.logger.KeyniuSLF4jServiceProvider修改logback的实现修改Logger的isDebugEnabled和debug方法4. 扩展SLF4jServiceProvider1. 实现KeyniuSLF4jServiceProvider代码和LogbackServiceProvider一致只是修改ILoggerFacotry的实现publicclassKeyniuSLF4jServiceProviderimplementsSLF4JServiceProvider{staticfinalStringNULL_CS_URLhttp://logback.qos.ch/codes.html#null_CS;publicstaticStringREQUESTED_API_VERSION2.0.99;privateLoggerContextdefaultLoggerContext;privateIMarkerFactorymarkerFactory;privateLogbackMDCAdaptermdcAdapter;...publicILoggerFactorygetLoggerFactory(){returnnewKeyniuLoggerFactory(this.defaultLoggerContext);}...}2. 实现ILoggerFactory实现ILoggerFactory返回自己的Logger对象publicclassKeyniuLoggerFactoryimplementsILoggerFactory{privateLoggerContextloggerContext;publicKeyniuLoggerFactory(LoggerContextloggerContext){this.loggerContextloggerContext;}OverridepublicLoggergetLogger(Strings){returnnewKeyniuLogger(this.loggerContext.getLogger(s));}}3. 修改Logger的实现修改所有的debug和isDebugEnabled判断MDC的xdebug的值publicclassKeyniuLoggerimplementsorg.slf4j.Logger{privatech.qos.logback.classic.Loggerback;publicKeyniuLogger(ch.qos.logback.classic.Loggerback){this.backback;}OverridepublicbooleanisDebugEnabled(){returnback.isDebugEnabled()||1.equals(MDC.get(xdebug));}Overridepublicvoiddebug(Strings){if(1.equals(MDC.get(xdebug))){back.info(s);}else{back.debug(s);}}}4. 修改启动参数java-Dslf4j.providercom.keyniu.dis.logger.KeyniuSLF4jServiceProvider com.keyniu.dis.DiveInMain5. 最终失败SLF4J(I):Attemptingtoloadprovidercom.keyniu.dis.logger.KeyniuSLF4jServiceProviderspecified viaslf4j.providersystem propertyExceptionin threadmainjava.lang.IllegalArgumentException:LoggerFactoryis not aLogbackLoggerContextbutLogbackis on theclasspath.EitherremoveLogbackor the competing implementation(classcom.keyniu.dis.logger.KeyniuLoggerFactoryloaded from file:/target/classes/).Ifyou are usingWebLogicyou will needtoaddorg.slf4jtoprefer-application-packages inWEB-INF/weblogic.xml:com.keyniu.dis.logger.KeyniuLoggerFactoryatorg.springframework.util.Assert.instanceCheckFailed(Assert.java:592)atorg.springframework.util.Assert.isInstanceOf(Assert.java:511)atorg.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:401)atorg.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:128)atorg.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:238)原因是spring的LogbackLoggingSystem要求ILoggerFactory返回的LoggerFactory对象必须实现LoggerContext 而LoggerContext.getLog是一个final方法、返回的类是final类这条路走不通。5. 修改logback实现publicfinalLoggergetLogger(Class?clazz){returnthis.getLogger(clazz.getName());}修改logback的ch.qos.logback.classic.LoggerContext的getLogger实现返回KeyniuLogger虽然可用但是修改第三方包并不是好的想法。暂时采用第2步的方案。https://blog.keyniu.com/web/
返回列表