从图中不难看出,在线程数为 2~16 之间,混合使用同步和异步的logger来打印日志,性能是最好的
1、选择Log4j2的理由是啥,为啥不用spring boot 默认的?
优化 | 说明 |
---|---|
执行速度 | Log4j 2.x 相对于 Log4j 1.x 和 Logback来说,具有更快的执行速度。一方面由于 重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上10倍。比如内部的消息队列采用了ArrayBlockingQueue,相对于原始的ArrayList和锁操作来说,管程类的消息队列拥有更好地性能。同时所需的内存更加少。这是因为Log4j 2.x 采用占位符的形式打印日志(类似于Slf4j门面日志的形式),会先判断一下日志的等级,然后再拼接要打印的内容。另一方面由于Log4j 2.x 充分利用Java 5的并发特性(主要是使用了一些concurrent包下锁),使得性能得到一定的改善,而Log4j 1.x和Logback很多地方还是用的重锁。 |
异步性能 | Asynchronous Loggers是Log4j2新增的日志器,异步日志器在其内部实现采用了LMAX Disruptor(一个无锁的线程间通信库)技术,Disruptor主要通过环形数组结构、元素位置定位和精巧的无锁设计(CAS)实现了在高并发情形下的高性能。而且Log4j 2.x中Asynchronous Appenders作为Asynchronous Loggers工作的一部分,效果进行了增强。每次写入磁盘时,都会进行flush操作,效果和配置“immediateFlush=true”一样。该异步Appender内部采用ArrayBlockingQueue的方式。RandomAccessFileAppender采用ByteBuffer+RandomAccessFile替代了BufferedOutputStream,官方给出的测试数据是它将速度提升了20-200%。 |
自动加载配置文件 | Log4j 2.x 和Logback都新增了自动加载日志配置文件的功能,又与Logback不同,配置发生改变时不会丢失任何日志事件。当Log4j 2.x中配置发生改变时,如果还有日志事件尚未处理,Log4j 2会继续处理,当处理完成后,Logger会重新指向新配置的LoggerConfig对象,并且删除无用的对象。 |
死锁问题的解决 | 在Log4j 1.x中同步写日志的时候,在高并发情况下出现死锁导致cpu使用率异常飙升。其中的原因是当一个进程写日志的时候需要获取到Logger和Appender。org.apache.log4j.Logger类继承于org.apache.log4j.Category、Appender继承于org.apache.log4j.AppenderSkeleton。通过Log4j 1.x中Category源码和Appender源码可以知道,当多线程并发时,可能会因为相互持有Logger和Appender发生死锁。 而在log4j 2.x中充分利用Java5的并发支持,并且以最低级别执行锁定。 |
2、Maven 依赖 pom.xml配置
去掉默认日志,加载别的日志,spring boot提供log4j2的解决方案,如下配置
<!-- 包含 mvc,aop 等jar资源 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <!-- 切换log4j2日志读取 --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>
然后添加如下两个依赖
<!-- 配置 log4j2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!-- 加上这个才能辨认到log4j2.yml文件 --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> </dependency>
3、配置文件添加log4j2.yml
文件存放resource目录下
# 共有8个级别,按照从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。 Configuration: status: warn monitorInterval: 30 Properties: # 定义全局变量 Property: # 缺省配置(用于开发环境)。其他环境需要在VM参数中指定,如下: #测试:-Dlog.level.console=warn -Dlog.level.xjj=trace #生产:-Dlog.level.console=warn -Dlog.level.xjj=info - name: log.level.console value: info - name: log.path value: log - name: project.name value: opendoc - name: log.pattern value: "%d{yyyy-MM-dd HH:mm:ss.SSS} -%5p ${PID:-} [%15.15t] %-30.30C{1.} : %m%n" Appenders: Console: #输出到控制台 name: CONSOLE target: SYSTEM_OUT PatternLayout: pattern: ${log.pattern} # 启动日志 RollingFile: - name: ROLLING_FILE fileName: ${log.path}/${project.name}.log filePattern: "${log.path}/historyRunLog/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz" PatternLayout: pattern: ${log.pattern} Filters: # 一定要先去除不接受的日志级别,然后获取需要接受的日志级别 ThresholdFilter: - level: error onMatch: DENY onMismatch: NEUTRAL - level: info onMatch: ACCEPT onMismatch: DENY Policies: TimeBasedTriggeringPolicy: # 按天分类 modulate: true interval: 1 DefaultRolloverStrategy: # 文件最多100个 max: 100 # 平台日志 - name: PLATFORM_ROLLING_FILE ignoreExceptions: false fileName: ${log.path}/platform/${project.name}_platform.log filePattern: "${log.path}/platform/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz" PatternLayout: pattern: ${log.pattern} Policies: TimeBasedTriggeringPolicy: # 按天分类 modulate: true interval: 1 DefaultRolloverStrategy: # 文件最多100个 max: 100 # 业务日志 - name: BUSSINESS_ROLLING_FILE ignoreExceptions: false fileName: ${log.path}/bussiness/${project.name}_bussiness.log filePattern: "${log.path}/bussiness/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz" PatternLayout: pattern: ${log.pattern} Policies: TimeBasedTriggeringPolicy: # 按天分类 modulate: true interval: 1 DefaultRolloverStrategy: # 文件最多100个 max: 100 # 错误日志 - name: EXCEPTION_ROLLING_FILE ignoreExceptions: false fileName: ${log.path}/exception/${project.name}_exception.log filePattern: "${log.path}/exception/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz" ThresholdFilter: level: error onMatch: ACCEPT onMismatch: DENY PatternLayout: pattern: ${log.pattern} Policies: TimeBasedTriggeringPolicy: # 按天分类 modulate: true interval: 1 DefaultRolloverStrategy: # 文件最多100个 max: 100 # DB 日志 - name: DB_ROLLING_FILE ignoreExceptions: false fileName: ${log.path}/db/${project.name}_db.log filePattern: "${log.path}/db/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz" PatternLayout: pattern: ${log.pattern} Policies: TimeBasedTriggeringPolicy: # 按天分类 modulate: true interval: 1 DefaultRolloverStrategy: # 文件最多100个 max: 100 Loggers: Root: level: info AppenderRef: - ref: CONSOLE - ref: ROLLING_FILE - ref: EXCEPTION_ROLLING_FILE Logger: - name: platform level: info additivity: false AppenderRef: - ref: CONSOLE - ref: PLATFORM_ROLLING_FILE - name: bussiness level: info additivity: false AppenderRef: - ref: BUSSINESS_ROLLING_FILE - name: exception level: debug additivity: true AppenderRef: - ref: EXCEPTION_ROLLING_FILE - name: db level: info additivity: false AppenderRef: - ref: DB_ROLLING_FILE # 监听具体包下面的日志 # Logger: # 为com.xjj包配置特殊的Log级别,方便调试 # - name: com.xjj # additivity: false # level: ${sys:log.level.xjj} # AppenderRef: # - ref: CONSOLE # - ref: ROLLING_FILE
4、引入配置文件
在application.yml引入
logging: config: classpath:log4j2.yml
5、不同日志工具类util编辑
由于配置了4个文件存放不同日志,分别为平台日志(${project.name}_platform.log)、 业务日志(${project.name}_bussiness.log)、错误日志(${project.name}_exception.log)、DB 日志(${project.name}_db.log),文件夹外面为运行日志,不同文件日志级别不一样,因此程序员在开发时候需要注意引入不同日志,也就是说开发出现的日志,程序员可以进行分类,分别调用公共方法即可。公共类编辑如下;
package com.open.util; /** * 本地日志枚举 * @author Administrator * */ public enum LogEnum { BUSSINESS("bussiness"), PLATFORM("platform"), DB("db"), EXCEPTION("exception"), ; private String category; LogEnum(String category) { this.category = category; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } }
package com.open.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 本地日志参考类 * @author Administrator * */ public class LogUtils { /** * 获取业务日志logger * * @return */ public static Logger getBussinessLogger() { return LoggerFactory.getLogger(LogEnum.BUSSINESS.getCategory()); } /** * 获取平台日志logger * * @return */ public static Logger getPlatformLogger() { return LoggerFactory.getLogger(LogEnum.PLATFORM.getCategory()); } /** * 获取数据库日志logger * * @return */ public static Logger getDBLogger() { return LoggerFactory.getLogger(LogEnum.DB.getCategory()); } /** * 获取异常日志logger * * @return */ public static Logger getExceptionLogger() { return LoggerFactory.getLogger(LogEnum.EXCEPTION.getCategory()); } }
具体调用如下:
@GetMapping("/helloworld") public String helloworld() throws Exception{ Logger log = LogUtils.getExceptionLogger(); Logger log1 = LogUtils.getBussinessLogger(); Logger log2 = LogUtils.getDBLogger(); userService.queryUser(); log.error("getExceptionLogger===日志测试"); log1.info("getBussinessLogger===日志测试"); log2.debug("getDBLogger===日志测试"); return "helloworld"; }
生成日志目录如下