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对象上,对于需要加密的字段添加注解

 

posted @ 2021-06-24 15:17  火羽  阅读(760)  评论(0编辑  收藏  举报