在工作中需要对Spring Boot日志进行JSON格式化,主要是为了传递给ELK做日志收集平台分析。
考虑了以下几种方案:
- 自定义Converter实现
- 重新覆写Appender和Layout类,自定义实现
- 使用
logstash-logback-encoder
来实现
最后使用logstash-logback-encoder
来实现,今后也方便扩展,在这里总结一下。
在Spring Boot的项目中,主要是使用了LogBack,这里简单介绍一下LogBack,Logback继承自log4j。
Logback的架构非常的通用,适用不同的使用场景。Logback 被分成三个不同的模块:logback-core,logback-classic,logback-access。
logback-core 是其它两个模块的基础。logback-classic 模块可以看作是 log4j 的一个优化版本,它天然的支持 SLF4J,所以你可以随意的从其它日志框架(例如:log4j 或者 java.util.logging)切回到 logack。
logback-access 可以与 Servlet 容器进行整合,例如:Tomcat、Jetty。它提供了 http 访问日志的功能。
在Spring Boot中默认内部集成了LogBack日志依赖,而Spring Boot默认使用了LogBack记录日志信息。所以我们可以直接就进行使用。
一、自定义Converter实现
主要是继承ch.qos.logback.classic.pattern.ClassicConverter
这个类,然后覆写convert方法实现逻辑。我们知道在logback中包含level,msg这些变量,使用Coverter我们便可以定义自己的变量,然后再在convert方法里面进行Json处理。比如这里我们新建类JsonLogConverter,如下:
package net.anumbrella.spring.log.config;
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.json.JSONObject;
/**
* @auther anumbrella
*/
public class JsonLogConverter extends ClassicConverter {
private JSONObject object = new JSONObject();
@Override
public String convert(ILoggingEvent iLoggingEvent) {
object.put("msg",iLoggingEvent.getMessage());
object.put("level", iLoggingEvent.getLevel().levelStr);
object.put("threadName", iLoggingEvent.getThreadName());
object.put("method", iLoggingEvent.getLoggerName());
return object.toString();
}
}
然后再在Resources下面添加logback-converter.xml文件,通过application.properties 配置logging.config=classpath:logback-converter.xml
指定logback的路径,然后logback会继续寻找名为logback-converter.xml的文件并读取相关配置。
这里我们还需要添加
<conversionRule conversionWord="customJson" converterClass="net.anumbrella.spring.log.config.JsonLogConverter" />
这个便是自定转换规则,conversionWord为自定义变量,而converterClass为自定义转换规则。具体logback.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds" packagingData="true">
<conversionRule conversionWord="customJson"
converterClass="net.anumbrella.spring.log.config.JsonLogConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%customJson%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
启动后我们可以在控制台看到具体的JSON日志,如下:
这种方式需要所有变量都要进行处理,同时灵活性也较差。
二、重新覆写Layout类,自定义实现
通过自定义Layout类来实现json输出,主要是继承ch.qos.logback.core.LayoutBase
这个类,然后覆写doLayout方法。比如这里我们新建类JsonLogLayout,如下:
package net.anumbrella.spring.log.config;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;
/**
* @auther anumbrella
*/
public class JsonLogLayout extends LayoutBase<ILoggingEvent> {
@Override
public String doLayout(ILoggingEvent event) {
StringBuilder result = new StringBuilder();
if (null != event) {
result.append("{");
result.append("\"time\":\"");
result.append(System.currentTimeMillis());
result.append("\",");
result.append("\"level\":\"");
result.append(event.getLevel());
result.append("\",");
result.append("\"threadName\":\"");
result.append(event.getThreadName());
result.append("\",");
result.append("\"msg\":\"");
result.append(event.getFormattedMessage().replace("\"", "\\\""));
result.append("\",");
result.append("\"method\":\"");
result.append(event.getLoggerName());
result.append("\"} \n");
}
return result.toString();
}
}
同理我们新建logback-layout.xml文件,然后在application.properties更改指定配置。然后还需要在logback-layout.xml中配置自定义的layout,并添加在encoder里。
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="net.anumbrella.spring.log.config.JsonLogLayout" />
</encoder>
具体XML如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds" packagingData="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="net.anumbrella.spring.log.config.JsonLogLayout" />
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
启动应用后,我们发现一样能实现相同功能。
这种方式和前面类似,所有变量都要进行处理,同时灵活性也较差。
三、使用logstash-logback-encoder
来实现
logstash-logback-encoder作为第三方类库来使用,功能性和扩展性都比较好,github开发者也很活跃,地址。
而且考虑到日志系统主要对接elasticsearch系统,通过logstash提供的类库来实现也是一种不错的解决方案。首先我们在pom.xml中引入类库logstash-logback-encoder
。
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>6.2</version>
</dependency>
然后在logback-logstash.xml中添加:
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<jsonGeneratorDecorator
class="net.logstash.logback.decorate.FeatureJsonGeneratorDecorator"/>
</encoder>
然后设置pattern模式,如下:
<providers>
<pattern>
<pattern>
{
"date": "%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}",
"level": "%level",
"msg": "%msg %n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"
}
</pattern>
</pattern>
</providers>
注意:providers是要包含在encoder下面。
具体XML详情如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds" packagingData="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<jsonGeneratorDecorator
class="net.logstash.logback.decorate.FeatureJsonGeneratorDecorator"/>
<providers>
<pattern>
<pattern>
{
"date": "%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}",
"level": "%level",
"msg": "%msg"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
配置就那么点点,是不是觉得非常的简单,这样就能实现日志json输出,而且如果想要对json进行格式化输入,直接配置更改jsonGeneratorDecorator就可以了。更改为如下:
<jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator"/>
结果如图:
相对来说使用logstash-logback-encoder实现更简单,而且而且官方也提供了其他扩展性功能,也能够实现自定义扩展。
四、扩展
在开发的时候我们希望能够走Spring Boot原生的日志显示,生产时候显示Json或者格式化显示Json。如果可配置那就方便了,其实可以通过logging.config指定不同文件就可以了。但还有一种方式——表达式。logback 在配置文件中支持通过 <if>、<then>、<else>
元素作为条件语句来区分不同的环境。条件处理需要 Janino 环境的支持。需要引入依赖,如下:
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>2.6.1</version>
</dependency>
同时我们可以在logback中定义变量读取配置文件的信息,如下:
<springProperty scope="context" name="logType" source="logging.type" defaultValue="normal" />
接着在表达式里面进行判断:
<if condition='property("logType").equals("normal")'>
......
</if>
代码实例:log
参考
- http://www.logback.cn/
- logback自定义Appender和Layout
- https://github.com/logstash/logstash-logback-encoder