springboot公共框架編寫LogFilter整合ELK,支援欄位脫敏
阿新 • • 發佈:2021-06-24
背景: 公共框架需要編寫一個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物件上,對於需要加密的欄位添加註解