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