ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Springboot backlog MDC + ClassicConverter

Springboot backlog MDC + ClassicConverter 提示文章写完后目录可以自动生成如何生成可参考右边的帮助文档文章目录前言一、Trace filter 实现1、请求入口 MDC 注入2、异步场景 MDC 传递二、Filter 注册三、Sensitive utilsExample ID Card maskerSensitiveUtils四、ClassicConverter 实现五、backlog 配置前言Filter 添加日志MDC变量使用ClassicConverter 实现日志脱敏全链路追踪通过 MDC 透传 traceId、userId 等全局标识串联单次请求的所有日志定位问题更高效全局脱敏日志输出前统一拦截自动对手机号、身份证、银行卡、密码等敏感字段打码一、Trace filter 实现1、请求入口 MDC 注入importcom.ft.mall.common.util.StringUtil;importjakarta.servlet.FilterChain;importjakarta.servlet.ServletException;importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importorg.slf4j.MDC;importorg.springframework.web.filter.OncePerRequestFilter;importjava.io.IOException;importjava.util.UUID;publicclassTraceFilterextendsOncePerRequestFilter{privatestaticfinalStringTRACE_IDtraceId;privatestaticfinalStringUSER_IDuserId;OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{try{StringtraceIdrequest.getHeader(X-Trace-Id);StringuserIdrequest.getHeader(X-User-Id);if(StringUtil.isEmptyStringAfterTrim(traceId)){traceIdUUID.randomUUID().toString();}if(StringUtil.isEmptyStringAfterTrim(userId)){traceIdanonymous;}MDC.put(TRACE_ID,traceId);MDC.put(USER_ID,userId);filterChain.doFilter(request,response);}finally{MDC.remove(TRACE_ID);MDC.remove(USER_ID);}}}2、异步场景 MDC 传递需通过TaskDecorator装饰线程池透传 MDC 上下文importorg.slf4j.MDC;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.task.TaskDecorator;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importjava.util.Map;importjava.util.concurrent.Executor;ConfigurationpublicclassThreadPoolConfig{Bean(asyncExecutor)publicExecutorasyncExecutor(){ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(100);executor.setThreadNamePrefix(async-);// 配置MDC上下文传递装饰器executor.setTaskDecorator(newMdcTaskDecorator());executor.initialize();returnexecutor;}/** * MDC上下文传递装饰器 */staticclassMdcTaskDecoratorimplementsTaskDecorator{OverridepublicRunnabledecorate(Runnablerunnable){// 父线程拷贝MDC上下文MapString,StringcontextMapMDC.getCopyOfContextMap();return()-{try{// 子线程设置MDCif(contextMap!null){MDC.setContextMap(contextMap);}runnable.run();}finally{MDC.clear();}};}}}二、Filter 注册importcom.ft.mall.common.web.filter.RepeatableReadFilter;importjakarta.servlet.Filter;importorg.springframework.boot.web.servlet.FilterRegistrationBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.Ordered;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{/** * 注册可重复读请求体过滤器 * * p拦截所有路径优先级最高确保在 Spring 拦截器和其他 Filter 之前包装请求。 * * return Filter 注册 Bean */BeanpublicFilterRegistrationBeanFilterrepeatableReadFilter(){FilterRegistrationBeanFilterregistrationnewFilterRegistrationBean();registration.setFilter(newTraceFilter());registration.addUrlPatterns(/*);registration.setOrder(Ordered.HIGHEST_PRECEDENCE1);registration.setName(traceFilter);returnregistration;}}三、Sensitive utilsExample ID Card maskerimportjava.util.Set;importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassIdCardMasker{// ---------- 身份证 ----------// 18位6位地区 8位日期 3位顺序 校验位(0-9/X)/** 手机号字段名集合大小写不敏感统一存小写 */publicstaticfinalSetStringMOBILE_KEYSSet.of(id_card,sdcard,cardId,省份证);/** 身份证正则1[3-9] 开头 9 位数字 *//* *(?![0-9Xx]) *[1-9]\d{5} // 6位地区 *(?:19|20)\d{2} // 年份 1900-2099 *(?:0[1-9]|1[0-2]) // 月份 01-12 *(?:0[1-9]|[12]\d|3[01]) // 日期 01-31 *\d{3} // 顺序码 *[\dXx] // 校验位 *(?![0-9Xx]) */privatestaticfinalStringID_CARD_REGEX[1-9]\\d{5}(?:19|20)\\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx];/** 字段名 alternation从 Set 动态构建 */privatestaticfinalStringKEY_ALTMaskerUtils.buildKeyAlternation();privatestaticfinalPatternID_CARD_PATTERNPattern.compile((\(?:KEY_ALT)\\\s*:\\s*\)((?![0-9])ID_CARD_REGEX(?![0-9Xx]))\// KV: mobile13800138000 / mobile:13800138000|((?:KEY_ALT)\\s*[:]\\s*)((?![0-9])ID_CARD_REGEX(?![0-9Xx]))// 全文裸匹配带边界防止从长数字中截取|((?![0-9])ID_CARD_REGEX(?![0-9Xx])),Pattern.CASE_INSENSITIVE);/** 脱敏保留前 6 后 4 */privatestaticfinalintPREFIX6;privatestaticfinalintSUFFIX4;/** * 统一入口支持全文、JSON、KV */publicstaticStringmask(Stringtext){if(textnull||text.isEmpty()){returntext;}MatchermID_CARD_PATTERN.matcher(text);StringBuildersbnewStringBuilder(text.length());while(m.find()){Stringreplacement;if(m.group(2)!null){replacementm.group(1)maskIdCard(m.group(2))\;}elseif(m.group(4)!null){replacementm.group(3)maskIdCard(m.group(4));}else{replacementmaskIdCard(m.group(5));}m.appendReplacement(sb,Matcher.quoteReplacement(replacement));}m.appendTail(sb);returnsb.toString();}privatestaticStringmaskIdCard(StringidCard){if(idCardnull||idCard.length()PREFIXSUFFIX){returnidCard;}StringBuildersbnewStringBuilder(idCard.length());sb.append(idCard,0,PREFIX);sb.append(*.repeat(idCard.length()-SUFFIX-PREFIX));sb.append(idCard,idCard.length()-SUFFIX,idCard.length());returnsb.toString();}}SensitiveUtilsimportcom.ft.mall.common.util.sensitive.IdCardMasker;importcom.ft.mall.common.util.sensitive.MobileMasker;/** * 敏感信息脱敏工具类 */publicclassSensitiveUtils{publicstaticStringconvert(StringlogMsg){if(StringUtil.isEmptyStringAfterTrim(logMsg)){returnlogMsg;}logMsgIdCardMasker.mask(logMsg);returnlogMsg;}}四、ClassicConverter 实现importch.qos.logback.classic.pattern.ClassicConverter;importch.qos.logback.classic.spi.ILoggingEvent;importcom.ft.mall.common.util.SensitiveUtils;publicclassSensitiveLogConverterextendsClassicConverter{OverridepublicStringconvert(ILoggingEventevent){returnconvertStr(event.getFormattedMessage());}publicStringconvertStr(StringlogMsg){returnSensitiveUtils.convert(logMsg);}}五、backlog 配置?xml version1.0 encodingUTF-8?configurationscantruescanPeriod60 seconds!-- 日志基础配置 --propertynameLOG_PATHvalue./logs/propertynameAPP_NAMEvaluespringboot-demo/!-- 注册自定义脱敏转换器conversionWord为日志格式中使用的关键字 --conversionRuleconversionWordsensitiveMsgconverterClasscom.example.config.SensitiveMessageConverter/!-- 控制台输出包含MDC链路信息 脱敏后日志 --appendernameCONSOLEclassch.qos.logback.core.ConsoleAppenderencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{50} - %sensitiveMsg%n/patterncharsetUTF-8/charset/encoder/appender!-- 文件输出按天滚动 --appendernameFILEclassch.qos.logback.core.rolling.RollingFileAppenderfile${LOG_PATH}/${APP_NAME}.log/filerollingPolicyclassch.qos.logback.core.rolling.TimeBasedRollingPolicyfileNamePattern${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log/fileNamePatternmaxHistory30/maxHistory/rollingPolicyencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{50} - %sensitiveMsg%n/patterncharsetUTF-8/charset/encoder/appender!-- 根日志级别 --rootlevelINFOappender-refrefCONSOLE/appender-refrefFILE//root/configuration
返回列表