关于log4j2:log4j2

log4j2 中logger,appender关系log4j2中Appender/Logger/Root 关系https://blog.csdn.net/HongZeng_CSDN/article/details/130094219 《Log4j 2 官网文档》多余性(Additivity)http://ifeve.com/log4j-2-additivity/

April 22, 2023 · 1 min · jiezi

关于log4j2:日志log4j2基于AsyncLogger的异步日志打印

前言在日志-log4j2基于AsyncAppender的异步日志打印一文中,剖析了Log4j2如何基于AsyncAppender来实现异步日志打印,本篇文章将剖析Log4j2如何基于AsyncLogger来实现异步日志打印。 本篇文章会波及局部Disruptor队列的相干概念,如果不相熟Disruptor队列,能够先浏览多线程学习-Disruptor队列理解相干概念。 Log4j2版本:2.17.1 注释首先搭建示例工程。引入依赖如下所示。 <dependencies> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.0</version> </dependency></dependencies>打印日志的测试类如下所示。 public class LearnLog4j2Async { private static final Logger logger = LoggerFactory .getLogger(LearnLog4j2Async.class); public static void main(String[] args) { logger.info("{} be happy every day.", "Lee"); }}要应用AysncLogger,须要在Log4j2的配置文件中应用<AsyncLogger>标签配置一个异步Logger,并为这个异步Logger配置非异步Appender。配置如下所示。 <?xml version="1.0" encoding="UTF-8"?><Configuration status="INFO"> <Appenders> <!-- 配置两个非异步Appender --> <Console name="MyConsole" target="SYSTEM_OUT"> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="%msg%n"/> </Console> <RollingFile name="MyFile" fileName="mylog.log" filePattern="mylog.log.%i"> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" /> <PatternLayout pattern="%msg%n"/> <SizeBasedTriggeringPolicy size="20M"/> </RollingFile> </Appenders> <Loggers> <!-- 为根Logger配置非异步Appender --> <Root level="INFO"> <Appender-ref ref="MyConsole"/> <Appender-ref ref="MyFile"/> </Root> <!-- 定义一个异步Logger并为其配置非异步Appender --> <AsyncLogger name="com.lee.learn.log4j2.asynclogger.LearnLog4j2Async" level="INFO" additivity="false"> <appender-ref ref="MyConsole"/> </AsyncLogger> </Loggers></Configuration>已知Log4j2框架在首次获取Logger时,会初始化LoggerContext,而初始化LoggerContext时有一个步骤就是将Log4j2配置对象XmlConfiguration设置给LoggerContext并启动XmlConfiguration,这里看一下XmlConfiguration的start()办法,如下所示。 ...

December 14, 2022 · 3 min · jiezi

关于log4j2:日志log4j2基于AsyncAppender的异步日志打印

前言在日志-log4j2日志框架源码学习一文中,对Log4j2的整体构造和同步打印流程进行了一个初步学习,本篇文章将对Log4j2异步打印流程进行学习。在同步日志打印中,利用业务逻辑与日志打印在一个线程中,利用后续业务逻辑须要期待日志打印实现能力继续执行,而异步日志打印中,利用业务逻辑与日志打印在不同线程中,Log4j2有专门的线程来实现日志打印而不会阻塞利用后续业务逻辑的执行。Log4j2提供了两种形式来配置异步日志打印,别离是AsyncAppender和AsyncLogger,本篇文章先对AsyncAppender进行学习。 Log4j2版本:2.17.1 注释首先须要搭建示例工程,在pom文件中引入Log4j2的依赖。 <dependencies> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.17.1</version> </dependency></dependencies>再创立一个打印日志的测试类,如下所示。 public class LearnLog4j2Async { private static final Logger logger = LoggerFactory .getLogger(LearnLog4j2Async.class); public static void main(String[] args) { logger.info("{} be happy every day.", "Lee"); }}应用AsyncAppender的形式来配置异步日志打印,是基于<Async>标签来配置并失去异步Appender,打印日志时待打印的日志会被增加到异步Appender的阻塞队列中,而后由一个专门的后盾线程生产阻塞队列中的待打印日志,这里的阻塞队列是ArrayBlockingQueue。应用AsyncAppender的形式的配置的一个例子如下所示。 <?xml version="1.0" encoding="UTF-8"?><Configuration status="INFO"> <Appenders> <!-- 配置两个失常的Appedner --> <Console name="MyConsole" target="SYSTEM_OUT"> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="%msg%n"/> </Console> <RollingFile name="MyFile" fileName="mylog.log" filePattern="mylog.log.%i"> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" /> <PatternLayout pattern="%msg%n"/> <SizeBasedTriggeringPolicy size="20M"/> </RollingFile> <!-- 让异步Appedner援用失常Appender --> <Async name="MyAsync"> <AppenderRef ref="MyConsole"/> <AppenderRef ref="MyFile"/> </Async> </Appenders> <Loggers> <!-- 让根日志打印器援用异步Appender --> <Root level="INFO"> <Appender-ref ref="MyAsync"/> </Root> </Loggers></Configuration>通过上述的配置,在测试程序中的logger会继承应用根日志打印器的LoggerConfig,也就持有了name为MyAsync的异步Appender,那么在打印日志时,最终会调用到异步Appender的append()办法,这里理论会调用到AsyncAppender的append()办法,如下所示。 ...

November 20, 2022 · 4 min · jiezi

关于log4j2:SpringBoot集成Log4j2日志框架

11

April 22, 2022 · 1 min · jiezi

关于log4j2:谷歌公布-Log4j-2-漏洞调查结果未受影响Google-Workspace-核心服务并未使用-Log4j-2

自 12 月 9 日 Apache Log4j 2 “惊天破绽”被曝出后,寰球泛滥科技企业已受到其影响。12 月 17 日,谷歌方面发表已对该事件进行了考察,并于 12 月 21 日将后果颁布在了 Google cloud 官网页面上。 据 Google cloud 页面上 12 月 21 日颁布的考察结果显示:面向消费者和付费用户的 Google Workspace 外围服务没有应用 Log4j2,也没有受到 CVE-2021-44228 和 CVE-2021-45046 “破绽”的影响。谷歌方面示意将为此平安征询提供工作区外围服务的具体状态。 谷歌方面示意,Google Cloud 正在积极关注开源 Apache“Log4j 2”实用程序(CVE-2021-44228 和 CVE-2021-45046)中的安全漏洞。他们曾经留神到了报告的 Apache“Log4j 1.x”破绽(CVE-2021-4104),并倡议用户更新到 Log4j2 的最新版本。 12 月 17 日,在 Google cloud 博客页面上,谷歌开源洞察小组(Open Source Insights Team)解释了他们察看到的对于 Apache Log4J 破绽的全副状况。他们形容了宽泛存在的破绽以及修复开源 JVM 生态系统的以后停顿。 随后,谷歌评估了该破绽对 Google cloud 产品和服务的潜在影响,并依据正在进行的调查结果,提供开源Apache“Log4j 2”破绽对 Google cloud 产品和服务的潜在影响做实时更新。谷歌方面示意,这将是一个继续的流动,后续也将持续通过此页面和客户沟通渠道提供更新。 ...

December 22, 2021 · 1 min · jiezi

关于log4j2:Log4j-漏洞修复检测-附检测工具

作者:SRE运维博客博客地址:https://www.cnsre.cn/ 文章地址:https://www.cnsre.cn/posts/211213210004/相干话题:https://www.cnsre.cn/tags/Log4j/近日的Log4j2,可是十分的火啊,我也是加班加点把补丁给打上了次安心。Apache Log4j2存在近程代码执行破绽,教训证,该破绽容许攻击者在指标服务器上执行任意代码,可导致服务器被黑客管制。因为Apache Log4j 2利用较为宽泛,倡议应用该组件的用户尽快采取安全措施。 影响范畴破绽影响版本: 2.0 <= Apache Log4j 2 <= log4j-2.15.0-rc1 破绽形容Apache Log4j 2是一个基于Java的日志记录工具,是对 Log4j 的降级。近日安恒信息应急响应核心监测到Apache Log4j 2存在近程代码执行破绽,攻击者可通过结构歹意申请利用该破绽实现在指标服务器上执行任意代码。 破绽修复因为Log4j2 作为日志记录根底第三方库,被大量Java框架及利用应用,只有用到 Log4j2 进行日志输入且日志内容能被攻击者局部可控,即可能会受到破绽攻打影响。因而,该破绽也同时影响寰球大量通用利用及组件,例如 :Apache Struts2、Apache Solr、Apache Druid、Apache Flink、Apache Flume、Apache Dubbo、Apache Kafka、Spring-boot-starter-log4j2、ElasticSearch、Redis、Logstash等倡议及时查看并降级所有应用了 Log4j 组件的零碎或利用。 紧急: 目前破绽POC已被公开,官网已公布平安版本,倡议应用该组件的用户尽快采取安全措施。 临时性缓解措施:1、在 jvm 参数中增加 -Dlog4j2.formatMsgNoLookups=true2、零碎环境变量中将LOG4J_FORMAT_MSG_NO_LOOKUPS 设置为 true3、创立 log4j2.component.properties 文件,文件中减少配置 log4j2.formatMsgNoLookups=true4、若相干用户临时无奈进行降级操作,也可通过禁止Log4j中SocketServer类所启用的socket端对公网凋谢来进行防护5、禁止装置log4j的服务器拜访外网,并在边界对dnslog相干域名拜访进行检测。局部公共dnslog平台如下 ceye.iodnslog.linkdnslog.cndnslog.iotu4.orgawvsscan119.autoverify.cnburpcollaborator.nets0x.cn彻底修复破绽:建议您在降级前做好数据备份工作,防止出现意外研发代码修复:降级到官网提供的 log4j-2.15.0-rc2 版本https://github.com/apache/log... 破绽检测工具检测工具下载地址 https://pan.cnsre.cn/d/Package/Linux/360log4j2.zip 破绽检测浏览器被动式扫描检测计划原理工程师可设置该代理通过浏览器被动扫描指标,查看 DNS Log 检测是否存在 log4j 破绽。应用办法1.浏览器或操作系统配置 HTTP/HTTPS 代理:219.141.219.69:18080 2.浏览器或操作系统将下列证书增加到信赖名单:附件sqli-hunter.pem 3.应用浏览器失常进行指标浏览,当完结扫描后,在http://219.141.219.69:18000/ 下查看是否存在以指标域名为名的 txt 文件,如 http://219.141.219.69/360.cn.txt 4.若存在,则阐明指标网站存在破绽,细节如下: 可看到残缺 HTTP 申请细节,params参数为存在 log4j 注入破绽的参数 ...

