SpringBoot Logback 日志打印详解
1. 为什么不要用 println
日志打印对于后端服务来说是极其重要的,很多时候我们定位问题都需要去 log 里找
Java 初学阶段,我们会经常用 System.out.println()
来打印 log,但是实际开发千万不要这么做
一来不够灵活,不能存文件、不能区分级别、不能配置开关
二来println
有很大的性能问题
我们看看 println
的源码:
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
| public void println(String x) { synchronized (this) { print(x); newLine(); } }
private void write(String s) { try { synchronized (this) { ensureOpen(); textOut.write(s); textOut.flushBuffer(); charOut.flushBuffer(); if (autoFlush && (s.indexOf('\n') >= 0)) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } } private void newLine() { try { synchronized (this) { ensureOpen(); textOut.newLine(); textOut.flushBuffer(); charOut.flushBuffer(); if (autoFlush) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } }
|
可以看到,System.out.println
方法使用了许多 synchronized
关键字,如果打印的内容比较长,并发高的情况下十分影响性能
所以开发中,我们都会使用相应的日志框架来打印日志,可以用配置文件实现灵活的配置
2. 日志门面与日志实现
日志框架分为两类,门面和实现,门面可以理解为定义了日志要怎么打,实现就是具体去打日志
门面:JCL(Jakarta Commons Logging) jboss-logging SLF4j
实现:Log4j JUL(java.util.logging) Log4j2 Logback
SpringBoot 使用 SLF4j 作为日志门面
如果我们使用 SpringBoot 的相应 starters,那么默认使用的日志实现框架就是 Logback
各个日志实现的配置文件:
框架 |
配置文件 |
Logback |
logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy |
Log4j2 |
log4j2-spring.xml or log4j2.xml |
JUL |
logging.properties |
Spring Boot 官方推荐使用 -spring
后缀的配置文件,这样 Spring 可以完全控制日志的初始化,日志也可以使用 Profile
3. Logback 介绍
Logback 是以前非常流畅的日志框架 Log4j 的继任者,它更小,但性能更强,更多优点看官方文档
它实现了日志门面 Slf4j,我们日志打印使用接口抽象即可:
| private final Log log = LogFactory.getLog(ClassName.class);
|
这样允许我们方便地切换到别的日志框架如 Log4j
通常我们可以引用 Lombok,这样直接在类上加上 @Slf4j
注解就可以直接打印 log:
| <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> <scope>provided</scope> </dependency>
|
| @RestController @Slf4j public class LogController {
@GetMapping("/log") public String hello() { String s = "log print test"; log.debug(s); log.info(s); log.error(s); log.warn(s); return s; } }
|
4. Logback 配置文件
这里给出我常用的logback-spring.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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
| <?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds"> <property name="FILE_ERROR_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} %file:%line: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="LOG_HOME" value="logs"/> <property name="LOG_FILE_SIZE" value="100MB"/>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<property name="CONSOLE_LOG_PATTERN_COLOR" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{0}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="SELF_DEFINE_LOG_PATTERN" value="[%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint}] [%clr(%5.5p){magenta}] [%clr(%5.10t){faint}] [%clr(%30.30c{0}.%20.20M:%4.4L){cyan}] -> %replace(%.-300m){'\r\n','__'}%ex{full}%n"/>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> </filter> <encoder> <pattern>${CONSOLE_LOG_PATTERN_COLOR}</pattern> <charset>UTF-8</charset> </encoder> </appender>
<appender name="CONSOLE2" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> </filter>
<encoder> <pattern>${SELF_DEFINE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> </encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>Info</level> </filter>
<File>${LOG_HOME}/web-log.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${LOG_HOME}/Log._%d{yyyy-MM-dd}.part_%i.log</FileNamePattern> <maxHistory>180</maxHistory> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>${LOG_FILE_SIZE}</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy>
<encoder> <pattern>${FILE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> </encoder> </appender>
<root level="info"> <appender-ref ref="CONSOLE2"/> </root>
<springProfile name="pro"> <root level="info"> <appender-ref ref="FILE"/> </root> </springProfile>
<springProfile name="dev"> <root level="info"> <appender-ref ref="FILE"/> </root> </springProfile> </configuration>
|
这里还是要再强调一下,对于并发或性能要求高的服务,不要打印方法名和行号,会影响性能
参见 Logbak 的官方文档
Generating the method name is not particularly fast. Thus, its use should be avoided unless execution speed is not an issue.
Generating the line number information is not particularly fast. Thus, its use should be avoided unless execution speed is not an issue.
看一下使用我自定义的 Pattern 打印的带颜色行号的日志格式:
5. 日志等级
默认等级在配置文件里配置,我们还可以手动配置指定包名、类名的日志等级:
| logging: level: root: "warn" org.springframework.web: "debug" org.hibernate: "error"
|
还可以给一些包分组,分组后一次性配置:
| logging: group: tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat" level: tomcat: "trace"
|
Spring Boot 预定义了两个分组
Name |
Loggers |
web |
org.springframework.core.codec, org.springframework.http,org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans |
sql |
org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener |
6. 统一日志输出
我用了一个开源项目 XXX,它日志框架用的是 Log4j ,我怎么统一用 SLF4j+Logback 来输出?
SLF4j 官方文档给出了下图的指引:
已切换 Log4j 为例:
我们只需要引入 log4j-over-slf4j.jar
,然后把开源项目的 log4j 依赖排除即可
| <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${org.slf4j-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>${org.slf4j-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>${org.slf4j-version}</version> </dependency>
|
篇幅有限,分布式环境的日志统一存储,下一篇再讲。
源码及脚本都在Github上
Enjoy it!