|
@@ -0,0 +1,546 @@
|
|
|
+参考:
|
|
|
+* [工作3年啦,你竟然连Java日志体系都没闹懂?说不过去~ ](https://mp.weixin.qq.com/s/8gTsdstUqHdyjLvRkv7nqw)
|
|
|
+* https://www.cnblogs.com/cb0327/p/5759441.html
|
|
|
+
|
|
|
+## logger 使用步骤
|
|
|
+
|
|
|
+1. 引入依赖
|
|
|
+```xml
|
|
|
+<project>
|
|
|
+ <properties>
|
|
|
+ <slf4j.version>1.7.25</slf4j.version>
|
|
|
+ <logback.version>1.1.11</logback.version>
|
|
|
+ </properties>
|
|
|
+ <dependencies>
|
|
|
+ <!-- slf4j -->
|
|
|
+ <dependency>
|
|
|
+ <groupId>org.slf4j</groupId>
|
|
|
+ <artifactId>slf4j-api</artifactId>
|
|
|
+ <version>${slf4j.version}</version>
|
|
|
+ </dependency>
|
|
|
+
|
|
|
+ <!-- logback -->
|
|
|
+ <dependency>
|
|
|
+ <groupId>ch.qos.logback</groupId>
|
|
|
+ <artifactId>logback-core</artifactId>
|
|
|
+ <version>${logback.version}</version>
|
|
|
+ </dependency>
|
|
|
+ <dependency>
|
|
|
+ <groupId>ch.qos.logback</groupId>
|
|
|
+ <artifactId>logback-classic</artifactId>
|
|
|
+ <version>${logback.version}</version>
|
|
|
+ </dependency>
|
|
|
+ <dependency>
|
|
|
+ <groupId>ch.qos.logback</groupId>
|
|
|
+ <artifactId>logback-access</artifactId>
|
|
|
+ <version>${logback.version}</version>
|
|
|
+ </dependency>
|
|
|
+ </dependencies>
|
|
|
+</project>
|
|
|
+```
|
|
|
+
|
|
|
+2. 配置 logback.xml
|
|
|
+> 实际项目中,可修改下面模板使用
|
|
|
+```xml
|
|
|
+<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+<configuration>
|
|
|
+
|
|
|
+ <!-- region 自定义属性 -->
|
|
|
+
|
|
|
+ <!-- 定义日志文件的存储地址 -->
|
|
|
+ <property name="LOG_DIR" value="/data/logs/logGuide/"/>
|
|
|
+ <!-- 定义日志最大的历史 30天 -->
|
|
|
+ <property name="maxHistory" value="30"/>
|
|
|
+ <!-- 定义日志文件大小 -->
|
|
|
+ <property name="maxFileSize" value="20MB"/>
|
|
|
+
|
|
|
+ <!--
|
|
|
+ %p:输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
|
|
|
+ %r:输出自应用启动到输出该日志讯息所耗费的毫秒数
|
|
|
+ %t:输出产生该日志事件的线程名
|
|
|
+ %f:输出日志讯息所属的类别的类别名
|
|
|
+ %c:输出日志讯息所属的类的全名
|
|
|
+ %d:输出日志时间点的日期或时间,指定格式的方式: %d{yyyy-MM-dd HH:mm:ss}
|
|
|
+ %l:输出日志事件的发生位置,即输出日志讯息的语句在他所在类别的第几行。
|
|
|
+ %m:输出代码中指定的讯息,如log(message)中的message
|
|
|
+ %n:输出一个换行符号
|
|
|
+ -->
|
|
|
+ <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
|
|
|
+ <property name="pattern" value="%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n"/>
|
|
|
+
|
|
|
+ <!--endregion-->
|
|
|
+
|
|
|
+ <!--
|
|
|
+ Appender: 设置日志信息的去向,常用的有以下几个
|
|
|
+ ch.qos.logback.core.ConsoleAppender (控制台)
|
|
|
+ ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
|
|
|
+ ch.qos.logback.core.FileAppender (文件)
|
|
|
+ -->
|
|
|
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
|
+ <!-- 字符串System.out(默认)或者System.err -->
|
|
|
+ <target>System.out</target>
|
|
|
+ <!-- 对记录事件进行格式化 -->
|
|
|
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
|
|
+ <pattern>${pattern}</pattern>
|
|
|
+ </encoder>
|
|
|
+ </appender>
|
|
|
+
|
|
|
+ <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
|
+ <!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建 -->
|
|
|
+ <file>${LOG_DIR}/info.log</file>
|
|
|
+ <!-- 当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。属性class定义具体的滚动策略类 -->
|
|
|
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
|
+ <!-- 必要节点,包含文件名及"%d"转换符,"%d"可以包含一个java.text.SimpleDateFormat指定的时间格式,默认格式是 yyyy-MM-dd -->
|
|
|
+ <fileNamePattern>${LOG_DIR}/info_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
|
|
|
+ <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
|
|
+ <maxFileSize>${maxFileSize}</maxFileSize>
|
|
|
+ </timeBasedFileNamingAndTriggeringPolicy>
|
|
|
+ <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,如果是6,则只保存最近6个月的文件,删除之前的旧文件 -->
|
|
|
+ <maxHistory>${maxHistory}</maxHistory>
|
|
|
+ </rollingPolicy>
|
|
|
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
|
|
+ <pattern>${pattern}</pattern>
|
|
|
+ </encoder>
|
|
|
+ <!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
|
|
|
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
|
|
|
+ <level>INFO</level>
|
|
|
+ <!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
|
|
|
+ <onMatch>ACCEPT</onMatch>
|
|
|
+ <!-- 用于配置不符合过滤条件的操作 DENY:日志将立即被抛弃不再经过其他过滤器 -->
|
|
|
+ <onMismatch>DENY</onMismatch>
|
|
|
+ </filter>
|
|
|
+ </appender>
|
|
|
+
|
|
|
+ <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
|
+ <file>${LOG_DIR}/error.log</file>
|
|
|
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
|
+ <fileNamePattern>${LOG_DIR}/error_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
|
|
|
+ <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
|
|
+ <maxFileSize>${maxFileSize}</maxFileSize>
|
|
|
+ </timeBasedFileNamingAndTriggeringPolicy>
|
|
|
+ <maxHistory>${maxHistory}</maxHistory>
|
|
|
+ </rollingPolicy>
|
|
|
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
|
|
+ <pattern>${pattern}</pattern>
|
|
|
+ </encoder>
|
|
|
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
|
|
|
+ <level>ERROR</level>
|
|
|
+ <onMatch>ACCEPT</onMatch>
|
|
|
+ <onMismatch>DENY</onMismatch>
|
|
|
+ </filter>
|
|
|
+ </appender>
|
|
|
+
|
|
|
+ <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
|
+ <file>${LOG_DIR}/debug.log</file>
|
|
|
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
|
+ <FileNamePattern>${LOG_DIR}/debug.%d{yyyy-MM-dd}.log
|
|
|
+ </FileNamePattern>
|
|
|
+ </rollingPolicy>
|
|
|
+ <encoder>
|
|
|
+ <Pattern>${pattern}</Pattern>
|
|
|
+ </encoder>
|
|
|
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
|
|
|
+ <level>DEBUG</level>
|
|
|
+ <onMatch>ACCEPT</onMatch>
|
|
|
+ <onMismatch>DENY</onMismatch>
|
|
|
+ </filter>
|
|
|
+ </appender>
|
|
|
+
|
|
|
+ <!--
|
|
|
+ 用来设置某一个包或者具体的某一个类的日志打印级别并指定appender
|
|
|
+ <logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
|
|
|
+ name:
|
|
|
+ 用来指定受此logger约束的某一个包或者具体的某一个类。
|
|
|
+ level:
|
|
|
+ 用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
|
|
|
+ 如果未设置此属性,那么当前logger将会继承上级的级别。
|
|
|
+ additivity:
|
|
|
+ 是否向上级logger传递打印信息。默认是true。
|
|
|
+ <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
|
|
|
+ -->
|
|
|
+
|
|
|
+ <!--
|
|
|
+ <logger name="liu.log.guide.util" additivity="false">
|
|
|
+ <level value="info"/>
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ <appender-ref ref="DEBUG"/>
|
|
|
+ <appender-ref ref="INFO"/>
|
|
|
+ <appender-ref ref="ERROR"/>
|
|
|
+ </logger>
|
|
|
+
|
|
|
+ <logger name="liu.log.guide.util.file" additivity="false">
|
|
|
+ <level value="info"/>
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ </logger>
|
|
|
+ -->
|
|
|
+ <logger name="liu.log.guide">
|
|
|
+ <appender-ref ref="ERROR"/>
|
|
|
+ </logger>
|
|
|
+
|
|
|
+ <!--
|
|
|
+ 也是<logger>元素,但是它是根logger。默认debug
|
|
|
+ level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
|
|
|
+ <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。
|
|
|
+ -->
|
|
|
+ <root level="INFO">
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ <appender-ref ref="DEBUG"/>
|
|
|
+ <appender-ref ref="INFO"/>
|
|
|
+ <appender-ref ref="ERROR"/>
|
|
|
+ </root>
|
|
|
+
|
|
|
+
|
|
|
+</configuration>
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+3. 打印日志
|
|
|
+```java
|
|
|
+public class LogGuideApplication {
|
|
|
+
|
|
|
+ private static Logger logger = LoggerFactory.getLogger(LogGuideApplication.class);
|
|
|
+
|
|
|
+ public static void main(String[] args) {
|
|
|
+ logger.trace("trace ...");
|
|
|
+ logger.debug("debug ...");
|
|
|
+ logger.info("info ...");
|
|
|
+ logger.warn("warn ...");
|
|
|
+ logger.error("error ...");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## logger 配置
|
|
|
+logback.xml 的结构:
|
|
|
+```
|
|
|
+configuration
|
|
|
+ |- property
|
|
|
+ |- appender
|
|
|
+ |- logger
|
|
|
+ |- root
|
|
|
+```
|
|
|
+
|
|
|
+**第1种情况:只配置root**
|
|
|
+```xml
|
|
|
+<configuration>
|
|
|
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
|
+ <encoder>
|
|
|
+ <pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
|
|
|
+ </encoder>
|
|
|
+ </appender>
|
|
|
+ <!-- 将 >= INFO 级别的日志信息,交给 STDOUT 处理-->
|
|
|
+ <root level="INFO">
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ </root>
|
|
|
+</configuration>
|
|
|
+```
|
|
|
+结果:控制台只会打印大于等于 INFO 级别的日志信息
|
|
|
+
|
|
|
+**第2种情况:配置一个logger**
|
|
|
+
|
|
|
+```xml
|
|
|
+<configuration>
|
|
|
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
|
+ <encoder>
|
|
|
+ <pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
|
|
|
+ </encoder>
|
|
|
+ </appender>
|
|
|
+
|
|
|
+ <logger name="liu.log.guide">
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ </logger>
|
|
|
+
|
|
|
+ <!-- 将 >= INFO 级别的日志信息,交给 STDOUT 处理-->
|
|
|
+ <root level="INFO">
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ </root>
|
|
|
+</configuration>
|
|
|
+```
|
|
|
+
|
|
|
+logger 有3个属性:
|
|
|
+* name 日志范围,包名或者类名。root没有name属性,默认根目录。日志范围会优先匹配最精确的包名。
|
|
|
+比如说上面这个例子,在没有name="liu.log.guide"的logger之前,是由root记录所有类包日志的。有了logger之后,包"liu.log.guide"里的日志会交给最精确匹配的logger来记录。
|
|
|
+* level 日志级别,默认继承上级的日志级别。用来筛选不同日志级别的日志信息。
|
|
|
+* additivity 是否将日志信息传递给上级,默认为true
|
|
|
+
|
|
|
+**上面的情况会发生如下过程:**
|
|
|
+1. logger 默认继承了上级root的level,所以会记录所有日志级别level大于等于INFO的日志信息。
|
|
|
+2. logger 将筛选后的日志信息交给appender处理。除此之外,additivity属性默认true,所以还会将日志信息传递给上级logger,即root
|
|
|
+3. root 收到下级logger传递来的日志信息后,将其全部交给自己的appender处理
|
|
|
+4. appender 收到日志信息后,如果设置了levelFilter日志级别过滤器,就会只将过滤后的日志信息输出到目的地。
|
|
|
+> logger上下级关系使有name表示的包或类决定的。比如 name="liu.log" 就是 name="liu.log.guide" 的上级
|
|
|
+
|
|
|
+所以最明显的结果就是:日志级别大于等于INFO的日志都会被记录2次,这2次分别来自于logger和 root
|
|
|
+
|
|
|
+**扩展**
|
|
|
+如果将上面的logback.xml改为如下这样,会发生什么呢?
|
|
|
+```xml
|
|
|
+<configuration>
|
|
|
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
|
+ <encoder>
|
|
|
+ <pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
|
|
|
+ </encoder>
|
|
|
+ </appender>
|
|
|
+
|
|
|
+ <logger name="liu.log.guide" level="INFO">
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ </logger>
|
|
|
+ <root level="WARN">
|
|
|
+ <appender-ref ref="STDOUT"/>
|
|
|
+ </root>
|
|
|
+</configuration>
|
|
|
+```
|
|
|
+
|
|
|
+## appender
|
|
|
+参考 https://www.cnblogs.com/cb0327/archive/2004/01/13/5770794.html
|
|
|
+
|
|
|
+### 自定义 appender
|
|
|
+
|
|
|
+> 本身logback提供了AppenderBase和UnsynchronizedAppenderBase两个抽象类(同步和异步),所以我们自定义时,只需要看实际业务继承其中的一个即可。
|
|
|
+
|
|
|
+1. 自定义appender类
|
|
|
+```java
|
|
|
+public class MyDbAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
|
|
|
+
|
|
|
+ Layout<ILoggingEvent> layout;
|
|
|
+
|
|
|
+ //自定义配置
|
|
|
+ String printString;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void start(){
|
|
|
+ //这里可以做些初始化判断 比如layout不能为null ,
|
|
|
+ if(layout == null) {
|
|
|
+ addWarn("Layout was not defined");
|
|
|
+ }
|
|
|
+ //或者写入数据库 或者redis时 初始化连接等等
|
|
|
+ super.start();
|
|
|
+ }
|
|
|
+ @Override
|
|
|
+ public void stop() {
|
|
|
+ //释放相关资源,如数据库连接,redis线程池等等
|
|
|
+ System.out.println("logback-stop方法被调用");
|
|
|
+ if(!isStarted()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ super.stop();
|
|
|
+ }
|
|
|
+ @Override
|
|
|
+ public void append(ILoggingEvent event) {
|
|
|
+ if (event == null || !isStarted()){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 此处自定义实现输出
|
|
|
+ // 获取输出值:event.getFormattedMessage()
|
|
|
+ // System.out.print(event.getFormattedMessage());
|
|
|
+ // 格式化输出
|
|
|
+ System.out.print(printString + ":" + layout.doLayout(event));
|
|
|
+
|
|
|
+ }
|
|
|
+ // 省略 setter 和 getter
|
|
|
+}
|
|
|
+```
|
|
|
+2. logback配置
|
|
|
+```xml
|
|
|
+<configuration>
|
|
|
+ <!-- 自定义appender -->
|
|
|
+ <appender name="MyDbAppender" class="liu.log.guide.appender.MyLogAppender">
|
|
|
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
|
|
+ <!-- 日志收集最低日志级别 -->
|
|
|
+ <level>INFO</level>
|
|
|
+ </filter>
|
|
|
+ <layout class="ch.qos.logback.classic.PatternLayout">
|
|
|
+ <pattern>${pattern}</pattern>
|
|
|
+ </layout>
|
|
|
+ <!-- 自定义参数 -->
|
|
|
+ <printString>刘传伟(logback)</printString>
|
|
|
+ </appender>
|
|
|
+
|
|
|
+ <logger name="liu.log.guide">
|
|
|
+ <appender-ref ref="MyDbAppender"/>
|
|
|
+ </logger>
|
|
|
+ <root>
|
|
|
+ <appender-ref ref="MyDbAppender"/>
|
|
|
+ </root>
|
|
|
+</configuration>
|
|
|
+```
|
|
|
+
|
|
|
+> 在 springboot-log-guide 中会用spring 继承logback将日志写入到数据库中
|
|
|
+
|
|
|
+参考:https://www.cnblogs.com/zimublog/p/12923786.html
|
|
|
+
|
|
|
+```java
|
|
|
+@Component
|
|
|
+public class MyLogDbAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private BusinessLogService businessLogService;
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
|
|
+ ThresholdFilter filter = new ThresholdFilter();
|
|
|
+ filter.setLevel("ERROR");
|
|
|
+ filter.setContext(context);
|
|
|
+ filter.start();
|
|
|
+ this.addFilter(filter);
|
|
|
+ this.setContext(context);
|
|
|
+
|
|
|
+ // 定义 logger (和在 logback.xml 中的配置是对应的)
|
|
|
+ Logger logger = context.getLogger("liu.log.guide");
|
|
|
+ logger.setLevel(Level.DEBUG);
|
|
|
+ logger.setAdditive(false);
|
|
|
+ logger.addAppender(MyLogDbAppender.this);
|
|
|
+ // 定义要有下面这句
|
|
|
+ super.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void append(ILoggingEvent event) {
|
|
|
+ if (event == null || !isStarted()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // TODO 根据实际情况替换
|
|
|
+ BusinessLog businessLog = new BusinessLog();
|
|
|
+ businessLog.setLevel(event.getLevel().levelStr);
|
|
|
+ businessLog.setClassPath(event.getLoggerName());
|
|
|
+ businessLog.setMessage(event.getMessage());
|
|
|
+ businessLog.setCreateBy(1L);
|
|
|
+ businessLog.setCreateDate(new Date());
|
|
|
+ try {
|
|
|
+ businessLogService.add(businessLog);
|
|
|
+ } catch (Exception e) {
|
|
|
+ this.addError("上报错误日志失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+## 对比 log4j.properties
|
|
|
+参考:https://blog.csdn.net/drift_away/article/details/7403658
|
|
|
+
|
|
|
+```properties
|
|
|
+log4j.rootLogger=DEBUG,CONSOLE,A1,im
|
|
|
+#DEBUG,CONSOLE,FILE,ROLLING_FILE,MAIL,DATABASE
|
|
|
+###################
|
|
|
+# Console Appender
|
|
|
+###################
|
|
|
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
|
|
+log4j.appender.Threshold=DEBUG
|
|
|
+log4j.appender.CONSOLE.Target=System.out
|
|
|
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
|
|
+log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
|
|
|
+#log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD] n%c[CATEGORY]%n%m[MESSAGE]%n%n
|
|
|
+#####################
|
|
|
+# File Appender
|
|
|
+#####################
|
|
|
+log4j.appender.FILE=org.apache.log4j.FileAppender
|
|
|
+log4j.appender.FILE.File=file.log
|
|
|
+log4j.appender.FILE.Append=false
|
|
|
+log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
|
|
|
+log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
|
|
|
+# Use this layout for LogFactor 5 analysis
|
|
|
+
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+## 打印日志的最佳实践
|
|
|
+
|
|
|
+参考:[别再乱打日志了,这份 Java 日志规范,应有尽有,建议收藏!!](https://mp.weixin.qq.com/s/UqvMC6t39YTPDa_FqrkbaA)
|
|
|
+
|
|
|
+## 占位符
|
|
|
+slf4j logger 占位符非常简单,并没有类型的区别
|
|
|
+```java
|
|
|
+String name = "中国";
|
|
|
+logger.info("我的名字叫{}", name);
|
|
|
+```
|
|
|
+
|
|
|
+## isDebugEnabled
|
|
|
+
|
|
|
+参考:[Java日志框架中需要判断log.isDebugEnabled()吗?](https://www.cnblogs.com/leiwei/p/14674143.html)
|
|
|
+
|
|
|
+很多代码中都用到了 isDebugEnabled ,如下:
|
|
|
+```java
|
|
|
+if (logger.isTraceEnabled()) {
|
|
|
+ logger.trace("Detected " + this.multipartResolver);
|
|
|
+}
|
|
|
+else if (logger.isDebugEnabled()) {
|
|
|
+ logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+看源码可知,isDebugEnabled() 用来判断当前日志级别是否低于debug,比如info级别高于debug,则不会打印日志。
|
|
|
+```java
|
|
|
+public boolean isDebugEnabled(Marker marker) {
|
|
|
+ final FilterReply decision = callTurboFilters(marker, Level.DEBUG);
|
|
|
+ if (decision == FilterReply.NEUTRAL) {
|
|
|
+ return effectiveLevelInt <= Level.DEBUG_INT;
|
|
|
+ } else {
|
|
|
+ throw new IllegalStateException("Unknown FilterReply value: " + decision);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**为什么要加if判断?**
|
|
|
+如果不加if判断,即使最终不需要debug日志输出,编译时也会自动执行字符串拼接,多少会消耗一部分性能。
|
|
|
+而加上if判断之后,就会跳过if条件内的代码
|
|
|
+```java
|
|
|
+//代码1
|
|
|
+logger.debug("这是一条debug日志:" + msg);
|
|
|
+//代码2
|
|
|
+if(logger.isDebugEnabled()) {
|
|
|
+ logger.debug("这是一条debug日志:" + msg);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+网上有部分人说,不需要加if判断,使用占位符即可。
|
|
|
+```java
|
|
|
+logger.debug("这是一条debug日志:{}", msg);
|
|
|
+```
|
|
|
+
|
|
|
+但在某些情况下,占位符依然不能避免性能消耗。
|
|
|
+```java
|
|
|
+logger.debug("这是一条debug日志:{}", obj.toString());
|
|
|
+```
|
|
|
+如上面这段代码,最终不会打印debug日志,但是会执行 obj.toString()
|
|
|
+
|
|
|
+### 最佳实践
|
|
|
+
|
|
|
+
|
|
|
+==**原则一:如果打印字符串常量,不需要`isDebugEnabled`**==
|
|
|
+
|
|
|
+比较下面两段代码:
|
|
|
+
|
|
|
+```java
|
|
|
+//代码1
|
|
|
+logger.debug("hello, world");
|
|
|
+//代码2
|
|
|
+if(logger.isDebugEnabled()){
|
|
|
+ logger.debug("hello, world");
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+因为打印的日志是字面常量,没有计算逻辑。两段代码的性能是几乎一样的。添加`isDebugEnabled`反而会导致额外的代码。
|
|
|
+
|
|
|
+==**原则二:如果有参数,且参数只是字符串常量或计算简单,使用占位符**==
|
|
|
+
|
|
|
+考虑如下代码,`debug`方法包含了参数`user.getName()`。虽然执行`debug`方法时,会计算`user.getName()`,但只是一个简单的**get**方法,没有复杂计算,这时候,也可以不添加`isDebugEnabled`。
|
|
|
+
|
|
|
+```java
|
|
|
+logger.debug("hello, {}", user.getName());
|
|
|
+```
|
|
|
+
|
|
|
+==**原则三:如果有参数,且参数计算复杂,添加`isDebugEnabled`**==
|
|
|
+
|
|
|
+```java
|
|
|
+logger.debug("order price: {}", calculatePrice());
|
|
|
+```
|
|
|
+
|
|
|
+假设`calculatePrice`方法需要经过复杂计算。那么就应该添加`isDebugEnabled`判断,使用如下的代码:
|
|
|
+```java
|
|
|
+if(logger.isDebugEnabled()){
|
|
|
+ logger.debug("order price: {}", calculatePrice());
|
|
|
+}
|
|
|
+```
|