December 13, 2021 · 1 min · jiezi

关于log4j2:Log4j史诗级漏洞从原理到实战只用3个实例就搞定

背景最近互联网技术圈最火的一件事莫过于Log4j2的破绽了。同时也涌现出了各类剖析文章,对于破绽的版本、破绽的起因、破绽的修复、程序员因而加班等等。 常常看我文章的敌人都晓得,面对这样热门有意思的技术点,怎能错过深入分析一波呢?大略你也曾经据说了,造成破绽的”罪魁祸首“是JNDI,明天咱们就聊它。 JNDI,好相熟,但……相熟的陌生人?JNDI到底是个什么鬼?好吧,如果你曾经有一两年的编程教训,但还不理解JNDI,甚至没听说过。那么,要么连忙换工作,要么连忙读读这篇文章。 JNDI是个什么鬼?说起JNDI,从事Java EE编程的人应该都在用着,但知不知道本人在用,那就看你对技术的钻研深度了。这次Log4j2曝出破绽,不正阐明大量我的项目或间接或间接的在用着JNDI。来看看JNDI到底是个什么鬼吧? 先来看看Sun官网的解释: Java命名和目录接口(Java Naming and Directory Interface ,JNDI)是用于从Java应用程序中拜访名称和目录服务的一组API。命名服务行将名称与对象相关联,以便能通过相应名称拜访这些对象。而目录服务即其对象具备属性及名称的命名服务。 命名或目录服务容许你集中管理共享信息的存储,这在网络应用程序中很重要,因为它能够使这类应用程序更加统一和易于治理。例如,能够将打印机配置存储在目录服务中,这样所有与打印机相干的应用程序都可能应用它。 概念是不是很形象,读了好几遍都没懂?一图胜千言: 看着怎么有点注册核心的意思?是的,如果你应用过Nacos或读过Nacos的源码,Naming Service这个概念肯定很相熟。在JNDI中,尽管实现形式不同、利用场景不同,但并不影响你通过类比注册核心的形式来了解JNDI。 如果你说没用过Nacos,那好,Map总用过吧。疏忽掉JNDI与Map底层实现的区别,JNDI提供了一个相似Map的绑定性能,而后又提供了基于lookup或search之类的办法来依据名称查找Object,好比Map的get办法。 总之,JNDI就是一个标准,标准就须要对应的API(也就是一些Java类)来实现。通过这组API,能够将Object(对象)和一个名称进行关联,同时提供了基于名称查找Object的路径。 最初,对于JNDI,SUN公司只是提供了一个接口标准,具体由对应的服务器来实现。比方,Tomcat有Tomcat的实现形式,JBoss有JBoss的实现形式,恪守标准就好。 命名服务与目录服务的区别命名服务就是下面提到的,相似Map的绑定与查找性能。比方:在Internet中的域名服务(domain naming service,DNS),就是提供将域名映射到IP地址的命名服务,在浏览器中输出域名,通过DNS找到相应的IP地址,而后拜访网站。 目录服务是对命名服务的扩大,是一种非凡的命名服务,提供了属性与对象的关联和查找。一个目录服务通常领有一个命名服务(然而一个命名服务不用具备一个目录服务)。比方电话簿就是一个典型的目录服务,个别先在电话簿里找到相干的人名,再找到这个人的电话号码。 目录服务容许属性(比方用户的电子邮件地址)与对象相关联(而命名服务则不然)。这样,应用目录服务时,能够基于对象的属性来搜寻它们。 JNDI架构分层JNDI通常分为三层: JNDI API:用于与Java应用程序与其通信,这一层把应用程序和理论的数据源隔离开来。因而无论应用程序是拜访LDAP、RMI、DNS还是其余的目录服务,跟这一层都没有关系。Naming Manager:也就是咱们提到的命名服务;JNDI SPI(Server Provider Interface):用于具体到实现的办法上。整体架构分层如下图: 须要留神的是:JNDI同时提供了应用程序编程接口(Application Programming Interface ,API)和服务提供程序接口(Service Provider Interface ,SPI)。 这样做对于与命名或目录服务交互的应用程序来说,必须存在一个用于该服务的JNDI服务提供程序,这便是JNDI SPI发挥作用的舞台。 一个服务提供程序基本上就是一组类,对特定的命名和目录服务实现了各种JNDI接口——这与JDBC驱动程序针对特定的数据系统实现各种JDBC接口极为类似。作为开发人员,不须要放心JNDI SPI。只需确保为每个要应用的命名或目录服务提供了一个服务提供程序即可。 JNDI的利用上面再理解一下JNDI容器的概念及利用场景。 JNDI容器环境JNDI中的命名(Naming),就是将Java对象以某个名称的模式绑定(binding)到一个容器环境(Context)中。当应用时,调用容器环境(Context)的查找(lookup)办法找出某个名称所绑定的Java对象。 容器环境(Context)自身也是一个Java对象,它也能够通过一个名称绑定到另一个容器环境(Context)中。将一个Context对象绑定到另外一个Context对象中,这就造成了一种父子级联关系,多个Context对象最终能够级联成一种树状构造,树中的每个Context对象中都能够绑定若干个Java对象。 JNDI 利用JNDI的根本应用操作就是:先创立一个对象,而后放到容器环境中,应用的时候再拿进去。 此时,你是否纳闷,干嘛这么吃力呢?换句话说,这么吃力能带来什么益处呢? 在实在利用中,通常是由零碎程序或框架程序先将资源对象绑定到JNDI环境中,后续在该零碎或框架中运行的模块程序就能够从JNDI环境中查找这些资源对象了。 对于JDNI与咱们实际相结合的一个例子是JDBC的应用。在没有基于JNDI实现时,连贯一个数据库通常须要:加载数据库驱动程序、连贯数据库、操作数据库、敞开数据库等步骤。而不同的数据库在对上述步骤的实现又有所不同,参数也可能发生变化。 如果把这些问题交由J2EE容器来配置和治理,程序就只需对这些配置和治理进行援用就能够了。 以Tomcat服务器为例,在启动时能够创立一个连贯到某种数据库系统的数据源(DataSource)对象,并将该数据源(DataSource)对象绑定到JNDI环境中,当前在这个Tomcat服务器中运行的Servlet和JSP程序就能够从JNDI环境中查问出这个数据源(DataSource)对象进行应用,而不必关怀数据源(DataSource)对象是如何创立进去的。 这种形式极大地加强了零碎的可维护性,即使当数据库系统的连贯参数产生变更时,也与应用程序开发人员无关。 JNDI将一些要害信息放到内存中,能够进步拜访效率;通过 JNDI能够达到解耦的目标,让零碎更具可维护性和可扩展性。 JNDI实战有了以上的概念和基础知识,当初能够开始实战了。 在架构图中,JNDI的实现层中蕴含了多种实现形式,这里就基于其中的RMI实现来写个实例体验一把。 基于RMI的实现RMI是Java中的近程办法调用,基于Java的序列化和反序列化传递数据。 能够通过如下代码来搭建一个RMI服务: // ①定义接口public interface RmiService extends Remote { String sayHello() throws RemoteException;}// ②接口实现public class MyRmiServiceImpl extends UnicastRemoteObject implements RmiService { protected MyRmiServiceImpl() throws RemoteException { } @Override public String sayHello() throws RemoteException { return "Hello World!"; }}// ③服务绑定并启动监听public class RmiServer { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.createRegistry(1099); System.out.println("RMI启动,监听:1099 端口"); registry.bind("hello", new MyRmiServiceImpl()); Thread.currentThread().join(); }}上述代码先定义了一个RmiService的接口,该接口实现了Remote,并对RmiService接口进行了实现。在实现的过程中继承了UnicastRemoteObject的具体服务实现类。 ...

December 13, 2021 · 3 min · jiezi

关于log4j2:Log4j史诗级漏洞我们这些小公司能做些什么

事件背景12月10日,看到朋友圈中曾经有人在通宵批改、上线零碎了。随即,又看到阿里云平安、腾讯安全部门收回的官网报告:”Apache Log4j2存在近程代码执行破绽“,且破绽已对外公开。 看到相干音讯,马上爬起来把所有我的项目的日志零碎过滤一遍,还好老我的项目采纳的log4j,新我的项目采纳的logback,没有中招。随后就看到朋友圈铺天盖地的相干音讯。 作为一个史诗级的事件,紧急批改破绽是必然的。作为程序员,如果看到这则音讯,连去核查一下零碎都做不到,那真的不是一个合格的程序员。 经验过这次事件,不仅是看热闹而已,还要思考一下,作为小公司如何防止、提前预防、做好筹备应答这类Bug。 破绽形容Apache Log4j2是一款优良的Java日志框架,与Logback平分秋色,大量支流的开源框架采纳了Log4j2,像Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。所以,这样一个底层框架呈现问题,影响面可想而知。 破绽信息:Apache Log4j 2.15.0-rc1 版本存在破绽绕过,需及时更新至 Apache Log4j 2.15.0-rc2 版本。 影响范畴:2.0 <= Apache log4j2 <= 2.14.1。 最新修复版本:https://github.com/apache/log... 补救计划计划一:降级版本,公布零碎; 计划二:长期补救: 批改JVM参数,设置 -Dlog4j2.formatMsgNoLookups=true。在波及破绽的我的项目的类门路(classpath)下减少log4j2.component.properties配置文件并减少配置项log4j2.formatMsgNoLookups=true。将零碎环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为 true。攻打原理攻打伪代码示例: import org.apache.log4j.Logger;import java.io.*;import java.sql.SQLException;import java.util.*;public class VulnerableLog4jExampleHandler implements HttpHandler { static Logger log = Logger.getLogger(log4jExample.class.getName()); /** * 示例伪代码:一个简略的HTTP端点,其中读取User Agent信息并进行日志记录; */ public void handle(HttpExchange he) throws IOException { // 获取user-agent信息 String userAgent = he.getRequestHeader("user-agent"); // 此行记录日志的代码,通过记录攻击者管制的HTTP用户代理标头来触发RCE。 // 攻击者能够设置他们的User-Agent header到${jndi:ldap://attacker.com/a} log.info("Request User Agent:" + userAgent); String response = "<h1>Hello There, " + userAgent + "!</h1>"; he.sendResponseHeaders(200, response.length()); OutputStream os = he.getResponseBody(); os.write(response.getBytes()); os.close(); }}基于上述代码的根本攻打步骤: ...

December 11, 2021 · 1 min · jiezi

关于log4j2:近期业务大量突增微服务性能优化总结3针对-x86-云环境改进异步日志等待策略

