日志
Java boy 后端项目日志一般用 logback or Log4j & Log4j2,spring 默认集成logback,本文主要以logback 展开例子说明。
对于日志系统有什么样的需求?
1、本地环境需要打印Console日志。
2、测试环境最好实时打印日志。
3、生产环境打印日志不要阻塞系统(日志阻塞导致很多故障,特别是流量大的场景,小流量可以忽略)
4、可以实时修改,有些日志不关注,需要隔离一下。
5、分离日志...ERROR ALL not care...
... 等等信息。
日志需求分析
"小小"《环境》
现实开发场景中,很多都需要有环境的概念,redis 预发和生产 prefix key 不同.
test 环境和生产环境的调用的接口不同... env=test env=prod 等等配置标识。
但是这些都不是非常的规范,在Spring Boot中,spring.profiles.active是一个非常重要的配置属性,它用于指定当前激活的配置文件(profiles)。通过这个属性,你可以控制应用程序在不同环境下的行为,例如开发、测试和生产环境。 SpringBoot激活profiles你知道几种方式?
- application.properties
properties
spring.profiles.active=dev
- 系统参数
bash
export SPRING_PROFILES_ACTIVE=dev
- 命令行参数
bash
java -jar your-application.jar --spring.profiles.active=prod
通过配置中心注入系统环境或者属性进行配置(放在公共配置,多应用统一使用),或者统一运维的部署脚本中指定,通过标准化的路径实现环境标准化,无需自定义。
环境隔离日志配置
这个是 logback-spring.xml 里面的配置
非生产、非预发、非测试 非开发环境打印console
springProfile 灵活的配置,可以让开发处理多环境的配置信息非常easy
xml
<springProfile name="!prod,!pre">
<root level="INFO">
<appender-ref ref="APPLICATION"/>
<appender-ref ref="FILE_ERROR"/>
<!--本地环境打印console 日志-->
<springProfile name="!test,!dev">
<appender-ref ref="CONSOLE"/>
</springProfile>
</root>
</springProfile>
日志阻塞
之前见过很多系统由于日志阻塞导致系统功能受影响,响应耗时增加,这样的例子非常多。常见的场景的处理方式是配置为异步日志。 logback之 AsyncAppender 的原理、源码及避坑建议
在普通的日志RollingFileAppender 基础上包装一个异步日志 ,如下配置。
xml
<appender name="APPLICATION" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>logs/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>1GB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="ASYNC_APPLICATION" class="ch.qos.logback.classic.AsyncAppender">
<neverBlock>true</neverBlock>
<queueSize>5120</queueSize>
<discardingThreshold>20</discardingThreshold>
<includeCallerData>true</includeCallerData>
<appender-ref ref="APPLICATION"/>
</appender>
异步日志,由于打印日志是异步的,导致测试环境跟踪的时候查看日志非实时写文件的,用起来非常恼火,特别是调试的时候,测试环境是否可以非异步的?
通过上面介绍的spring环境隔离即可完成,如下配置。
xml
<!-- prod 和 pre 进行异步日志-->
<springProfile name="prod,pre">
<appender name="ASYNC_APPLICATION" class="ch.qos.logback.classic.AsyncAppender">
<neverBlock>true</neverBlock>
<queueSize>5120</queueSize>
<discardingThreshold>20</discardingThreshold>
<includeCallerData>true</includeCallerData>
<appender-ref ref="APPLICATION"/>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC_APPLICATION"/>
</root>
</springProfile>
<!--test 环境和本地 不进行异步日志-->
<springProfile name="!prod,!pre">
<root level="INFO">
<appender-ref ref="APPLICATION"/>
<!--本地环境打印console 日志-->
<springProfile name="!test,!dev">
<appender-ref ref="CONSOLE"/>
</springProfile>
</root>
</springProfile>
怎么判断异步日志是否配置OK? debug ?arthas 也行?
查看一下异步日志的实例是否存在
bash
[arthas@34932]$ vmtool -x 1 --action getInstances --className ch.qos.logback.classic.AsyncAppender --limit 5
@AsyncAppender[][
@AsyncAppender[ch.qos.logback.classic.AsyncAppender[ASYNC_LOG_FILE_OKHTTP]],
@AsyncAppender[ch.qos.logback.classic.AsyncAppender[ASYNC_LOG_FILE_MIDDLEWARE]],
@AsyncAppender[ch.qos.logback.classic.AsyncAppender[ASYNC_ERROR]],
@AsyncAppender[ch.qos.logback.classic.AsyncAppender[ASYNC_APPLICATION]],
]
logback 配置文件不推荐放在配置中心
配置文件和源码放在一起,通过环境变量区分不同环境的处理方式比较好,方便修改
某些场景需要隐藏某些日志,放在配置中心非常不方便,修改起来也非常麻烦,修改错误了也不知道,只有发布的时候才能感知。
eg: kafka的日志不要放在all 日志里面,需要在线日志系统订阅关键的业务日志信息,减少费用。
xml
<logger name="org.apache.kafka" level="INFO" additivity="false">
<appender-ref ref="LOG_FILE_MIDDLEWARE"/>
</logger>
eg: 我要禁用日志 ,这种想法可能跟随者业务需求进行变化。
xml
<logger name="org.redisson.connection.DNSMonitor" level="OFF"/>
分离日志
- additivity="false" 不加入总日志信息
- error 日志分离
xml
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application_error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>logs/application_error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>1GB</maxFileSize>
<maxHistory>3</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
总结
日志系统平常开发过程中经常遇到,日志打印刷屏、日志太多、日志治理等发生在日常的开发过程中,养成一个好的习惯,好的日志更快速的排查到问题。
线上环境error日志重点关注,前提是error日志要少,如果一个error一天几百个G,会有人看?
附上完整日志配置
xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] %X{method} %-5level [%thread] [%logger:%line] :%m %n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 采用Spring boot中默认的控制台彩色日志输出模板 -->
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="APPLICATION" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>logs/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>1GB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application_error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>logs/application_error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>1GB</maxFileSize>
<maxHistory>3</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="LOG_FILE_OKHTTP" class="ch.qos.logback.core.rolling.RollingFileAppender" additivity="false">
<file>logs/application_okhttp.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application_okhttp.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>1GB</maxFileSize>
<maxHistory>2</maxHistory>
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
<!-- 格式化输出 -->
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="LOG_FILE_MIDDLEWARE" class="ch.qos.logback.core.rolling.RollingFileAppender" additivity="false">
<immediateFlush>false</immediateFlush>
<file>logs/application_middleware_client.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application_middleware_client.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>1GB</maxFileSize>
<maxHistory>2</maxHistory>
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
<!-- 格式化输出 -->
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="okhttp3.OkHttpClient" level="INFO" additivity="false">
<appender-ref ref="LOG_FILE_OKHTTP"/>
</logger>
<logger name="org.apache.kafka" level="INFO" additivity="false">
<appender-ref ref="LOG_FILE_MIDDLEWARE"/>
</logger>
<logger name="org.apache.zookeeper" level="INFO" additivity="false">
<appender-ref ref="LOG_FILE_MIDDLEWARE"/>
</logger>
<logger name="org.apache.curator" level="INFO" additivity="false">
<appender-ref ref="LOG_FILE_MIDDLEWARE"/>
</logger>
<!-- prod 和 pre 进行异步日志-->
<springProfile name="prod,pre">
<appender name="ASYNC_APPLICATION" class="ch.qos.logback.classic.AsyncAppender">
<neverBlock>true</neverBlock>
<queueSize>5120</queueSize>
<discardingThreshold>20</discardingThreshold>
<includeCallerData>true</includeCallerData>
<appender-ref ref="APPLICATION"/>
</appender>
<appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
<neverBlock>true</neverBlock>
<queueSize>5120</queueSize>
<discardingThreshold>20</discardingThreshold>
<includeCallerData>true</includeCallerData>
<appender-ref ref="FILE_ERROR"/>
</appender>
<appender name="ASYNC_LOG_FILE_MIDDLEWARE" class="ch.qos.logback.classic.AsyncAppender">
<neverBlock>true</neverBlock>
<queueSize>1024</queueSize>
<discardingThreshold>20</discardingThreshold>
<includeCallerData>false</includeCallerData>
<appender-ref ref="LOG_FILE_MIDDLEWARE"/>
</appender>
<appender name="ASYNC_LOG_FILE_OKHTTP" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>1024</queueSize>
<discardingThreshold>20</discardingThreshold>
<neverBlock>true</neverBlock>
<includeCallerData>false</includeCallerData>
<appender-ref ref="LOG_FILE_OKHTTP"/>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC_APPLICATION"/>
<appender-ref ref="ASYNC_ERROR"/>
</root>
</springProfile>
<!--test 环境和本地 不进行异步日志-->
<springProfile name="!prod,!pre">
<root level="INFO">
<appender-ref ref="APPLICATION"/>
<appender-ref ref="FILE_ERROR"/>
<!--本地环境打印console 日志-->
<springProfile name="!test,!dev">
<appender-ref ref="CONSOLE"/>
</springProfile>
</root>
</springProfile>
<!-- 日志通过系统环境变量配置spring active: export SPRING_PROFILES_ACTIVE=dev
https://blog.csdn.net/weixin_42033269/article/details/102805546 -->
</configuration>