日志概述
日志的作用
- 便于分析程序执行过程
- 方便调试
- 可以将业务数据存储到文件、数据库、es,便于统计分析
常见的日志组件
- 日志门面|接口层:提供日志接口 | api,常见的 slf4j、apache的commons-logging
- 日志实现:提供具体实现,常见的 log4j、log4j2、logback、jdk自带的jul
日志组件对性能有一定影响,尤其是需要大量使用日志的项目,应该注意日志组件的选择。
日志之所以采用门面模式,是为了在项目中使用统一的日志api,方便以后切换。比如项目统一使用commons-logging的日志接口来记录日志,使用log4j作具体实现,后续可以直接切换为log4j2等其它日志实现组件,无需修改记录日志的相关java代码。
ssm老项目常用 commons-logging+log4j。
slf4j支持占位符,性能更高,springboot默认使用 slf4j+logback,通常也是使用 slf4j+logback。
springboot的日志实现
springboot默认集成了spring-boot-starter-logging,采用slf4j+logback作日志
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
这个依赖包含了
- slf4j-api:slf4j的日志接口
- logback-classic:logback对slf4j的实现
- jul-to-slf4j:将jul转slf4j,已经包含了slf4j、logback的依赖
- log4j-to-slf4j:将log4j转slf4j,已经包含了log4j、slf4j、logback的依赖
可以看出,spring-boot-starter-logging可以将其它日志类型转换为slf4j,如果要统一第三方jar依赖中的日志,exclusion移除第三方依赖自带的日志组件即可,spring-boot-starter-logging会自动适配为slf4j。
logback的日志格式
logback的日志级别
TRACE < DEBUG < INFO(默认) < WARN < ERROR < FATAL
logback日志默认格式
2020-05-24 16:51:38.323 INFO 25168 --- [ main] com.chy.visit.VisitApplication : No active profile set, falling back to default profiles: default
日期时间 日志级别 pid 线程名(main是主线程) 哪个类输出的日志(包名.类名) 信息
pid即进程id,记录当前java进程的pid
logback常用配置
resources下新建 logback.xml 或 logback-spring.xml ,springboot应用启动时会自动加载这个文件,官方推荐使用logback-spring.xml,因为可以自动应用一些spring的配置。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 日志存储目录 -->
<property name="LOG_HOME" value="logs" />
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="springboot"/>
<!-- 日志格式,%-5level表示日志级别占5个字符位 -->
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n"/>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用日志格式 -->
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 逐日输出文件 -->
<appender name="DAY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名 -->
<FileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}.log</FileNamePattern>
<!-- 日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> -->
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
<!-- 日志文件最大体积 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>500MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 全部日志输出到一个文件中,用于springboot admin、elk之类的应用统一收集、处理日志 -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/${appName}-all.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 日志默认输出配置 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="DAY_FILE"/>
<appender-ref ref="FILE"/>
</root>
<!-- 可以给包、类指定日志输出级别 -->
<logger name="com.apache.ibatis" level="WARN" />
<logger name="java.sql.Connection" level="WARN" />
<logger name="java.sql.Statement" level="WARN" />
<logger name="java.sql.PreparedStatement" level="WARN" />
<logger name="java.sql.ResultSet" level="DEBUG" />
<logger name="com.alibaba.druid.pool.DruidPooledResultSet" level="ERROR" />
<logger name="org.apache.shiro" level="WARN" />
<logger name="springfox.documentation" level="WARN" />
</configuration>
yml日志配置
也可以在yml中配置日志,生产更推荐在xml文件中统一配置日志,测试环境可以在yml中修改日志配置比较方便,便于调试
logging:
path: logs/${spring.application.name}/
level.com.chy.mall.service.impl.UserServiceImpl: debug
level.com.chy.mall.dao: debug
记录日志
使用commons-logging
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Xxx {
private static Log log = LogFactory.getLog(this.getClass());
public void xxx(){
log.info("xxx");
}
}
使用slf4j(推荐)
- 可以使用 LoggerFactory 手动创建日志对象,也可以使用 lombok 的注解自动创建log对象,默认对象名 log;
- 输出多个值时,尽量不要用 + 号拼接字符串,使用占位符代替,性能更高;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Xxx {
private static Logger logger = LoggerFactory.getLogger(this.getClass());
public void xxx(){
String username="xxx", tel="xxx", address="xxx";
log.info("username:{},tel:{},address:{}",username,tel,address);
}
}
可以用 topic 属性设置日志主题,缺省时默认为所在类的全限定类名
// 17:37:09.322 [main] INFO com.chy.mall.service.UserServiceImpl - 日志信息
@Slf4j
// 17:34:08.554 [main] INFO Xxx服务 - xxx日志信息
// 17:34:08.554 [main] INFO Xxx定时任务 - xxx日志信息
@Slf4j(topic = "Xxx服务")
@Slf4j(topic = "Xxx定时任务")
日志规范
1、接口、rpc调用、重要的service方法,务必打印出方法入参、返回值
2、写代码时务必要在关键、重要之处务必打印日志,考虑出现bug时,打印的日志是否方便排查、定位问题
3、复杂类型尽量转换为 json 格式打印,json格式更通用,便于 copy 到 postman 测试、修复数据等
//用户信息user=User(userId=1, username=chy, tel=1888xxxx, orderList=null)
log.info("用户信息user={}", user);
//用户信息user={"tel":"1888xxxx","userId":1,"username":"chy"}
log.info("用户信息user={}", JSON.toJSONString(user));