最近,业务增长的很迅猛,对于咱们后盾这块也是一个不小的挑战,这次遇到的外围业务接口的性能瓶颈,并不是独自的一个问题导致的,而是几个问题揉在一起:咱们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问题。这也是我经验不足,导致没能一下子定位解决;而我又对咱们后盾整个团队有着执著的自尊,不想通过大量程度扩容这种形式挺过压力顶峰,导致线上间断几晚都呈现了不同水平的问题,必定对于咱们的业务增长是有影响的。这也是我不成熟和要反思的中央。这系列文章次要记录下咱们针对这次业务增长,对于咱们后盾微服务零碎做的通用技术优化,针对业务流程和缓存的优化因为只实用于咱们的业务,这里就不再赘述了。本系列会分为如下几篇: 改良客户端负载平衡算法开发日志输入异样堆栈的过滤插件针对 x86 云环境改良异步日志期待策略减少对于同步微服务的 HTTP 申请期待队列的监控以及云上部署,须要小心达到实例网络流量下限导致的申请响应迟缓针对零碎要害业务减少必要的侵入式监控针对 x86 云环境改良异步日志期待策略因为线上业务量级比拟大(日申请上亿,日活用户几十万),同时业务波及逻辑很简单,线上日志级别咱们采纳的是 info 级别,导致线上日志量十分宏大,所以咱们很早就应用了 Log4j2 异步日志。Log4j2 异步日志基于 Disruptor,其中的期待策略,本次优化前,咱们选用的是 BLOCK。 Log4j2 异步日志的期待策略Disruptor 的消费者做的事件其实就是一直查看是否有音讯到来,其实就是某个状态位是否就绪,就绪后读取音讯进行生产。至于如何一直查看,这个就是期待策略。Disruptor 中有很多期待策略,相熟多处理器编程的对于期待策略必定不会生疏,在这里能够简略了解为当工作没有到来时,线程应该如何期待并且让出 CPU 资源能力在工作到来时尽量快的开始工作。在 Log4j2 中,异步日志基于 Disruptor,同时应用 AsyncLoggerConfig.WaitStrategy 这个环境变量对于 Disruptor 的期待策略进行配置,目前最新版本的 Log4j2 中能够配置: switch (strategyUp) { case "SLEEP": final long sleepTimeNs = parseAdditionalLongProperty(propertyName, "SleepTimeNs", 100L); final String key = getFullPropertyKey(propertyName, "Retries"); final int retries = PropertiesUtil.getProperties().getIntegerProperty(key, 200); return new SleepingWaitStrategy(retries, sleepTimeNs); case "YIELD": return new YieldingWaitStrategy(); case "BLOCK": return new BlockingWaitStrategy(); case "BUSYSPIN": return new BusySpinWaitStrategy(); case "TIMEOUT": return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS); default: return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS);}原本,咱们应用的是基于 Wait/Notify 的 BlockingWaitStrategy。然而这种策略导致业务量突增的时候,日志写入线程在一段时间内始终未能被唤醒,导致 RingBuffer 中积压了很多日志事件。 ...

November 3, 2021 · 2 min · jiezi

关于log4j2:近期业务大量突增微服务性能优化总结2开发日志输出异常堆栈的过滤插件

最近,业务增长的很迅猛,对于咱们后盾这块也是一个不小的挑战,这次遇到的外围业务接口的性能瓶颈,并不是独自的一个问题导致的,而是几个问题揉在一起:咱们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问题。这也是我经验不足,导致没能一下子定位解决;而我又对咱们后盾整个团队有着执著的自尊,不想通过大量程度扩容这种形式挺过压力顶峰,导致线上间断几晚都呈现了不同水平的问题,必定对于咱们的业务增长是有影响的。这也是我不成熟和要反思的中央。这系列文章次要记录下咱们针对这次业务增长,对于咱们后盾微服务零碎做的通用技术优化,针对业务流程和缓存的优化因为只实用于咱们的业务,这里就不再赘述了。本系列会分为如下几篇: 改良客户端负载平衡算法开发日志输入异样堆栈的过滤插件针对 x86 云环境改良异步日志期待策略减少对于同步微服务的 HTTP 申请期待队列的监控以及云上部署,须要小心达到实例网络流量下限导致的申请响应迟缓针对零碎要害业务减少必要的侵入式监控开发日志输入异样堆栈的过滤插件咱们个别会在异样产生时,打印日志,同时日志中带有异样堆栈。 在线上因为某个根底组件或者某个存储慢导致大量超时异样产生时,如果都打印残缺的异样栈,则一下子会输入大量的日志,导致写入日志也会成为瓶颈(尽管咱们应用了 Log4j2 的异步日志 ,然而如果 RingBuffer 满了输入日志还是会导致业务线程阻塞)。从而导致同一微服务中其余原本失常的业务,也变得不失常了。 并且,咱们发现 为何 JVM 参数中退出 -XX:-OmitStackTraceInFastThrow为了防止这个问题可能首先想到的是,JVM 参数中去掉 -XX:-OmitStackTraceInFastThrow。这个 OmitStackTraceInFastThrow 默认是启用的,其作用是,当某个 JDK 内置异样通过某一个办法抛出过多次数时(常见是 NullPointerException),主动省略异样堆栈,其实就是 Throwable.getOurStacktrace() 返回空数组。底层实现的形式是当这些异样被抛出时,会记录在办法的 method_data 中。当这些异样被抛出时,查看对应办法的 method_data 是否有过多次数的这些异样被抛出,如果有,则应用不含堆栈的异样对象替换原有异样对象从而实现异样堆栈被疏忽。对应源码: graphKit.cpp bool treat_throw_as_hot = false; ciMethodData* md = method()->method_data(); if (ProfileTraps) { //如果有太屡次,则 treat_throw_as_hot 为 true if (too_many_traps(reason)) { treat_throw_as_hot = true; } if (C->trap_count(reason) != 0 && method()->method_data()->trap_count(reason) != 0 && has_ex_handler()) { treat_throw_as_hot = true; } } if (treat_throw_as_hot && (!StackTraceInThrowable || OmitStackTraceInFastThrow)) { ciInstance* ex_obj = NULL; switch (reason) { case Deoptimization::Reason_null_check: //对于 NullPointerException 返回对应的空堆栈的内置 NullPointerException 对象 ex_obj = env()->NullPointerException_instance(); break; case Deoptimization::Reason_div0_check: //对于 ArithmeticException 返回对应的空堆栈的内置 ArithmeticException 对象 ex_obj = env()->ArithmeticException_instance(); break; case Deoptimization::Reason_range_check: //对于 ArrayIndexOutOfBounds 返回对应的空堆栈的内置 NullPoArrayIndexOutOfBoundsinterException 对象 ex_obj = env()->ArrayIndexOutOfBoundsException_instance(); break; case Deoptimization::Reason_class_check: if (java_bc() == Bytecodes::_aastore) { //对于 ArrayStoreException 返回对应的空堆栈的内置 ArrayStoreException 对象 ex_obj = env()->ArrayStoreException_instance(); } else { //对于 ClassCastException 返回对应的空堆栈的内置 ClassCastException 对象 ex_obj = env()->ClassCastException_instance(); } break; default: break; }然而,咱们个别会在 JVM 启动参数中退出 -XX:-OmitStackTraceInFastThrow 将其敞开,次要起因是: ...

November 2, 2021 · 3 min · jiezi

关于log4j2:log4j2-滚动删除日志文件最后访问时间大小

有时咱们须要滚动删除日志,不然日志会越积越多。从 log4j2 2.5 之后,在日志滚动的时候能够自定义删除操作,比方咱们心愿保留3天的日志,能够这么配置: - name: FileAppender fileName: /tmp/log/test.log filePattern: /tmp/log/test.log.%d{yyyy-MM-dd}" PatternLayout: pattern: %-d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%c] [%p] %m%n Policies: TimeBasedTriggeringPolicy: {} DefaultRolloverStrategy: Delete: basePath: /tmp/log maxDepth: 1 IfFileName: glob: "*.log.*" IfLastModified: age: 3dDefaultRolloverStrategy 中的 Delete 局部就是删除相干的配置 basePath 定义了扫描日志文件的根门路。maxDepth 定义了遍历的层级,1示意 bashPath 下的所有文件IfFileName 定义了扫描的文件格式IfLastModified 定义了只有在最初拜访工夫在3天以上的才会被删除这里须要留神 删除操作只会产生在日志滚动时,而滚动的机会取决于 filePattern 和 Triggering Policies (下面配置中 Policies 局部)IfFileName 指定删除的文件格式,只有符合条件都会被删除,并没有限度是通过以后服务输入。下面这样配置是为了只删除历史日志文件。IfFileName 和 IfLastModified 都属于 pathConditions。pathConditions 是一个数组,决定哪些文件会被删除,如果定义了多个,须要多个条件同时满足才会被删除。以上能满足个别的需要了,然而偶然删除过期数据还不足够,比方某个工夫日志量激增,可能会导致磁盘占满,所以须要加一个兜底策略。 - name: FileAppender fileName: /tmp/log/test.log filePattern: /tmp/log/test.log.%d{yyyy-MM-dd}" PatternLayout: pattern: %-d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%c] [%p] %m%n Policies: TimeBasedTriggeringPolicy: {} DefaultRolloverStrategy: Delete: basePath: /tmp/log maxDepth: 1 IfFileName: glob: "*.log.*" IfAny: IfLastModified: age: 3d IfAccumulatedFileSize: exceeds: 200GB这里改了一下条件,IfAny 示意或者,对于文件名合乎 .log. 格局的,最初拜访工夫为3天以上或总共超过 200GB 时都会删除文件。 ...

November 1, 2021 · 2 min · jiezi

关于log4j2:获取异常信息里再出异常就找不到日志了我TM人傻了

本系列是 我TM人傻了 系列第三期[捂脸],往期精彩回顾: 降级到Spring 5.3.x之后,GC次数急剧减少,我TM人傻了这个大表走索引字段查问的 SQL 怎么就成全扫描了,我TM人傻了 最近组里用第三方给的 SDK 搞了点开发,最近线上忽然开始报错,并且发现一个特地奇怪的问题,组员和我说,代码运行到一半不走了,跳过了一段(这代码是刚加入东奥会加入跳远么???)。 代码如下,逻辑非常简单: try { log.info("initiate client with conf: {}", conf); SDKClient client = new SDKClient(conf); client.init(); log.info("client initiated");} catch (Exception e) { log.error("initiate client failed", e);}log.info("start to manipulate...");咱们发现 client 实际上没有初始化胜利,前面的业务解决始终在报错。查看日志,发现: initiate client with conf: xxxxxstart to manipulate...这就是组员说的代码产生了跳跃。因为既没有打印 client initiated,也没有打印 initiate client failed...就间接 start to manipulate... 了。 老读者晓得,咱们的线上是 k8s + Docker,并且每个镜像中内置了 Arthas,并且 Java 版本是 Java 16,并且启用了 JFR。日志中具备链路信息,通过 ELK Agent 拉取到对立日志服务器。 ...

