日志

为什么要写日志

举个简单的例子,你把项目写完后,交给客户,然后客户运行的时候出现问题,很紧急,你要解决这个问题,怎么办?

这就是写日志的原因,在程序出现问题的时候,日志可以第一时间定位到问题的所在,方便及时排查、解决问题。

在系统开发中,日志是很重要的一个环节,日志写得好对于我们开发调试,线上问题追踪等都有很大的帮助。但记日志并不是简单的输出信息,需要考虑很多问题,比如日志输出的速度,日志输出对于系统内存,CPU的影响等,为此,出现了很多日志框架,以帮助开发者解决这些问题。

为什么项目里不让使用System.out

  1. 出错了不知道哪个类哪个方法第几行有问题
  2. System.out.println(“”) 很费性能
  3. 你写了以后过几天你就找不见写在哪了
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {

long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i ++){
// 注释掉试试
System.out.println("测试System.out性能");
}
long end = System.currentTimeMillis();
System.out.println("共耗时"+(1.0*(end - start)/1000) + "s");

}

不注释System.out共耗时6.83s
注释System.out共耗时0.004s

6.83 / 0.004 = 1707.5
差距够大吧

怎么写日志

假如你开发完项目,交给其他人,他们遇到问题,找你解决,你远在千里,解决的时候需要什么信息,你写日志的时候就写什么信息。就这么简单。

写日志用什么

  1. j.u.l (即java.util.logging)
  2. log4j
  3. commons-logging
  4. logback
  5. SLF4J

这些都可以,但是推荐使用 SLF4J + logback

Java日志

SLF4J+logback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- SLF4J -->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>

<!-- Logback -->
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.3</version>
</dependency>

<!-- lombok -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>

logback.xml
放src目录下或者src/main/resources目录下就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- scan="true" scanPeriod="60 seconds" debug="false" -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="./logs" />
<property name="LOG_LEVEL" value="INFO" />
<property name="appName" value="appName"></property>

<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] [%line] - %msg%n</pattern>
</encoder>
</appender>

<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/${appName}.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] [%line] - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>

<!-- 日志输出级别 -->
<root level="${LOG_LEVEL}">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>

常用写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class LogTest {

private static final Logger log = LoggerFactory.getLogger(CrawlerUmetrip.class);

public static void main(String[] args) {
log.debug("调试信息");
log.info("详细信息");
log.warn("警告信息");
log.error("错误信息");
}

}

更简洁的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LogTest {


public static void main(String[] args) {
log.debug("调试信息");
log.info("详细信息");
log.warn("警告信息");
log.error("错误信息");
}

}

SLF4J+log4j

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- SLF4J -->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>


<!-- lombok -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
### log4j配置 ###
log4j.rootLogger = DEBUG, console, D, E
log4j.additivity.org.apache = false

### 输出信息到控制台 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
#log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout = com.custom.CustomPatternLayout
log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t] %p: -%m%n

### 输出DEBUG 级别以上的日志到=E://logs/log.log ###
#log4j.appender.D = org.apache.log4j.RollingFileAppender
log4j.appender.D = com.custom.TestRollingFileAppender
log4j.appender.D.File = E://logs/test/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.MaxFileSize = 10MB
#log4j.appender.D.MaxBackupIndex = 10 #MaxBackupIndex在TestRollingFileAppender没用,可以不写。
log4j.appender.D.layout = com.custom.CustomPatternLayout
log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t] %p: -%m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.RollingFileAppender
log4j.appender.E.File = E://logs/test/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.MaxFileSize = 10MB
log4j.appender.E.MaxBackupIndex = 10
log4j.appender.E.layout = com.custom.CustomPatternLayout
log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t] %p: -%m%n

log4j.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' >

<!-- 输出到控制台 -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<!-- 设置布局 -->
<layout class="com.custom.CustomPatternLayout"><!-- org.apache.log4j.PatternLayout -->
<!-- 输出格式/转换格式 -->
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t] %p: -%m%n" />
</layout>
<!--过滤器设置输出的级别-->
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<!-- 设置最低级别 -->
<param name="levelMin" value="debug" />
<!-- 设置最高级别 -->
<param name="levelMax" value="fatal" />
<param name="AcceptOnMatch" value="true" />
</filter>
</appender>

<!-- 设置输出到文件 设置滚动方式 -->
<appender name="fileD" class="com.custom.TestRollingFileAppender"> <!-- org.apache.log4j.RollingFileAppender -->
<!-- 设置日志输出文件名 -->
<param name="File" value="E:/logs/test4/log.log" />
<!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
<param name="Append" value="true" />
<!-- 设置滚动时最多保存几个日志文件 -->
<param name="MaxBackupIndex" value="10" />
<!-- 设置文件大小达到多大时滚动-->
<param name="MaxFileSize" value="10MB"/>
<!-- 设置布局 -->
<layout class="com.custom.CustomPatternLayout"><!-- org.apache.log4j.PatternLayout -->
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t] %p: -%m%n" />
</layout>
</appender>

<!-- 设置输出到文件 设置滚动方式 -->
<appender name="fileE" class="org.apache.log4j.RollingFileAppender">
<!-- 设置日志输出文件名 -->
<param name="File" value="E:/logs/test4/error.log" />
<!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
<param name="Append" value="true" />
<!-- 设置滚动时最多保存几个日志文件 -->
<param name="MaxBackupIndex" value="10" />
<!-- 设置文件大小达到多大时滚动-->
<param name="MaxFileSize" value="10MB"/>
<!-- 设置布局 -->
<layout class="com.custom.CustomPatternLayout"><!-- org.apache.log4j.PatternLayout -->
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t] %p: -%m%n" />
</layout>
</appender>

<!-- -->
<!-- 设置输出到文件 设置滚动方式 -->
<appender name="fileF" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="E:/logs/test4/fatal.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd'.log'" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t] %p: -%m%n" />
</layout>
</appender>



<!-- 设置根logger-->
<root>
<priority value ="debug"/>
<!-- 设置输出到哪,和appender name相对应 -->
<appender-ref ref="console"/>
<appender-ref ref="fileD"/>
<appender-ref ref="fileE"/>
</root>

</log4j:configuration>

springboot项目日志输出到控制台

在application.properties文件里添加以下两行:

1
2
logging.file=account.log
logging.config= logback-spring.xml的绝对路径

再把logback-spring.xml文件放到对应的路径下,通过slf4j+logback来管理,在配置里配置不往控制台输出日志。
默认的logback-spring.xml在 classpath/logback-spring.xml

References

[1] 【Java深入学习系列】之那些年我们用过的日志框架
[2] Java日志框架(Commons-logging,SLF4j,Log4j,Logback)
[3] 封装SLF4J/Log4j,不再处处定义logger变量
[4] 日志工具现状调研
[5] logback layoutInsteadOfEncoder
[6] Logback源码赏析-日志按时间滚动(切割)
[7] features/log
[8] mavenrepo/index
[9] boot-features-logging.html
[10] boot-features-logging
[11] spring howto-logging
[12] log4j/1.2/faq.html
[13] Spring Boot干货系列:(七)默认日志logback配置解析
[14] 面试官:Logback如何配置,才能提升TPS?
[15] logback异步日志配置