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));
	
	.....
	}

}