August 11, 2021 · 3 min · jiezi

关于log4j2:如何监控-Log4j2-异步日志遇到写入瓶颈

如何监控 Log4j2 异步日志遇到写入瓶颈在之前的一篇文章中(一次鞭辟入里的 Log4j2 异步日志输入阻塞问题的定位),咱们详细分析了一个经典的 Log4j2 异步日志阻塞问题的定位,次要起因还是日志文件写入慢了。并且比拟深刻的剖析了 Log4j2 异步日志的原理,最初给出了一些解决方案。 新的问题 - 如何更好的应答这种状况?之前提出的解决方案仅仅是针对之前定位的问题的优化,然而随着业务倒退,日志量必定会更多,大量的日志可能导致写入日志成为新的性能瓶颈。对于这种状况,咱们须要监控。 首先想到的是过程内部采集零碎指标监控:当初服务都提倡上云,并实现云原生服务。对于云服务,存储日志很可能应用 NFS(Network File System),例如 AWS 的 EFS。这种 NFS 一动都能够动静的管制 IO 最大承载,然而服务的增长是很难预估完满的,并且高并发业务流量根本都是一瞬间达到,仅通过 IO 定时采集很难评估到真正的流量尖峰(例如 IO 定时采集是 5s 一次,然而在某一秒内忽然达到很多流量,导致过程内大多线程阻塞,这之后采集 IO 看到 IO 压力貌似不大的样子)。并且,因为线程的阻塞,导致可能咱们看到的 CPU 占用貌似也不高的样子。所以,内部定时采集指标,很难真正定位到日志流量问题。 而后咱们思考过程本人监控,裸露接口给内部监控定时查看,例如 K8s 的 pod 健康检查等等。在过程的日志写入压力过大的时候,新扩容一个实例;启动实现后,在注册核心将这个日志压力大的过程的状态设置为临时下线(例如 Eureka 置为 OUT_OF_SERVICE,Nacos 置为 PAUSED),让流量转发到其余实例。待日志压力小之后,再批改状态为 UP,持续服务。 那么如何实现这种监控呢? 监控 Log4j2 异步日志的外围 - 监控 RingBuffer依据之前咱们剖析 Log4j2 异步日志的原理,咱们晓得其外围是 RingBuffer 这个数据结构作为缓存。咱们能够监控其残余大小的变动来判断以后日志压力。那么怎么能拿到呢? Log4j2 异步日志与 RingBuffer 的关系Log4j2 对于每一个 AsyncLogger 配置,都会创立一个独立的 RingBuffer,例如上面的 Log4j2 配置: <!--省略了除了 loggers 以外的其余配置--> <loggers> <!--default logger --> <Asyncroot level="info" includeLocation="true"> <appender-ref ref="console"/> </Asyncroot> <AsyncLogger name="RocketmqClient" level="error" additivity="false" includeLocation="true"> <appender-ref ref="console"/> </AsyncLogger> <AsyncLogger name="com.alibaba.druid.pool.DruidDataSourceStatLoggerImpl" level="error" additivity="false" includeLocation="true"> <appender-ref ref="console"/> </AsyncLogger> <AsyncLogger name="org.mybatis" level="error" additivity="false" includeLocation="true"> <appender-ref ref="console"/> </AsyncLogger></loggers>这个配置蕴含 4 个 AsyncLogger,对于每个 AsyncLogger 都会创立一个 RingBuffer。Log4j2 也思考到了监控 AsyncLogger 这种状况,所以将 AsyncLogger 的监控裸露成为一个 MBean(JMX Managed Bean)。 ...

July 23, 2021 · 3 min · jiezi

关于log4j2:LOG4J2-配置详细入门指南

LOG4J2 配置:具体入门指南埃里克·迪特里希 2018 年 9 月 5 日 软件开发中最烦人的方面之一相对是日志记录。如果一个非平庸的应用程序短少日志记录,那么保护它的人都会遇到困难,预先调试将次要是一个猜谜游戏。明天,咱们通过提供无关 log4j2 配置的教程,为解决这种状况做出了另一奉献。 有很多办法能够为Java记录日志。您能够应用更手动的办法,但举荐的办法是采纳专用的日志记录框架。这就是为什么咱们要介绍 Apache log4j2,它是 log4j 的改良版本,是行业标准的日志框架之一。 咱们将首先疾速回顾一下咱们之前对于 Java 日志的文章,并介绍一些对于 log4j2 的事实。而后咱们将持续介绍咱们在上一个教程中开始编写的示例应用程序的状态。 之后,咱们进入文章的重点。您将学习如何配置 log4j2,从根底开始,并逐渐学习更高级的主题,例如日志格局、附加程序、日志级别和日志层次结构。 让咱们开始吧。 应用 Log4j2 进行日志记录:又不是头一回了本教程中,咱们应用了 log4j 版本 2,这是来自 Apache 我的项目的日志框架。 让咱们进一步理解 Java 应用程序日志并查看 log4j2 配置。明天咱们将介绍 log4j2 配置的根本方面,以帮忙您入门。 Log4j 的性能使其成为 Java 最风行的日志框架之一。它能够配置为多个日志记录目的地和各种日志文件格式。 能够在单个类级别过滤和定向日志音讯,从而使开发人员和运维人员可能对应用程序音讯进行精密管制。 让咱们通过应用命令行 Java 应用程序配置 log4j 来查看这些机制。 示例应用程序让咱们应用 log4j 进行日志记录的应用程序。 package com.company;import org.apache.logging.log4j.Logger;import org.apache.logging.log4j.LogManager;public class Main { private static final Logger logger = LogManager.getLogger(Main.class); public static void main(String[] args) { String message = "Hello there!"; logger.trace(message); logger.debug(message); logger.info(message); logger.warn(message); logger.error(message); logger.fatal(message); }}咱们在每个 log4j 预约义的日志级别记录雷同的音讯:跟踪、调试、信息、正告、谬误和致命。 ...

July 2, 2021 · 4 min · jiezi

Spring-Boot-2-集成log4j2日志框架

前言Log4j2是 Log4j 的进化版本,并提供了许多 Logback 可用的改进,同时解决了 Logback 体系结构中的一些固有问题。而且日志处理中我们会用到kafka作为日志管道。而kafka客户端依赖与Logback的兼容不是很完美,你可以选择排除依赖冲突或者使用Log4j2 。<!-- more --> 排除Logback依赖Spring Boot 2.x默认使用Logback日志框架,要使用 Log4j2必须先排除 Logback。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <!--排除logback--> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions></dependency>引入Log4j2依赖<!--log4j2 依赖--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId></dependency>上面的 log4j2 已经适配了slf4j日志门面,所以我们的代码无需替换,只需要替换具体的日志框架以及对应的配置文件。 配置Log4j2创建log4j2.xml文件,放在工程resources目录里。这样就可以不加任何配置。如果你需要指定配置文件需要在Spring boot 配置文件application.yml中指定 logging.config 属性。下面是一份比较详细的 log4j2 配置文件 : <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="WARN" monitorInterval="30"> <!--先定义所有的appender--> <appenders> <!--这个输出控制台的配置--> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用--> <File name="log" fileName="log/test.log" append="false"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 --> <DefaultRolloverStrategy max="20"/> </RollingFile> <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.springframework" level="INFO"/> <logger name="org.mybatis" level="INFO"/> <root level="all"> <appender-ref ref="Console"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers> </configuration>基本上你拿上面的配置根据你自己的需要更改一下即可生效。 windows 下 ${sys:user.home} 会将日志打印到用户目录下 ...

October 9, 2019 · 2 min · jiezi

SpringBoot20-基础案例02配置Log4j2实现不同环境日志打印

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、Log4j2日志简介日志打印是了解Web项目运行的最直接方式,所以在项目开发中是需要首先搭建好的环境。 1、Log4j2特点1)核心特点相比与其他的日志系统,log4j2丢数据这种情况少;disruptor技术,在多线程环境下,性能高;并发的特性,减少了死锁的发生。 2)性能测试 2、日志打印之外观模式每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,增加应用程序代码和日志框架的耦合性。《阿里巴巴Java开发手册》,其中有一条规范做了『强制』要求:SLF4JJava简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现。 二、配置日志打印1、项目结构 2、不同环境的日志配置使用最直接的方式,不同环境加载不同的日志配置。1)开发环境配置 logging: config: classpath:log4j2-boot-dev.xml2)生产环境配置 logging: config: classpath:log4j2-boot-pro.xml3、Log4j2的配置文件<?xml version="1.0" encoding="UTF-8"?><!--monitorInterval:Log4j2 自动检测修改配置文件和重新配置本身,设置间隔秒数--><configuration monitorInterval="5"> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--变量配置--> <Properties> <!-- 格式化输出: %date表示日期,%thread表示线程名, %-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <!-- %logger{36} 表示 Logger 名字最长36个字符 --> <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <!-- 定义日志存储的路径,不要配置相对路径 --> <property name="FILE_PATH" value="E:/logs/dev" /> <property name="FILE_NAME" value="boot-log4j2" /> </Properties> <appenders> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="${LOG_PATTERN}"/> <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> </console> <!--文件会打印出所有信息--> <File name="Filelog" fileName="${FILE_PATH}/log4j2.log" append="true"> <PatternLayout pattern="${LOG_PATTERN}"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy同一文件夹下15个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy同一文件夹下15个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy同一文件夹下15个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。--> <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.mybatis" level="info" additivity="false"> <AppenderRef ref="Console"/> </logger> <!--监控系统信息--> <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。--> <Logger name="org.springframework" level="info" additivity="false"> <AppenderRef ref="Console"/> </Logger> <root level="info"> <appender-ref ref="Console"/> <appender-ref ref="Filelog"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers></configuration>三、测试日志打印1、简单的测试程序import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class Log4j2Controller { private static final Logger LOGGER = LoggerFactory.getLogger(Log4j2Controller.class); /** * 日志级别 * OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL */ @RequestMapping("/printLog") public String printLog (){ LOGGER.error("ERROR 级别日志"); LOGGER.warn("WARN 级别日志"); LOGGER.info("INFO 级别日志"); LOGGER.debug("DEBUG 级别日志"); LOGGER.trace("TRACE 级别日志"); return "success" ; }}2、测试效果图 ...

July 10, 2019 · 2 min · jiezi

