一、前言
记录应用系统曰志主要有三个原因 记录操作轨迹、监控系统运行状况、回溯系统故障。记录操作行为及操作轨迹数据,可以数据化地分析用户偏好,有助于优化业务逻辑,为用户提供个性化的服务。例如,通过 access.log 记录用户的操作频度和跳转链接,有助于分析用户的后续行为。<br /> 全面有效的日志系统有助于建立完善的应用监控体系,由此工程师可以实时监控系统运行状况,及时预警,避免故障发生。监控系统运行状况,是指对服务器使用状态,如内存、 CPU 等使用情况,应用运行情况 如响应时间 QPS 等交互状态;应用错误信息,如空指针、 SQL 异常等的监控。例如,在 CPU 使用率大于 60%, 四核服务器中load 大于4时发出报警,提醒工程师及时处理,避免发生故障。<br /> 当系统发生线上问题时,完整的现场日志有助于工程师快速定位问题。例如当系统内存溢出时,如果日志系统记录了问题发生现场的堆信息,就可以通过这个曰志分析是什么对象在大量产生并且没有释放内存,回溯系统故障,从而定位问题。
二、规范
推荐的日志文件命名方式为appName_logType_logName.log 其中 logType为日志类型,推荐分类有 stats monitor visit等, logName 为日志描述。这种命名的好处是通过文件名就可以知道曰志文件属于什么应用,什么类型 ,什么目的,也有利于归类查找。
例如, mppserver 应用中单独监控时区转换异常的日志文件名定义为mppserver__monitor_timeZoneConvert.log
保存时间:
代码规约推荐曰志文件至少保存15天,可以根据日志文件的重要程度、文件大小及磁盘空间再自行延长保存时间。
预先判断日志级别:
对DEBUG 、INFO 级别的日志,必须使用条件输出或者使用占位符的方式打印。该约定综合考虑了程序的运行效率和日志打印需求。例如 在某个配置了打印日志级别为WARN 的应用中,如果针对 DEBUG 级别的日志,仅仅在程序中写出 logger.debug(”Processing trade with id:” + id + ” and symbol:"+ symbol);,那么该日志不会被打印但是会执行字符串拼接操作,如果 symbol 是对象 还会执行 toString() 方法
白白浪费了系统资源。
如下示例代码为正确的打印日志方式:
//使明条件判断形式if (logger.isDebugEnabled()) {logger.debug ("Processing trade with id:" + id + "and symlbol:" + symbol) ;}//使用占位符形式logger.debug ("Processing trade with id: {} and symbol: {}",id, symbol);
避免无效日志打印:
生产环境禁止输出 DEBUG 曰志且有选择地输出 INFO日志。使用 INFO、WARN 级别来记录业务行为信息时,一定要控制日志输出量,以免磁盘空间不足。同时要为曰志文件设置合理的生命周期及时清理过期的日志。避免重复打印,务必在日志配置文件中设置 additivity=false。
区别对待错误日志:
WARN、ERROR 都是与错误有关的日志级别,但不要一发生错误就笼统地输出ERROR 级别日志。 一些业务异常是可以通过引导重试就能恢复正常的,例如用户输入参数错误。在这种情况下,记录日志是为了在用户咨询时可以还原现场,如果输出ERROR 级别就表示一旦出现就需要人为介入,这显然不合理。所以,ERROR只记录系统逻辑错误、异常或者违反重要的业务规则,其他错误都可以归为 WARN级别。
保存记录内容完整:
曰志记录的内容包括现场上下文信息与异常堆栈信息,所以打印时需要注意以下两点:
- 记录异常时一定要输出异常堆栈,例如
logger.error("xxx" +e.getMessage(),e) - 曰志中如果输出对象实例,要确保实例类重写了 toString()方法,否则只会输出对象的 hashCode 没有实际意义。
三、SpringBoot默认日志配置
1.由低到高:trace < debug < info < warn < error
2. Spring Boot默认设定的是 info 级别日志,(日志默认级别也称为root级别)。可修改默认级别日志:logging.level.root=级别名
3. 可以进行调整日志级别,设定某个级别后,就只打印设定的这个级别及后面高级别的日志信息。没有指定级别的就用SpringBoot默认规定的级别:root级别
4. 可修改指定包的日志级别:指定某个包下面的所有日志级别:logging.level.包名=级别名
分析日志底层实现
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--spring-boot-starter-web 中引入了 spring-boot-starter 启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.3.1.RELEASE</version><scope>compile</scope></dependency><!--spring-boot-starter 中引入了 spring-boot-starter-logging 日志启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId><version>2.3.1.RELEASE</version><scope>compile</scope></dependency><!--spring-boot-starter-logging 日志启动器 采用的是 logback 日志框架 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><scope>compile</scope></dependency>
总结:SpringBoot中默认日志启动器为 spring-boot-starter-logging ,默认采用的是 logback 日志框架
在 spring-boot-2.3.1.RELEASE.jar! \org\springframework\boot\logging\logback\base.xml 做了日志的默认配置**
<included><!--日志格式默认规定--><include resource="org/springframework/boot/logging/logback/defaults.xml" /><!--日志文件默认生成路径--><property name="LOG_FILE"value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/><!--控制台日志信息默认配置--><include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <!--文件中日志信息默认配置--><include resource="org/springframework/boot/logging/logback/file-appender.xml" /> <!--日志级别默认为: info --><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="FILE" /></root></included>
四、自定义日志配置
1.首先需要修改依赖
修改pom依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 去掉springboot默认配置 --><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency> <!-- 引入log4j2依赖 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency>
yml文件配置
logging:
file:
#在当前项目下生成日志文件
name: demo.log #文件名
#在指定路径下生成日志
path: D:/spring/log
pattern:
console: '%clr(%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n)'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} >>> [%thread] >>> %-5level >>> %logger{50} >>> %msg%n'
#path: logs/log_lzy
config: classpath:logconfig.xml #xml配置地址
注意:如上,yml文件中首尾加上单引号可解决识别不了%的问题,properties不需要加
可以采用xml的方式进行配置
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--变量配置-->
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="./logs/log4j2" />
<property name="FILE_NAME" value="lzy-log4j2-demo" />
</Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
</appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="Filelog"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
可以选择yml的方式进行配置
配置前需要向pom.xml导入依赖
<!-- 加上这个才能辨认到log4j2-spring.yml文件 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.11.0.rc1</version>
</dependency>
<!-- log4j2支持异步日志,导入disruptor依赖,不需要支持异步日志,也可以去掉该依赖包 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
application.yml的配置
# log4j2配置
logging:
config: classpath:config/log4j2-spring.yml
file:
path: F:/spring/log
log4j2-spring.yml的配置
# 共有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: F:/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
