欢迎阅读另一篇关于SpringBoot中日志的文章 SpringBoot动态修改日志级别
本文基于SpringBoot2.6讲解
本文内容:
- 按照日志级别输出到不同日志文件
- 控制台日志设置和SpringBoot同风格彩色字体样式
- 指定该包下的日志都打印在customAppender中指定的文件路径
- 启动后不变的日志打印到单独的包,例如环境变量打印到environment.log中
- 按照springboot环境配置日志
初始化一个SpringBoot后启动项目,控制台会带有彩色日志样式.但是当我们配置了自己的logback-spring.xml日志文件时,样式可能就不存在了,console控制台变成了黑白屏。
其实彩色日志样式是SpringBoot自己做了特殊处理,实现了Logback的conversionRule
,源码参见:org.springframework.boot.logging.logback.DefaultLogbackConfiguration
我们可以直接在logback-spring.xml中使用SpringBoot实现的conversionRule,让console日志变成彩屏。
logback-spring.xml中如下几个参数
CONSOLE_LOG_PATTERN
FILE_LOG_PATTERN
LOG_DATEFORMAT_PATTERN
LOG_LEVEL_PATTERN
LOG_EXCEPTION_CONVERSION_WORD
LOG_PATH
CONSOLE_LOG_CHARSET
FILE_LOG_CHARSET
对应SpringBoot的几个配置
logging.pattern.console=
logging.pattern.file=
logging.pattern.level=
logging.pattern.dateformat=
logging.exception-conversion-word=
logging.file.path=logs
logging.charset.console=UTF-8
logging.charset.file=UTF-8
logback-spring.xml放到resources
目录即可被SpringBoot识别
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--自定义日志文件格式,支持控制台彩色字体,参见org.springframework.boot.logging.logback.DefaultLogbackConfiguration-->
<!-- 彩色日志依赖的渲染类 -->
<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"/>
<!--这几个属性SpringBoot会设置到SystemProperty,参考o.s.b.logging.LoggingSystemProperties.apply-->
<property name="LOG_HOME" value="${LOG_PATH:-logs}"/>
<property name="FILE_LOG_CHARSET" value="${FILE_LOG_CHARSET:-UTF-8}"/>
<property name="CONSOLE_LOG_CHARSET" value="${CONSOLE_LOG_CHARSET:-UTF-8}"/>
<!--<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } [%t] %-40.40logger{39} : %m%n%wEx"/> -->
<property name="FILE_LOG_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} %-4line : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 所有级别日志都在控制台输出 -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d{yyyy-MM-dd HH:mm:ss.SSS}表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern><![CDATA[${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(%-4L) %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}]]></pattern>
<charset>${CONSOLE_LOG_CHARSET}</charset>
</encoder>
<!--<filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <level>INFO</level>-->
<!-- <onMatch>ACCEPT</onMatch>-->
<!-- <onMismatch>ACCEPT</onMismatch>-->
<!--</filter>-->
</appender>
<!-- debug级别日志,按天生成日志文件 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/debug.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>5</MaxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${FILE_LOG_CHARSET}</charset>
</encoder>
<!--<filter class="com.bruce.kafka.log.LevelAndNameFilter">-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- info级别日志,按天生成日志文件 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>5</MaxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${FILE_LOG_CHARSET}</charset>
</encoder>
<!--自定义filter,让environment日志不在info.log文件中打印-->
<!--<filter class="com.bruce.kafka.log.LevelAndNameFilter">-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- warn error级别日志,按天生成日志文件 -->
<appender name="WARN_ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/warn-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/warn-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>5</MaxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>-->
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${FILE_LOG_CHARSET}</charset>
</encoder>
<!-- 允许打印警告日志到文件的过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<!-- ACCEPT:日志会被立即处理,不再经过剩余过滤器-->
<!-- NEUTRAL:有序列表里的下个过滤器过接着处理日志-->
<onMatch>ACCEPT</onMatch>
<onMismatch>NEUTRAL</onMismatch>
</filter>
<!-- 允许打印错误日志到文件的过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- DENY:日志将立即被抛弃不再经过其他过滤器-->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="Console"/>
<!--<appender-ref ref="DEBUG_FILE"/>-->
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_ERROR_FILE"/>
</root>
<appender name="environmentAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/environment.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/environment.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>5</MaxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } [%t] %-10.40logger{39} : %m%n%wEx</pattern>
<charset>${FILE_LOG_CHARSET}</charset>
</encoder>
</appender>
<!--additivity设为false,让Logger只会在自己的appender里输出,不会在root的logger的appender里输出,
所以Console Appender无法拦截到,控制台也不会打印日志-->
<logger name="environment" level="INFO" additivity="false">
<appender-ref ref="environmentAppender"/>
</logger>
<appender name="customAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/custom.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/custom.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>5</MaxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${FILE_LOG_CHARSET}</charset>
</encoder>
</appender>
<!--指定该包下的日志都打印在customAppender中指定的文件路径-->
<logger name="com.bruce.kafka">
<appender-ref ref="customAppender"/>
</logger>
</configuration>
environment 日志配置的使用
通过LoggerFactory获取名称为environment的Logger对象即可
@SpringBootApplication
public class KafkaProducerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(KafkaProducerApplication.class, args);
Logger envLogger = LoggerFactory.getLogger("environment");
ConfigurableEnvironment environment = context.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
envLogger.info(propertySource.getName());
}
for (PropertySource<?> propertySource : propertySources) {
if (propertySource instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>) propertySource;
String[] propertyNames = enumerablePropertySource.getPropertyNames();
for (String propertyName : propertyNames) {
envLogger.info("[{}] {} = {}", propertySource.getName(), propertyName, enumerablePropertySource.getProperty(propertyName));
}
}
}
}
}
如果想让environment中的日志在控制台展示,但不输入到info.log, warn-error.log中,可以将additivity设置为true,并在INFO
, WARN-ERROR
appender中使用自定义的Filter,过滤掉LoggerName为environment的日志. 参见: ch.qos.logback.classic.filter.LevelFilter
/**
* Created by bruce on 2021/11/30 20:05
*/
public class LevelAndNameFilter extends AbstractMatcherFilter<ILoggingEvent> {
Level level;
@Override
public FilterReply decide(ILoggingEvent event) {
if (!isStarted()) {
return FilterReply.NEUTRAL;
}
if (event.getLoggerName().equals("environment")){
return onMismatch;
}
if (event.getLevel().equals(level)) {
return onMatch;
} else {
return onMismatch;
}
}
public void setLevel(Level level) {
this.level = level;
}
public void start() {
if (this.level != null) {
super.start();
}
}
}
按照SpringBoot环境配置日志
<!-- 不同环境日志输出设置 -->
<springProfile name="default">
<root level="debug">
<appender-ref ref="Console"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_ERROR_FILE"/>
</root>
</springProfile>
<springProfile name="fat | uat">
<root level="info">
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_ERROR_FILE"/>
</root>
</springProfile>
<springProfile name="pro">
<root level="warn">
<appender-ref ref="WARN_ERROR_FILE"/>
</root>
</springProfile>
当应用不以SpringBoot方式启动,仅仅作为一个普通main时,发现控制台彩色日志没有了,此时需要手动调用
AnsiOutput.setEnabled(AnsiOutput.Enabled.ALWAYS);
public static void main(String[] args) {
System.setProperty("java.awt.headless", "true");
AnsiOutput.setEnabled(AnsiOutput.Enabled.ALWAYS);
//SpringApplication.run(NettyHttp2Application.class, args);
Http2Server http2Server = new Http2Server();
http2Server.start(8443);
}
最少依赖为
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--只是在调用AnsiOutput.setEnabled(AnsiOutput.Enabled.ALWAYS);有个校验被用到-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>