一次生产环境单机日志不打印的排查过程

背景Q 业务系统在做发布前检查工作时,发现一台单机的主日志没有打印,而其他生产机器的日志表现则是正常的。 排查流 思考通过 greys 观察主 service 的业务入口,发现该机器线上流量的请求接受、逻辑处理、结果反馈均正常。排查范围就聚焦在 log jar 配置上。 解决SOF搜索,找到一篇 log 绑定包冲突的排查 & 解决方法。排查本地是否存在绑定包冲突的问题,结果发现确实是的 —— 引入 slf4j-log4j12-empty_version.jar,排掉其他 jar 包引入的日志绑定包。发布、部署该单机,问题解决。原理研究了一下 slf4j-log4j12 实现绑定的原理——简单来说,日志输出到单机本地,需要以下 3 类 jar 包相互配合: slf4j 。业务程序调用的日志接口,只有接口定义,没有实现,从而保证了代码层调用 Log 的统一日志组件。比如 log4j-api、log4j-core,是 log4j2 的内部实现;logback-classic、logback-core,是 logback 的内部实现;log4j,是 logj4 的内部实现。它们本身是没有冲突的,可以并存绑定包。将 slf4j-api 的接口绑定到对应的 log 实现上,比如 slf4j-log4j12、log4j-slf4j-impl(关键) 具体到代码实现上,就是通过 StaticLoggerBinder.getSingleton ( ) 方法返回的单例,实现包的绑定 —— qjt 程序中,同时存在着两类绑定包:log4j-slf4j-impl-2.7.jar 和 slf4j-log4j12-1.7.2.jar。这实际是一种意义上的日志绑定包冲突 —— 选定哪个包,取决于 ClassLoader 先加载哪个,具有一定的随机性。 其他生产机 StaticLoggerBinder 的加载情况 —— 可以看到使用的是 log4j-slf4j-impl-2.7.jar ...

June 4, 2019 · 1 min · jiezi

探索Java日志的奥秘:底层日志系统-log4j2

