SpringBoot+logback 日志打印脱敏,正常获取对象不受影响
添加依赖
注意:springboot版本2.7.0
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency>
自定义枚举值
/** * 脱敏枚举值 */ public enum SensitiveType { DEFAULT, // 默认规则(部分隐藏) PHONE, // 手机号 ID_CARD, // 身份证号 EMAIL, // 邮箱 ADDRESS, //地址 USER_NAME, //用户名 NUMBER; //数字,直接全脱敏 }
自定义注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Sensitive { SensitiveType type() default SensitiveType.DEFAULT; }
重写ClassicConverter
import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import com.ybchen.log.Sensitive; import com.ybchen.log.SensitiveType; import org.slf4j.helpers.MessageFormatter; import java.lang.reflect.Field; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * @description: 敏感日志转换器 * @author: 陈彦斌 * @create: 2024-12-09 18:55 */ public class SensitiveLogConverter extends ClassicConverter { @Override public String convert(ILoggingEvent event) { Object[] args = event.getArgumentArray(); if (args == null || args.length == 0) { return event.getFormattedMessage(); } for (int i = 0; i < args.length; i++) { Object arg = args[i]; if (arg != null) { args[i] = desensitize(arg); } } return MessageFormatter.arrayFormat(event.getMessage(), args).getMessage(); } private Object desensitize(Object obj) { if (obj == null) { return null; } try { Class<?> clazz = obj.getClass(); // 如果对象是集合类型 if (obj instanceof List) { List<?> list = (List<?>) obj; List<Object> newList = new ArrayList<>(); for (Object item : list) { newList.add(desensitize(item)); // 递归处理列表中的每个对象 } return newList; } // 如果对象是普通类型,直接返回 if (isPrimitiveOrWrapper(clazz) || clazz == String.class) { return obj; } // 创建新实例 Object newObj = clazz.getDeclaredConstructor().newInstance(); // 遍历字段 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Object fieldValue = field.get(obj); // 获取原字段值 if (fieldValue == null){ continue; } if (field.isAnnotationPresent(Sensitive.class)) { // 处理标注为 @Sensitive 的字段 if (fieldValue instanceof String) { Sensitive sensitive = field.getAnnotation(Sensitive.class); SensitiveType type = sensitive.type(); field.set(newObj, applyDesensitization((String) fieldValue, type)); }else if ( fieldValue instanceof Integer || fieldValue instanceof Long || fieldValue instanceof BigDecimal || fieldValue instanceof Byte || fieldValue instanceof Short || fieldValue instanceof Float || fieldValue instanceof Double ){ //数字直接脱敏 field.set(newObj, null); }else if (fieldValue instanceof List) { // 如果字段是 List 类型,递归处理其内部元素 List<?> list = (List<?>) fieldValue; List<Object> newList = new ArrayList<>(); for (Object item : list) { newList.add(desensitize(item)); } field.set(newObj, newList); } else { // 对其他非字符串类型的字段,递归脱敏 field.set(newObj, desensitize(fieldValue)); } } else { // 未标注 @Sensitive 的字段直接复制 field.set(newObj, fieldValue); } } return newObj; // 返回脱敏后的新对象 } catch (Exception e) { e.printStackTrace(); return obj; // 异常情况下返回原对象 } } private String applyDesensitization(String value, SensitiveType type) { if (value==null || "".equals(value)){ return ""; } switch (type) { case DEFAULT: return value.replaceAll("^(.{3}).*$", "$1*****"); case PHONE: if (value.length()<11||value.length()>11){ return value.replaceAll("^(.{5}).*$", "$1*****"); } return value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); case ID_CARD: if (value.length()<18){ return value.replaceAll("^(.{5}).*$", "$1*****"); } return value.replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1**********$2"); case EMAIL: if (!value.contains("@")){ return value.replaceAll("^(.{5}).*$", "$1*****"); } return value.replaceAll("(?<=^.{3}).*(?=@)", "*****"); case ADDRESS: if (value.length() <= 9) { return value.replaceAll("(.)(.*)(..)", "$1*$2$3"); } return value.replaceAll("(.{3}).*(.{3})", "$1*****$2"); case USER_NAME: return value.replaceAll("(\\S)\\S(\\S*)", "$1*$2"); default: return value; } } private boolean isPrimitiveOrWrapper(Class<?> clazz) { return clazz.isPrimitive() || clazz == Boolean.class || clazz == Integer.class || clazz == Long.class || clazz == Double.class || clazz == Float.class || clazz == Byte.class || clazz == Short.class || clazz == BigDecimal.class || clazz == Character.class; } }
logback-spring.xml配置
<configuration> <conversionRule conversionWord="sensitive" converterClass="com.ybchen.config.SensitiveLogConverter" /> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %sensitive{%msg}%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="CONSOLE" /> </root> </configuration>
VO类
@Data public class UserVO { @Sensitive(type = SensitiveType.USER_NAME) private String userName; @Sensitive(type = SensitiveType.EMAIL) private String email; @Sensitive(type = SensitiveType.PHONE) private String phone; @Sensitive(type = SensitiveType.ID_CARD) private String idCard; @Sensitive(type = SensitiveType.ADDRESS) private String address; @Sensitive private Integer age; @Sensitive private BigDecimal money; @Sensitive private String content; }
打印日志
@PostMapping("test")
public Object test(@RequestBody List<UserVO> userList){
UserVO vo=new UserVO();
vo.setUserName("张三");
vo.setAddress("广东省广州市天河区xxxxxx号");
vo.setAge(18);
log.info("日志脱敏----info:\r\n{}\r\n{}", userList,vo);
log.info("\r\n");
log.error("日志脱敏----error:\r\n{}\r\n{}", userList,vo);
System.out.println("正常获取脱敏数据 userName:"+vo.getUserName());
System.out.println("正常获取脱敏数据 address:"+vo.getAddress());
return userList;
}
效果


浙公网安备 33010602011771号