1. 程式人生 > 其它 >springboot公共框架編寫LogFilter整合ELK,支援欄位脫敏

springboot公共框架編寫LogFilter整合ELK,支援欄位脫敏

背景: 公共框架需要編寫一個logfilter,對於依賴公共框架的專案的所有請求響應進行按 規範格式進行日誌列印,同時支援日誌中的特殊欄位進行資料脫敏,脫敏規則是 **XXXX 前兩位用**處理

思路: 1.公共日誌過濾器

2.提供一個日誌忽略註解

3. 提供一個特殊的序列化工具,實現**XXXX

程式碼:

1.註冊過濾器

package com.common.base.config;

import com.common.base.filter.LogFilter;
import com.common.base.filter.RequestWrapperFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置過濾器的載入
 * @Auther: tony_t_peng
 * @Date: 2020-10-19 16:39
 * @Description: 
 */
@Configuration
public class FilterConfig {


    /***
     *  定義一個filter,快取請求--解決http流只能一次讀取的問題
     * @Author tony_t_peng
     * @Date  2020-10-19 16:41
     */
    @Bean
    public RequestWrapperFilter buildRequestWrapperFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        RequestWrapperFilter requestWrapperFilter = new RequestWrapperFilter();
        filterRegistrationBean.setFilter(requestWrapperFilter);
        filterRegistrationBean.addUrlPatterns("*");//配置過濾規則
        filterRegistrationBean.setName("requestWrapperFilter");//設定日誌過濾器
        filterRegistrationBean.setOrder(1);//執行次序
        return requestWrapperFilter;
    }

    /***
     *  順序1--日誌過濾器
     * @Author tony_t_peng
     * @Date  2020-10-19 16:41
     */
    @Bean
    public LogFilter buildReqResFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        LogFilter logFilter = new LogFilter();
        filterRegistrationBean.setFilter(logFilter);
        filterRegistrationBean.addUrlPatterns("*");//配置過濾規則
        filterRegistrationBean.setName("logFilter");//設定日誌過濾器
        filterRegistrationBean.setOrder(2);//執行次序
        return logFilter;
    }

}

  

package com.common.base.filter;

import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/** * 流只能讀取一次 * 定義一個filter,快取請求--解決http流只能一次讀取的問題 * @Auther: tony_t_peng * @Date: 2020-08-06 09:48 * @Description: */ public class RequestWrapperFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { filterChain.doFilter(
new ContentCachingRequestWrapper(httpServletRequest), new ContentCachingResponseWrapper(httpServletResponse)); } }
package com.common.base.filter;

import com.common.base.aspect.ConsoleLogAspect;
import com.common.base.utils.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Auther: tony_t_peng
 * @Date: 2020-10-19 16:31
 * @Description: 
 */
public class LogFilter implements Filter {

    public static Logger logger = LoggerFactory.getLogger(Filter.class);

    @Value("${spring.application.name:#{null}}")
    private String applicationName;

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        String requestBody = "";
        String responseBody="";
        ContentCachingResponseWrapper responseWrapper = null;
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        long startTime = System.currentTimeMillis();
        chain.doFilter(request, response);
        long endTime = System.currentTimeMillis();
        if (req != null && req instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) req;
            requestBody = new String(wrapper.getContentAsByteArray());
        }
        if(response!=null){
            responseWrapper = (ContentCachingResponseWrapper) res;
            responseBody = new String(responseWrapper.getContentAsByteArray());
        }
        //後期整合,整合ELK
        String requestURI = request.getQueryString() == null ? request.getRequestURI() : (request.getRequestURI() + "?" + request.getQueryString());

        if(isNotStaticRequest(requestURI)&&!ConsoleLogAspect.isIgnoreLog()){
            logger.info("請求"+JsonUtil.toJSONString(requestBody) + " 訪問了"+applicationName+" 服務 uri:" + requestURI + ",返回"+responseBody+"  總用時 " + (endTime - startTime) + " 毫秒。");
        }
        responseWrapper.copyBodyToResponse();
    }

    private boolean isNotStaticRequest(String requestURI){
        if(requestURI.endsWith(".jpg")||requestURI.endsWith(".png")||requestURI.endsWith(".html")||requestURI.endsWith(".css")||requestURI.endsWith(".js"))
            return false;
        return true;
    }



}

2.編寫公共框架日誌忽略,需要特殊處理的日誌,支援註解來實現日誌忽略

import java.lang.annotation.*;

/**
 * @Auther: tony_t_peng
 * @Date: 2020-11-10 10:54
 * @Description:
 */
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreLog {

}
@Aspect
@Component
public class ConsoleLogAspect {

    public final  static String IGNORE_LOG="ignore_log";
    public final static ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();

    public static boolean isIgnoreLog(){
        return  threadLocal.get()!=null?threadLocal.get():false;
    }

    @Before("@annotation(com.common.base.annotation.IgnoreLog)")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        ConsoleLogAspect.threadLocal.set(true);
        return pjp.proceed();
    }

}

3.對於特殊需求的日誌,忽略公共日誌處理,方法中專案中進行日誌處理

package com.common.base.serializer;

import com.common.base.utils.StringUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @Auther: tony_t_peng
 * @Date: 2021-06-24 14:23
 * @Description:  字串加密序列化工具  對於欄位用**XXXX來進行加密
 *
 *  用於日誌敏感欄位加密,新增屬性上新增 @JsonSerialize(using = EncryptSerializer.class),
 *  即可用jakson進行加密
 */

@Component
public class EncryptSerializer extends JsonSerializer<String> {

    @Override
    public void serialize(String dataString, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
        String data=dataString;
        if(StringUtil.isNotEmpty(dataString)){
            data=data.length()>2?"**"+data.substring(2,data.length()):"**";
        }
        jsonGenerator.writeString(data);
    }


}

4.測試 controller方法上新增@IgnoreLog,在方法中新增

    @RequestMapping(value = "/convert",method = RequestMethod.POST)
    @IgnoreLog
    public Result<ConvertQuestionResponseMO> convert(@RequestBody @Valid ConvertQuestionRequestMO request)
    {
        logger.info("request:"+JsonUtil.toJSONString(request));
        Result<ConvertQuestionResponseMO> result = convertQuestionService.convertQuestion(request);
        logger.info("request:"+JsonUtil.toJSONString(result));
        return result;
    }

request物件上,對於需要加密的欄位添加註解