前言log4j2是apache在log4j的基础上,参考logback架构实现的一套新的日志系统(我感觉是apache害怕logback了)。log4j2的官方文档上写着一些它的优点:在拥有全部logback特性的情况下,还修复了一些隐藏问题API 分离:现在log4j2也是门面模式使用日志,默认的日志实现是log4j2,当然你也可以用logback(应该没有人会这么做)性能提升:log4j2包含下一代基于LMAX Disruptor library的异步logger,在多线程场景下,拥有18倍于log4j和logback的性能多API支持:log4j2提供Log4j 1.2, SLF4J, Commons Logging and java.util.logging (JUL) 的API支持避免锁定:使用Log4j2 API的应用程序始终可以选择使用任何符合SLF4J的库作为log4j-to-slf4j适配器的记录器实现自动重新加载配置:与Logback一样,Log4j 2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。高级过滤: 与Logback一样,Log4j 2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。插件架构: Log4j使用插件模式配置组件。因此,您无需编写代码来创建和配置Appender,Layout,Pattern Converter等。Log4j自动识别插件并在配置引用它们时使用它们。属性支持:您可以在配置中引用属性,Log4j将直接替换它们,或者Log4j将它们传递给将动态解析它们的底层组件。Java 8 Lambda支持自定义日志级别产生垃圾少:在稳态日志记录期间,Log4j 2 在独立应用程序中是无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应时间性能。和应用server集成:版本2.10.0引入了一个模块log4j-appserver,以改进与Apache Tomcat和Eclipse Jetty的集成。Log4j2类图:这次从四个地方去探索源码:启动,配置,异步,插件化源码探索启动log4j2的关键组件LogManager根据配置指定LogContexFactory,初始化对应的LoggerContextLoggerContext1、解析配置文件,解析为对应的java对象。2、通过LoggerRegisty缓存Logger配置3、Configuration配置信息4、start方法解析配置文件,转化为对应的java对象5、通过getLogger获取logger对象LoggerLogManaer该组件是Log4J启动的入口,后续的LoggerContext以及Logger都是通过调用LogManager的静态方法获得。我们可以使用下面的代码获取LoggerLogger logger = LogManager.getLogger();可以看出LogManager是十分关键的组件,因此在这个小节中我们详细分析LogManager的启动流程。LogManager启动的入口是下面的static代码块:/** * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be * extended to allow multiple implementations to be used. / static { // Shortcut binding to force a specific logging implementation. final PropertiesUtil managerProps = PropertiesUtil.getProperties(); final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME); if (factoryClassName != null) { try { factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class); } catch (final ClassNotFoundException cnfe) { LOGGER.error(“Unable to locate configured LoggerContextFactory {}”, factoryClassName); } catch (final Exception ex) { LOGGER.error(“Unable to create configured LoggerContextFactory {}”, factoryClassName, ex); } } if (factory == null) { final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>(); // note that the following initial call to ProviderUtil may block until a Provider has been installed when // running in an OSGi environment if (ProviderUtil.hasProviders()) { for (final Provider provider : ProviderUtil.getProviders()) { final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory(); if (factoryClass != null) { try { factories.put(provider.getPriority(), factoryClass.newInstance()); } catch (final Exception e) { LOGGER.error(“Unable to create class {} specified in provider URL {}”, factoryClass.getName(), provider .getUrl(), e); } } } if (factories.isEmpty()) { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } else if (factories.size() == 1) { factory = factories.get(factories.lastKey()); } else { final StringBuilder sb = new StringBuilder(“Multiple logging implementations found: \n”); for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) { sb.append(“Factory: “).append(entry.getValue().getClass().getName()); sb.append(”, Weighting: “).append(entry.getKey()).append(’\n’); } factory = factories.get(factories.lastKey()); sb.append(“Using factory: “).append(factory.getClass().getName()); LOGGER.warn(sb.toString()); } } else { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } } }这段静态代码段主要分为下面的几个步骤:首先根据特定配置文件的配置信息获取loggerContextFactory如果没有找到对应的Factory的实现类则通过ProviderUtil中的getProviders()方法载入providers,随后通过provider的loadLoggerContextFactory方法载入LoggerContextFactory的实现类如果provider中没有获取到LoggerContextFactory的实现类或provider为空,则使用SimpleLoggerContextFactory作为LoggerContextFactory。根据配置文件载入LoggerContextFactory// Shortcut binding to force a specific logging implementation. final PropertiesUtil managerProps = PropertiesUtil.getProperties(); final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME); if (factoryClassName != null) { try { factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class); } catch (final ClassNotFoundException cnfe) { LOGGER.error(“Unable to locate configured LoggerContextFactory {}”, factoryClassName); } catch (final Exception ex) { LOGGER.error(“Unable to create configured LoggerContextFactory {}”, factoryClassName, ex); } }在这段逻辑中,LogManager优先通过配置文件”log4j2.component.properties”通过配置项”log4j2.loggerContextFactory”来获取LoggerContextFactory,如果用户做了对应的配置,通过newCheckedInstanceOf方法实例化LoggerContextFactory的对象,最终的实现方式为:public static <T> T newInstanceOf(final Class<T> clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException { try { return clazz.getConstructor().newInstance(); } catch (final NoSuchMethodException ignored) { // FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as above return clazz.newInstance(); } }在默认情况下,不存在初始的默认配置文件log4j2.component.properties,因此需要从其他途径获取LoggerContextFactory。通过Provider实例化LoggerContextFactory对象代码:if (factory == null) { final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>(); // note that the following initial call to ProviderUtil may block until a Provider has been installed when // running in an OSGi environment if (ProviderUtil.hasProviders()) { for (final Provider provider : ProviderUtil.getProviders()) { final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory(); if (factoryClass != null) { try { factories.put(provider.getPriority(), factoryClass.newInstance()); } catch (final Exception e) { LOGGER.error(“Unable to create class {} specified in provider URL {}”, factoryClass.getName(), provider .getUrl(), e); } } } if (factories.isEmpty()) { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } else if (factories.size() == 1) { factory = factories.get(factories.lastKey()); } else { final StringBuilder sb = new StringBuilder(“Multiple logging implementations found: \n”); for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) { sb.append(“Factory: “).append(entry.getValue().getClass().getName()); sb.append(”, Weighting: “).append(entry.getKey()).append(’\n’); } factory = factories.get(factories.lastKey()); sb.append(“Using factory: “).append(factory.getClass().getName()); LOGGER.warn(sb.toString()); } } else { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } }这里比较有意思的是hasProviders和getProviders都会通过线程安全的方式去懒加载ProviderUtil这个对象。跟进lazyInit方法:protected static void lazyInit() { //noinspection DoubleCheckedLocking if (INSTANCE == null) { try { STARTUP_LOCK.lockInterruptibly(); if (INSTANCE == null) { INSTANCE = new ProviderUtil(); } } catch (final InterruptedException e) { LOGGER.fatal(“Interrupted before Log4j Providers could be loaded.”, e); Thread.currentThread().interrupt(); } finally { STARTUP_LOCK.unlock(); } } }再看构造方法:private ProviderUtil() { for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) { loadProvider(resource.getUrl(), resource.getClassLoader()); } }这里的懒加载其实就是懒加载Provider对象。在创建新的providerUtil实例的过程中就会直接实例化provider对象,其过程是先通过getClassLoaders方法获取provider的类加载器,然后通过loadProviders(classLoader);加载类。在providerUtil实例化的最后,会统一查找”META-INF/log4j-provider.properties”文件中对应的provider的url,会考虑从远程加载provider。而loadProviders方法就是在ProviderUtil的PROVIDERS列表中添加对一个的provider。可以看到默认的provider是org.apache.logging.log4j.core.impl.Log4jContextFactoryLoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactoryLog4jAPIVersion = 2.1.0FactoryPriority= 10很有意思的是这里懒加载加上了锁,而且使用的是lockInterruptibly这个方法。lockInterruptibly和lock的区别如下:lock 与 lockInterruptibly比较区别在于:lock 优先考虑获取锁,待获取锁成功后,才响应中断。lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt 方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。 ReentrantLock.lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。上面有一句注释值得注意:/* * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support. * * @since 2.1 / protected static final Lock STARTUP_LOCK = new ReentrantLock(); // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and // wait for a Provider to be installed. See LOG4J2-373 private static volatile ProviderUtil INSTANCE;原来这里是为了让osgi可以阻止启动。再回到logManager:可以看到在加载完Provider之后,会做factory的绑定:if (factories.isEmpty()) { LOGGER.error(“Log4j2 could not find a logging implementation. " + “Please add log4j-core to the classpath. Using SimpleLogger to log to the console…”); factory = new SimpleLoggerContextFactory(); } else if (factories.size() == 1) { factory = factories.get(factories.lastKey()); } else { final StringBuilder sb = new StringBuilder(“Multiple logging implementations found: \n”); for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) { sb.append(“Factory: “).append(entry.getValue().getClass().getName()); sb.append(”, Weighting: “).append(entry.getKey()).append(’\n’); } factory = factories.get(factories.lastKey()); sb.append(“Using factory: “).append(factory.getClass().getName()); LOGGER.warn(sb.toString()); }到这里,logmanager的启动流程就结束了。配置在不使用slf4j的情况下,我们获取logger的方式是这样的:Logger logger = logManager.getLogger(xx.class)跟进getLogger方法: public static Logger getLogger(final Class<?> clazz) { final Class<?> cls = callerClass(clazz); return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls)); }这里有一个getContext方法,跟进,public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) { try { return factory.getContext(FQCN, loader, null, currentContext); } catch (final IllegalStateException ex) { LOGGER.warn(ex.getMessage() + " Using SimpleLogger”); return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext); } }上文提到factory的具体实现是Log4jContextFactory,跟进getContext方法:public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext); if (externalContext != null && ctx.getExternalContext() == null) { ctx.setExternalContext(externalContext); } if (ctx.getState() == LifeCycle.State.INITIALIZED) { ctx.start(); } return ctx; }直接看start:public void start() { LOGGER.debug(“Starting LoggerContext[name={}, {}]…”, getName(), this); if (PropertiesUtil.getProperties().getBooleanProperty(“log4j.LoggerContext.stacktrace.on.start”, false)) { LOGGER.debug(“Stack trace to locate invoker”, new Exception(“Not a real error, showing stack trace to locate invoker”)); } if (configLock.tryLock()) { try { if (this.isInitialized() || this.isStopped()) { this.setStarting(); reconfigure(); if (this.configuration.isShutdownHookEnabled()) { setUpShutdownHook(); } this.setStarted(); } } finally { configLock.unlock(); } } LOGGER.debug(“LoggerContext[name={}, {}] started OK.”, getName(), this); }发现其中的核心方法是reconfigure方法,继续跟进:private void reconfigure(final URI configURI) { final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; LOGGER.debug(“Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}”, contextName, configURI, this, cl); final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl); if (instance == null) { LOGGER.error(“Reconfiguration failed: No configuration found for ‘{}’ at ‘{}’ in ‘{}’”, contextName, configURI, cl); } else { setConfiguration(instance); / * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) { * old.stop(); } / final String location = configuration == null ? “?” : String.valueOf(configuration.getConfigurationSource()); LOGGER.debug(“Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}”, contextName, location, this, cl); } }可以看到每一个configuration都是从ConfigurationFactory拿出来的,我们先看看这个类的getInstance看看:public static ConfigurationFactory getInstance() { // volatile works in Java 1.6+, so double-checked locking also works properly //noinspection DoubleCheckedLocking if (factories == null) { LOCK.lock(); try { if (factories == null) { final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>(); final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY); if (factoryClass != null) { addFactory(list, factoryClass); } final PluginManager manager = new PluginManager(CATEGORY); manager.collectPlugins(); final Map<String, PluginType<?>> plugins = manager.getPlugins(); final List<Class<? extends ConfigurationFactory>> ordered = new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size()); for (final PluginType<?> type : plugins.values()) { try { ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class)); } catch (final Exception ex) { LOGGER.warn(“Unable to add class {}”, type.getPluginClass(), ex); } } Collections.sort(ordered, OrderComparator.getInstance()); for (final Class<? extends ConfigurationFactory> clazz : ordered) { addFactory(list, clazz); } // see above comments about double-checked locking //noinspection NonThreadSafeLazyInitialization factories = Collections.unmodifiableList(list); } } finally { LOCK.unlock(); } } LOGGER.debug(“Using configurationFactory {}”, configFactory); return configFactory; }这里可以看到ConfigurationFactory中利用了PluginManager来进行初始化,PluginManager会将ConfigurationFactory的子类加载进来,默认使用的XmlConfigurationFactory,JsonConfigurationFactory,YamlConfigurationFactory这三个子类,这里插件化加载暂时按下不表。回到reconfigure这个方法,我们看到获取ConfigurationFactory实例之后会去调用getConfiguration方法:public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) { if (!isActive()) { return null; } if (loader == null) { return getConfiguration(name, configLocation); } if (isClassLoaderUri(configLocation)) { final String path = extractClassLoaderUriPath(configLocation); final ConfigurationSource source = getInputFromResource(path, loader); if (source != null) { final Configuration configuration = getConfiguration(source); if (configuration != null) { return configuration; } } } return getConfiguration(name, configLocation); }跟进getConfiguration,这里值得注意的是有很多个getConfiguration,注意甄别,如果不确定的话可以通过debug的方式来确定。public Configuration getConfiguration(final String name, final URI configLocation) { if (configLocation == null) { final String config = this.substitutor.replace( PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY)); if (config != null) { ConfigurationSource source = null; try { source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config)); } catch (final Exception ex) { // Ignore the error and try as a String. LOGGER.catching(Level.DEBUG, ex); } if (source == null) { final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); source = getInputFromString(config, loader); } if (source != null) { for (final ConfigurationFactory factory : factories) { final String[] types = factory.getSupportedTypes(); if (types != null) { for (final String type : types) { if (type.equals(””) || config.endsWith(type)) { final Configuration c = factory.getConfiguration(source); if (c != null) { return c; } } } } } } } } else { for (final ConfigurationFactory factory : factories) { final String[] types = factory.getSupportedTypes(); if (types != null) { for (final String type : types) { if (type.equals(”*”) || configLocation.toString().endsWith(type)) { final Configuration config = factory.getConfiguration(name, configLocation); if (config != null) { return config; } } } } } } Configuration config = getConfiguration(true, name); if (config == null) { config = getConfiguration(true, null); if (config == null) { config = getConfiguration(false, name); if (config == null) { config = getConfiguration(false, null); } } } if (config != null) { return config; } LOGGER.error(“No log4j2 configuration file found. Using default configuration: logging only errors to the console.”); return new DefaultConfiguration(); }这里就会根据之前加载进来的factory进行配置的获取,具体的不再解析。回到reconfigure,之后的步骤就是setConfiguration,入参就是刚才获取的configprivate synchronized Configuration setConfiguration(final Configuration config) { Assert.requireNonNull(config, “No Configuration was provided”); final Configuration prev = this.config; config.addListener(this); final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES); try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException map.putIfAbsent(“hostName”, NetUtils.getLocalHostname()); } catch (final Exception ex) { LOGGER.debug(“Ignoring {}, setting hostName to ‘unknown’”, ex.toString()); map.putIfAbsent(“hostName”, “unknown”); } map.putIfAbsent(“contextName”, name); config.start(); this.config = config; updateLoggers(); if (prev != null) { prev.removeListener(this); prev.stop(); } firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config)); try { Server.reregisterMBeansAfterReconfigure(); } catch (final Throwable t) { // LOG4J2-716: Android has no java.lang.management LOGGER.error(“Could not reconfigure JMX”, t); } return prev; }这个方法最重要的步骤就是config.start,这才是真正做配置解析的public void start() { LOGGER.debug(“Starting configuration {}”, this); this.setStarting(); pluginManager.collectPlugins(pluginPackages); final PluginManager levelPlugins = new PluginManager(Level.CATEGORY); levelPlugins.collectPlugins(pluginPackages); final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins(); if (plugins != null) { for (final PluginType<?> type : plugins.values()) { try { // Cause the class to be initialized if it isn’t already. Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader()); } catch (final Exception e) { LOGGER.error(“Unable to initialize {} due to {}”, type.getPluginClass().getName(), e.getClass() .getSimpleName(), e); } } } setup(); setupAdvertisement(); doConfigure(); final Set<LoggerConfig> alreadyStarted = new HashSet<LoggerConfig>(); for (final LoggerConfig logger : loggers.values()) { logger.start(); alreadyStarted.add(logger); } for (final Appender appender : appenders.values()) { appender.start(); } if (!alreadyStarted.contains(root)) { // LOG4J2-392 root.start(); // LOG4J2-336 } super.start(); LOGGER.debug(“Started configuration {} OK.”, this); }这里面有如下步骤:获取日志等级的插件初始化初始化Advertiser配置先看一下初始化,也就是setup这个方法,setup是一个需要被复写的方法,我们以XMLConfiguration作为例子,@Override public void setup() { if (rootElement == null) { LOGGER.error(“No logging configuration”); return; } constructHierarchy(rootNode, rootElement); if (status.size() > 0) { for (final Status s : status) { LOGGER.error(“Error processing element {}: {}”, s.name, s.errorType); } return; } rootElement = null; }发现这里面有一个比较重要的方法constructHierarchy,跟进:private void constructHierarchy(final Node node, final Element element) { processAttributes(node, element); final StringBuilder buffer = new StringBuilder(); final NodeList list = element.getChildNodes(); final List<Node> children = node.getChildren(); for (int i = 0; i < list.getLength(); i++) { final org.w3c.dom.Node w3cNode = list.item(i); if (w3cNode instanceof Element) { final Element child = (Element) w3cNode; final String name = getType(child); final PluginType<?> type = pluginManager.getPluginType(name); final Node childNode = new Node(node, name, type); constructHierarchy(childNode, child); if (type == null) { final String value = childNode.getValue(); if (!childNode.hasChildren() && value != null) { node.getAttributes().put(name, value); } else { status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND)); } } else { children.add(childNode); } } else if (w3cNode instanceof Text) { final Text data = (Text) w3cNode; buffer.append(data.getData()); } } final String text = buffer.toString().trim(); if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) { node.setValue(text); } }发现这个就是一个树遍历的过程。诚然,配置文件是以xml的形式给出的,xml的结构就是一个树形结构。回到start方法,跟进doConfiguration:protected void doConfigure() { if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase(“Properties”)) { final Node first = rootNode.getChildren().get(0); createConfiguration(first, null); if (first.getObject() != null) { subst.setVariableResolver((StrLookup) first.getObject()); } } else { final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES); final StrLookup lookup = map == null ? null : new MapLookup(map); subst.setVariableResolver(new Interpolator(lookup, pluginPackages)); } boolean setLoggers = false; boolean setRoot = false; for (final Node child : rootNode.getChildren()) { if (child.getName().equalsIgnoreCase(“Properties”)) { if (tempLookup == subst.getVariableResolver()) { LOGGER.error(“Properties declaration must be the first element in the configuration”); } continue; } createConfiguration(child, null); if (child.getObject() == null) { continue; } if (child.getName().equalsIgnoreCase(“Appenders”)) { appenders = child.getObject(); } else if (child.isInstanceOf(Filter.class)) { addFilter(child.getObject(Filter.class)); } else if (child.getName().equalsIgnoreCase(“Loggers”)) { final Loggers l = child.getObject(); loggers = l.getMap(); setLoggers = true; if (l.getRoot() != null) { root = l.getRoot(); setRoot = true; } } else if (child.getName().equalsIgnoreCase(“CustomLevels”)) { customLevels = child.getObject(CustomLevels.class).getCustomLevels(); } else if (child.isInstanceOf(CustomLevelConfig.class)) { final List<CustomLevelConfig> copy = new ArrayList<CustomLevelConfig>(customLevels); copy.add(child.getObject(CustomLevelConfig.class)); customLevels = copy; } else { LOGGER.error(“Unknown object "{}" of type {} is ignored.”, child.getName(), child.getObject().getClass().getName()); } } if (!setLoggers) { LOGGER.warn(“No Loggers were configured, using default. Is the Loggers element missing?”); setToDefault(); return; } else if (!setRoot) { LOGGER.warn(“No Root logger was configured, creating default ERROR-level Root logger with Console appender”); setToDefault(); // return; // LOG4J2-219: creating default root=ok, but don’t exclude configured Loggers } for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) { final LoggerConfig l = entry.getValue(); for (final AppenderRef ref : l.getAppenderRefs()) { final Appender app = appenders.get(ref.getRef()); if (app != null) { l.addAppender(app, ref.getLevel(), ref.getFilter()); } else { LOGGER.error(“Unable to locate appender {} for logger {}”, ref.getRef(), l.getName()); } } } setParents(); }发现就是对刚刚获取的configuration进行解析,然后塞进正确的地方。回到start方法,可以看到昨晚配置之后就是开启logger和appender了。异步AsyncAppenderlog4j2突出于其他日志的优势,异步日志实现。我们先从日志打印看进去。找到Logger,随便找一个log日志的方法。public void debug(final Marker marker, final Message msg) { logIfEnabled(FQCN, Level.DEBUG, marker, msg, msg != null ? msg.getThrowable() : null); }一路跟进@PerformanceSensitive // NOTE: This is a hot method. Current implementation compiles to 29 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! private void logMessageTrackRecursion(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) { try { incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031 tryLogMessage(fqcn, level, marker, msg, throwable); } finally { decrementRecursionDepth(); } }可以看出这个在打日志之前做了调用次数的记录。跟进tryLogMessage,@PerformanceSensitive // NOTE: This is a hot method. Current implementation compiles to 26 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! private void tryLogMessage(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) { try { logMessage(fqcn, level, marker, msg, throwable); } catch (final Exception e) { // LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger handleLogMessageException(e, fqcn, msg); } }继续跟进:@Override public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message; final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy(); strategy.log(this, getName(), fqcn, marker, level, msg, t); }这里可以看到在实际打日志的时候,会从config中获取打日志的策略,跟踪ReliabilityStrategy的创建,发现默认的实现类为DefaultReliabilityStrategy,跟进看实际打日志的方法@Override public void log(final Supplier<LoggerConfig> reconfigured, final String loggerName, final String fqcn, final Marker marker, final Level level, final Message data, final Throwable t) { loggerConfig.log(loggerName, fqcn, marker, level, data, t); }这里实际打日志的方法居然是交给一个config去实现的。。。感觉有点奇怪。。跟进看看@PerformanceSensitive(“allocation”) public void log(final String loggerName, final String fqcn, final Marker marker, final Level level, final Message data, final Throwable t) { List<Property> props = null; if (!propertiesRequireLookup) { props = properties; } else { if (properties != null) { props = new ArrayList<>(properties.size()); final LogEvent event = Log4jLogEvent.newBuilder() .setMessage(data) .setMarker(marker) .setLevel(level) .setLoggerName(loggerName) .setLoggerFqcn(fqcn) .setThrown(t) .build(); for (int i = 0; i < properties.size(); i++) { final Property prop = properties.get(i); final String value = prop.isValueNeedsLookup() // since LOG4J2-1575 ? config.getStrSubstitutor().replace(event, prop.getValue()) // : prop.getValue(); props.add(Property.createProperty(prop.getName(), value)); } } } final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t); try { log(logEvent, LoggerConfigPredicate.ALL); } finally { // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) ReusableLogEventFactory.release(logEvent); } }可以清楚的看到try之前是在创建LogEvent,try里面做的才是真正的log(好tm累),一路跟进。private void processLogEvent(final LogEvent event, LoggerConfigPredicate predicate) { event.setIncludeLocation(isIncludeLocation()); if (predicate.allow(this)) { callAppenders(event); } logParent(event, predicate); }接下来就是callAppender了,我们直接开始看AsyncAppender的append方法:/** * Actual writing occurs here. * * @param logEvent The LogEvent. / @Override public void append(final LogEvent logEvent) { if (!isStarted()) { throw new IllegalStateException(“AsyncAppender " + getName() + " is not active”); } final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation); InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage()); if (!transfer(memento)) { if (blocking) { if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock AsyncQueueFullMessageUtil.logWarningToStatusLogger(); logMessageInCurrentThread(logEvent); } else { // delegate to the event router (which may discard, enqueue and block, or log in current thread) final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel()); route.logMessage(this, memento); } } else { error(“Appender " + getName() + " is unable to write primary appenders. queue is full”); logToErrorAppenderIfNecessary(false, memento); } } }这里主要的步骤就是:生成logEvent将logEvent放入BlockingQueue,就是transfer方法如果BlockingQueue满了则启用相应的策略同样的,这里也有一个线程用来做异步消费的事情private class AsyncThread extends Log4jThread { private volatile boolean shutdown = false; private final List<AppenderControl> appenders; private final BlockingQueue<LogEvent> queue; public AsyncThread(final List<AppenderControl> appenders, final BlockingQueue<LogEvent> queue) { super(“AsyncAppender-” + THREAD_SEQUENCE.getAndIncrement()); this.appenders = appenders; this.queue = queue; setDaemon(true); } @Override public void run() { while (!shutdown) { LogEvent event; try { event = queue.take(); if (event == SHUTDOWN_LOG_EVENT) { shutdown = true; continue; } } catch (final InterruptedException ex) { break; // LOG4J2-830 } event.setEndOfBatch(queue.isEmpty()); final boolean success = callAppenders(event); if (!success && errorAppender != null) { try { errorAppender.callAppender(event); } catch (final Exception ex) { // Silently accept the error. } } } // Process any remaining items in the queue. LOGGER.trace(“AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.”, queue.size()); int count = 0; int ignored = 0; while (!queue.isEmpty()) { try { final LogEvent event = queue.take(); if (event instanceof Log4jLogEvent) { final Log4jLogEvent logEvent = (Log4jLogEvent) event; logEvent.setEndOfBatch(queue.isEmpty()); callAppenders(logEvent); count++; } else { ignored++; LOGGER.trace(“Ignoring event of class {}”, event.getClass().getName()); } } catch (final InterruptedException ex) { // May have been interrupted to shut down. // Here we ignore interrupts and try to process all remaining events. } } LOGGER.trace(“AsyncAppender.AsyncThread stopped. Queue has {} events remaining. " + “Processed {} and ignored {} events since shutdown started.”, queue.size(), count, ignored); } /* * Calls {@link AppenderControl#callAppender(LogEvent) callAppender} on all registered {@code AppenderControl} * objects, and returns {@code true} if at least one appender call was successful, {@code false} otherwise. Any * exceptions are silently ignored. * * @param event the event to forward to the registered appenders * @return {@code true} if at least one appender call succeeded, {@code false} otherwise / boolean callAppenders(final LogEvent event) { boolean success = false; for (final AppenderControl control : appenders) { try { control.callAppender(event); success = true; } catch (final Exception ex) { // If no appender is successful the error appender will get it. } } return success; } public void shutdown() { shutdown = true; if (queue.isEmpty()) { queue.offer(SHUTDOWN_LOG_EVENT); } if (getState() == State.TIMED_WAITING || getState() == State.WAITING) { this.interrupt(); // LOG4J2-1422: if underlying appender is stuck in wait/sleep/join/park call } } }直接看run方法:阻塞获取logEvent将logEvent分发出去如果线程要退出了,将blockingQueue里面的event消费完在退出。AsyncLogger直接从AsyncLogger的logMessage看进去:public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) { if (loggerDisruptor.isUseThreadLocals()) { logWithThreadLocalTranslator(fqcn, level, marker, message, thrown); } else { // LOG4J2-1172: avoid storing non-JDK classes in ThreadLocals to avoid memory leaks in web apps logWithVarargTranslator(fqcn, level, marker, message, thrown); } }跟进logWithThreadLocalTranslator,private void logWithThreadLocalTranslator(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) { // Implementation note: this method is tuned for performance. MODIFY WITH CARE! final RingBufferLogEventTranslator translator = getCachedTranslator(); initTranslator(translator, fqcn, level, marker, message, thrown); initTranslatorThreadValues(translator); publish(translator); }这里的逻辑很简单,就是将日志相关的信息转换成RingBufferLogEvent(RingBuffer是Disruptor的无所队列),然后将其发布到RingBuffer中。发布到RingBuffer中,那肯定也有消费逻辑。这时候有两种方式可以找到这个消费的逻辑。找disruptor被使用的地方,然后查看,但是这样做会很容易迷惑按照Log4j2的尿性,这种Logger都有对应的start方法,我们可以从start方法入手寻找在start方法中,我们找到了一段代码:final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler()}; disruptor.handleEventsWith(handlers);直接看看这个RingBufferLogEventHandler的实现:public class RingBufferLogEventHandler implements SequenceReportingEventHandler<RingBufferLogEvent>, LifecycleAware { private static final int NOTIFY_PROGRESS_THRESHOLD = 50; private Sequence sequenceCallback; private int counter; private long threadId = -1; @Override public void setSequenceCallback(final Sequence sequenceCallback) { this.sequenceCallback = sequenceCallback; } @Override public void onEvent(final RingBufferLogEvent event, final long sequence, final boolean endOfBatch) throws Exception { event.execute(endOfBatch); event.clear(); // notify the BatchEventProcessor that the sequence has progressed. // Without this callback the sequence would not be progressed // until the batch has completely finished. if (++counter > NOTIFY_PROGRESS_THRESHOLD) { sequenceCallback.set(sequence); counter = 0; } } /* * Returns the thread ID of the background consumer thread, or {@code -1} if the background thread has not started * yet. * @return the thread ID of the background consumer thread, or {@code -1} / public long getThreadId() { return threadId; } @Override public void onStart() { threadId = Thread.currentThread().getId(); } @Override public void onShutdown() { }}顺着接口找上去,发现一个接口:/* * Callback interface to be implemented for processing events as they become available in the {@link RingBuffer} * * @param <T> event implementation storing the data for sharing during exchange or parallel coordination of an event. * @see BatchEventProcessor#setExceptionHandler(ExceptionHandler) if you want to handle exceptions propagated out of the handler. /public interface EventHandler<T>{ /* * Called when a publisher has published an event to the {@link RingBuffer} * * @param event published to the {@link RingBuffer} * @param sequence of the event being processed * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer} * @throws Exception if the EventHandler would like the exception handled further up the chain. / void onEvent(T event, long sequence, boolean endOfBatch) throws Exception;}通过注释可以发现,这个onEvent就是处理逻辑,回到RingBufferLogEventHandler的onEvent方法,发现里面有一个execute方法,跟进:public void execute(final boolean endOfBatch) { this.endOfBatch = endOfBatch; asyncLogger.actualAsyncLog(this); }这个方法就是实际打日志了,AsyncLogger看起来还是比较简单的,只是使用了一个Disruptor。插件化之前在很多代码里面都可以看到final PluginManager manager = new PluginManager(CATEGORY);manager.collectPlugins(pluginPackages);其实整个log4j2为了获得更好的扩展性,将自己的很多组件都做成了插件,然后在配置的时候去加载plugin。跟进collectPlugins。 public void collectPlugins(final List<String> packages) { final String categoryLowerCase = category.toLowerCase(); final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>(); // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader(); if (builtInPlugins.isEmpty()) { // If we didn’t find any plugins above, someone must have messed with the log4j-core.jar. // Search the standard package in the hopes we can find our core plugins. builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES); } mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase)); // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) { mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase)); } // Next iterate any packages passed to the static addPackage method. for (final String pkg : PACKAGES) { mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); } // Finally iterate any packages provided in the configuration (note these can be changed at runtime). if (packages != null) { for (final String pkg : packages) { mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); } } LOGGER.debug(“PluginManager ‘{}’ found {} plugins”, category, newPlugins.size()); plugins = newPlugins; }处理逻辑如下:从Log4j2Plugin.dat中加载所有的内置的plugin然后将OSGi Bundles中的Log4j2Plugin.dat中的plugin加载进来再加载传入的package路径中的plugin最后加载配置中的plugin逻辑还是比较简单的,但是我在看源码的时候发现了一个很有意思的东西,就是在加载log4j2 core插件的时候,也就是PluginRegistry.getInstance().loadFromMainClassLoader()这个方法,跟进到decodeCacheFiles:private Map<String, List<PluginType<?>>> decodeCacheFiles(final ClassLoader loader) { final long startTime = System.nanoTime(); final PluginCache cache = new PluginCache(); try { final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE); if (resources == null) { LOGGER.info(“Plugin preloads not available from class loader {}”, loader); } else { cache.loadCacheFiles(resources); } } catch (final IOException ioe) { LOGGER.warn(“Unable to preload plugins”, ioe); } final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>(); int pluginCount = 0; for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) { final String categoryLowerCase = outer.getKey(); final List<PluginType<?>> types = new ArrayList<>(outer.getValue().size()); newPluginsByCategory.put(categoryLowerCase, types); for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) { final PluginEntry entry = inner.getValue(); final String className = entry.getClassName(); try { final Class<?> clazz = loader.loadClass(className); final PluginType<?> type = new PluginType<>(entry, clazz, entry.getName()); types.add(type); ++pluginCount; } catch (final ClassNotFoundException e) { LOGGER.info(“Plugin [{}] could not be loaded due to missing classes.”, className, e); } catch (final LinkageError e) { LOGGER.info(“Plugin [{}] could not be loaded due to linkage error.”, className, e); } } } final long endTime = System.nanoTime(); final DecimalFormat numFormat = new DecimalFormat("#0.000000”); final double seconds = (endTime - startTime) * 1e-9; LOGGER.debug(“Took {} seconds to load {} plugins from {}”, numFormat.format(seconds), pluginCount, loader); return newPluginsByCategory; }可以发现加载时候是从一个文件(PLUGIN_CACHE_FILE)获取所有要获取的plugin。看到这里的时候我有一个疑惑就是,为什么不用反射的方式直接去扫描,而是要从文件中加载进来,而且文件是写死的,很不容易扩展啊。然后我找了一下PLUGIN_CACHE_FILE这个静态变量的用处,发现了PluginProcessor这个类,这里用到了注解处理器。/* * Annotation processor for pre-scanning Log4j 2 plugins. /@SupportedAnnotationTypes(“org.apache.logging.log4j.core.config.plugins.")public class PluginProcessor extends AbstractProcessor { // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing /** * The location of the plugin cache data file. This file is written to by this processor, and read from by * {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}. */ public static final String PLUGIN_CACHE_FILE = “META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat”; private final PluginCache pluginCache = new PluginCache(); @Override public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { System.out.println(“Processing annotations”); try { final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class); if (elements.isEmpty()) { System.out.println(“No elements to process”); return false; } collectPlugins(elements); writeCacheFile(elements.toArray(new Element[elements.size()])); System.out.println(“Annotations processed”); return true; } catch (final IOException e) { e.printStackTrace(); error(e.getMessage()); return false; } catch (final Exception ex) { ex.printStackTrace(); error(ex.getMessage()); return false; } }}(不太重要的方法省略)我们可以看到在process方法中,PluginProcessor会先收集所有的Plugin,然后在写入文件。这样做的好处就是可以省去反射时候的开销。然后我又看了一下Plugin这个注解,发现它的RetentionPolicy是RUNTIME,一般来说PluginProcessor是搭配RetentionPolicy.SOURCE,CLASS使用的,而且既然你把自己的Plugin扫描之后写在文件中了,RetentionPolicy就没有必要是RUNTIME了吧,这个是一个很奇怪的地方。小结总算是把Log4j2的代码看完了,发现它的设计理念很值得借鉴,为了灵活性,所有的东西都设计成插件式。互联网技术日益发展,各种中间件层出不穷,而作为工程师的我们更需要做的是去思考代码与代码之间的关系,毫无疑问的是,解耦是最具有美感的关系。本文作者:netflix阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 10, 2019 · 19 min · jiezi

