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

浙公网安备 33010602011771号