logback access event 自定义字段
一、logback介绍
二、logback-access介绍
2.1 logback-access.xml 配置
三、使用问题
四、解决过程
logback access event 自定义字段
一、logback介绍
Logback是由log4j创始人设计的一个开源日志组件。LogBack被分为3个组件,logback-core, logback-classic 和 logback-access。
相信大家很多人都会用到logback 或者其他类似的日志框架、我们多数会主动在代码的某处调用 log.info()进行记录关键信息。
这里主要介绍logback-access 的一些用法
二、logback-access介绍
logback-access:
调用接口的时候,对于每一个调用都记录一下访问日志。方便后面的请求追踪
一般如果服务中有使用到gateway服务的话,可以考虑使用logback-access进行记录每个http请求的信息
比如当访问localhost:8080/hello的时候。可以打印json格式的日志 。(这个过程不需要你做其他的代码。在logback-access.xml配置文件配置就好 )
2.1 logback-access.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>common</pattern>
</encoder>
</appender>
<springProperty scope="context" name="LogstashEable"
source="logstash.enable" defaultValue="false" />
<if condition='${LogstashEable}'>
<then>
<springProperty scope="context"
name="LogstashDestination" source="logstash.destination" />
<appender name="LOGSTASH"
class="net.logstash.logback.appender.LogstashAccessTcpSocketAppender">
<destination>${LogstashDestination}</destination>
<encoder
class="net.logstash.logback.encoder.LogstashAccessEncoder"></encoder>
</appender>
<appender-ref ref="LOGSTASH" />
</then>
</if>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/tmp/logs/gateway-access.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
</rollingPolicy>
<encoder
class="net.logstash.logback.encoder.LogstashAccessEncoder">
</encoder>
</appender>
<!-- <appender-ref ref="FILE" />-->
<!-- <appender-ref ref="CONSOLE" />-->
</configuration>
展示结果如下:
{
"@timestamp":"2019-07-02T16:12:47.059+08:00",
"@version":1,
"@message":"0:0:0:0:0:0:0:1 - - [2019-07-02T16:12:47.059+08:00] "GET /hello HTTP/1.1" 200 60",
"@fields.method":"GET",
"@fields.protocol":"HTTP/1.1",
"@fields.status_code":200,
"@fields.requested_url":"GET /hello HTTP/1.1",
"@fields.requested_uri":"/hello",
"@fields.remote_host":"0:0:0:0:0:0:0:1",
"@fields.HOSTNAME":"0:0:0:0:0:0:0:1",
"@fields.content_length":60,
"@fields.elapsed_time":10,
"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
}
三、使用问题
logback-access的Json里面都是默认拥有的成员字段。每个字段拥有的含义。都是Http协议相关的字段。
显然如果在我们的业务系统中。这样字段有时候不能满足我们的需求。比如我想记录。每个调用api的memberId。方面去做一个track(当然也有其他的track方案。这里只讨论使用logback-access的情况)
四、解决过程
1. 优先看官方文档,结果找了很久很久的官方文档(或者stackoverflow)。都没有对应的官方api提供(感觉是官方不再更新这个文档了)。有兴趣的可以去看看
2. 只能debug 方式来研究了。通过debug的方式可以得知。
net.rakugakibox.spring.boot.logback.access.LogbackAccessContext
这个类中有个putProperty方法用来赋值logback-access的Json的值。所以我们只需要在这个类初始化之前。把我们想要的字段赋值进去。再完成对应bean的初始化。就可以达到我们想要的效果。
另外我们可以知道LogbackAccessContext是在TomcatServletWebServerFactory的成员对象LogbackAccessTomcatValve里面。所以我们需要先提前获取TomcatServletWebServerFactory对象。
写个LogbackAccessContextUtil我们自定义的工具类
核心代码如下:
import lombok.extern.slf4j.Slf4j;
import net.rakugakibox.spring.boot.logback.access.LogbackAccessContext;
import net.rakugakibox.spring.boot.logback.access.tomcat.LogbackAccessTomcatValve;
import org.apache.catalina.Valve;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import java.lang.reflect.Field;
import java.util.List;
/**
* @ClassName: LogbackAccessContextUtil
* @Description: manager logbackAccessContext class
* @Author: linl
* @Date: 2019-04-24 10:54
**/
@Slf4j
public class LogbackAccessContextUtil {
// 通过spring的ApplicationContext getBean
private static TomcatServletWebServerFactory tomcatServletWebServerFactory = ApplicationContextHolder.getApplicationContext().getBean(TomcatServletWebServerFactory.class);;
private static LogbackAccessContext logbackAccessContext;
{
// init LogbackAccessContext
List<Valve> values = (List<Valve>) tomcatServletWebServerFactory.getEngineValves();
LogbackAccessTomcatValve accessTomcatValve = null;
for (Valve value : values) {
if (value.getClass() == LogbackAccessTomcatValve.class) {
accessTomcatValve = (LogbackAccessTomcatValve) value;
break;
}
}
//get logbackAccessContext from the logbackAccessTomcatValve by invoking.
LogbackAccessContext logbackAccessContext = null;
Field field = null;
try {
field = accessTomcatValve.getClass().getDeclaredField("logbackAccessContext");
field.setAccessible(true);
logbackAccessContext = (LogbackAccessContext) field.get(accessTomcatValve);
} catch (Exception e) {
log.error("Exception happened when fetching the logback access context ."
+ e.getMessage());
}
//get logstashAccessEncoder from the logbackAccessContext
if (logbackAccessContext == null) {
log.error("AccessFilter logbackAccessContext == null error");
throw new BusinessException(BaseResultEnum.EXCEPTION.getCode(),
BaseResultEnum.EXCEPTION.getMsg());
}
this.logbackAccessContext = logbackAccessContext;
}
/* -------------------- Single —————————————————— */
private static LogbackAccessContextUtil instance;
private LogbackAccessContextUtil() {
}
public static LogbackAccessContextUtil getInstance() {
if (instance == null) {
instance = new LogbackAccessContextUtil();
}
return instance;
}
/* -------------------- Single —————————————————— */
/**
* @Author linl
* @Description update AccessLog Property
* @Date 2019-04-24 11:02
* @Param [key, value]
* @return void
**/
public static void putProperty(String key, String value) {
getInstance().logbackAccessContext.putProperty(key, value);
}
/**
* @Author linl
* @Description clean AccessLog Property
* @Date 2019-04-24 11:07
* @Param []
* @return void
**/
public static void reset() {
getInstance().logbackAccessContext.reset();
//putProperty env after property reset
LogbackAccessContextUtil.putProperty("env", ApplicationContextHolder.getApplicationContext()
.getEnvironment().getActiveProfiles()[0]);
}
}
然后我们在自己的gatewayServer 微服务 AccessFilter.class拦截类 使用上面写好的工具类。提前加入我们想要的自定义字段
代码如下
@Autowired
private HttpServletRequest httpServletRequest;
@Slf4j
@Component
public class AccessFilter extends ZuulFilter {
@Override
public Object run() throws BusinessException {
LogbackAccessContextUtil.putProperty("ip",httpServletRequest.getHeader(HeaderCode.FORWARDED_IP));
LogbackAccessContextUtil.putProperty("referer",httpServletRequest.getHeader(HeaderCode.REFERER));
LogbackAccessContextUtil.putProperty("userAgent",httpServletRequest.getHeader(HeaderCode.USER_AGENT));
LogbackAccessContextUtil.putProperty(GeneralConstantKey.APPID, httpServletRequest.getHeader(HeaderCode.APP_ID));
.....
}
}