spring boot 整合mybatis 无法输出sql的问题

使用spring boot整合mybatis,测试功能的时候,遇到到了sql问题,想要从日志上看哪里错了,但是怎么都无法输出执行的sql,我使用的是log4j2,百度了一下,很多博客都说,加上下面的日志配置: <logger name=“java.sql.Statement” level=“debug”/> <logger name=“java.sql.PreparedStatement” level=“debug”/> <logger name=“java.sql.Connection” level=“debug”/> <logger name=“ResultSet” level=“debug”/>经实际测试,没什么用。只好去官网找解决方案,在mybatis日志上看到,如果存在内置日志,就是用内置的日志,自己配置的日志就忽略了。MyBatis 内置日志工厂基于运行时自省机制选择合适的日志工具。它会使用第一个查找得到的工具(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。 不少应用服务器(如 Tomcat 和 WebShpere)的类路径中已经包含 Commons Logging,所以在这种配置环境下的 MyBatis 会把它作为日志工具,记住这点非常重要。这将意味着,在诸如 WebSphere 的环境中,它提供了 Commons Logging 的私有实现,你的 Log4J 配置将被忽略。MyBatis 将你的 Log4J 配置忽略掉是相当令人郁闷的(事实上,正是因为在这种配置环境下,MyBatis 才会选择使用 Commons Logging 而不是 Log4J)。如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志工具,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择别的日志工具。<configuration> <settings> … <setting name=“logImpl” value=“LOG4J”/> … </settings></configuration> logImpl 可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了接口 org.apache.ibatis.logging.Log 的,且构造方法是以字符串为参数的类的完全限定名。(译者注:可以参考org.apache.ibatis.logging.slf4j.Slf4jImpl.java的实现)所以我就创建了一个mybatis-config文件:<?xml version=“1.0” encoding=“UTF-8”?><!DOCTYPE configuration PUBLIC “-//mybatis.org//DTD Config 3.0//EN” “http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <settings> <setting name=“logImpl” value=“LOG4J2”/> </settings></configuration>配置mybatis:mybatis: type-aliases-package: com.demo mapper-locations: classpath:mapper/.xml configuration: cache-enabled: true lazy-loading-enabled: true multiple-result-sets-enabled: true default-executor-type: simple default-statement-timeout: 25000 config-location: classpath:mybatis-config.xml然而启动的时候报错:aused by: java.lang.IllegalStateException: Property ‘configuration’ and ‘configLocation’ can not specified with together at org.springframework.util.Assert.state(Assert.java:73) ~[spring-core-5.0.10.RELEASE.jar:5.0.10.RELEASE] at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:377) ~[mybatis-spring-1.3.2.jar:1.3.2] at org.mybatis.spring.SqlSessionFactoryBean.getObject(SqlSessionFactoryBean.java:547) ~[mybatis-spring-1.3.2.jar:1.3.2] at org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.sqlSessionFactory(MybatisAutoConfiguration.java:153) ~[mybatis-spring-boot-autoconfigure-1.3.2.jar:1.3.2] at org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration$$EnhancerBySpringCGLIB$$8eb42bd5.CGLIB$sqlSessionFactory$0(<generated>) ~[mybatis-spring-boot-autoconfigure-1.3.2.jar:1.3.2] at org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration$$EnhancerBySpringCGLIB$$8eb42bd5$$FastClassBySpringCGLIB$$1132516e.invoke(<generated>) ~[mybatis-spring-boot-autoconfigure-1.3.2.jar:1.3.2] 报错信息说configuration和configLocation不能同时存在,所以我就想两个功能一定是相同的,只不过配置方式不一样,一个是xml,一个是spring boot的风格,所以我就删掉了config-location,在configuration下面找到了一个log-impl,加上这个配置之后,问题就解决了,如下:mybatis: type-aliases-package: com.demo mapper-locations: classpath:mapper/.xml configuration: cache-enabled: true lazy-loading-enabled: true multiple-result-sets-enabled: true default-executor-type: simple default-statement-timeout: 25000 log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl输出日志: ==> Preparing: select prodid,prodname,isgroupinsur,isdtbprod from bx_product where prodid in ( 1 , 2 , 3 ) ==> Parameters: <== Total: 0只要加这个配置就能解决问题了。 ...

February 28, 2019 · 1 min · jiezi