---
name: log-desensitize
description: 为Java/Spring Boot项目集成日志脱敏功能。基于Log4j2 RewriteAppender机制,在日志输出层自动 masking 敏感数据(手机号、身份证、邮箱、银行卡等)。涵盖依赖检查与升级、log4j2配置Rewrite改造、启动类注解添加、se.properties规则文件生成。当用户说"日志脱敏"、"log desensitize"、"日志mask"、"敏感数据脱敏"、"配置日志脱敏"时使用此skill。
---
# 日志脱敏集成
为 Java/Spring Boot 项目集成基于 Log4j2 RewriteAppender 的日志脱敏能力,在日志输出层自动 masking 敏感数据,无需修改业务代码。
---
## 输入格式
用户应提供以下信息(缺少时主动询问):
| 参数 | 必填 | 说明 | 示例 |
|------|------|------|------|
| 项目路径 | 是 | Maven 项目的根目录或启动模块目录 | `/path/to/nicestay-api` |
| log4j2 版本 | 否 | 默认跟随 Spring Boot BOM 管理版本 | `2.24.3` |
| 脱敏规则 | 否 | 默认包含全部内置规则,可指定子集 | `手机号,身份证,邮箱` |
---
## 执行流程
### Task 1: 检查并处理 log4j-core 依赖
**1.1 扫描项目 pom.xml**
在项目根 pom.xml 和核心模块 pom.xml 中搜索 `log4j-core` 依赖:
```xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
```
**1.2 判断处理策略**
| 场景 | 处理方式 |
|------|---------|
| 已存在且指定了版本 | 检查版本号,若非最新稳定版则更新 `<version>` |
| 已存在但版本由 BOM/parent 管理 | 不修改版本,确认版本 ≥ 2.17.1(安全版本)即可 |
| 已存在但标记为 `<optional>true</optional>` | 保留 optional 标记不变 |
| 不存在 | 在核心模块 pom.xml 的 `<dependencies>` 中添加 |
**1.3 版本安全底线**
log4j-core 版本必须 ≥ **2.17.1**(修复 Log4Shell 漏洞)。若检测到低于此版本,必须升级并警告用户。
**1.4 添加依赖模板**
若需新增依赖,添加至核心模块(如 `jeecg-boot-base-core/pom.xml`):
```xml
<!-- log4j2 core (for Log4j2 desensitization RewriteAppender) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<optional>true</optional>
</dependency>
```
> 版本由 Spring Boot BOM 管理时无需显式指定 `<version>`。
---
### Task 2: 更新 log4j2 配置文件 — 为每个 Appender 添加 Rewrite
**2.1 定位或创建 log4j2 配置文件**
按优先级查找现有配置:
1. `log4j2-spring.xml`(Spring Boot 推荐)
2. `log4j2.xml`
3. 若均不存在,在启动模块 `src/main/resources/` 下创建 `log4j2-spring.xml`
**2.2 Rewrite 改造规则**
对配置文件中的**每一个**现有 Appender,执行以下改造:
**改造步骤:**
1. 将原 Appender 的 `name` 改为 `{原名}-inner`(如 `STDOUT` → `STDOUT-inner`)
2. 在原 Appender 位置上方新增一个 `RewriteAppender`,使用原名作为新 Appender 的 `name`
3. `RewriteAppender` 内部引用 `{原名}-inner` 的 AppenderRef
4. 配置 `RewritePolicy` 使用 `se.properties` 中的脱敏规则
**改造前示例:**
```xml
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
</encoder>
</appender>
```
**改造后示例(Log4j2 格式):**
```xml
<!-- 原始 Appender 改名为 STDOUT-inner -->
<Console name="STDOUT-inner">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n"/>
</Console>
<!-- 新增 RewriteAppender 使用原名 STDOUT -->
<Rewrite name="STDOUT">
<AppenderRef ref="STDOUT-inner"/>
<RewritePolicy>
<!-- 脱敏规则通过 PropertiesRewritePolicy 或自定义 Plugin 加载 se.properties -->
</RewritePolicy>
</Rewrite>
```
**2.3 同步更新所有 AppenderRef 引用**
修改 `<root>` 或 `<logger>` 中的 `appender-ref`,确保引用名称与 Rewrite 后的名称一致:
```xml
<root level="INFO">
<appender-ref ref="STDOUT"/> <!-- 引用 Rewrite 后的名称 -->
<appender-ref ref="FILE"/> <!-- 引用 Rewrite 后的名称 -->
</root>
```
**2.4 完整的 log4j2-spring.xml 模板**
若需从零创建,参考以下结构(按项目实际 Appender 增减):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<properties>
<property name="LOG_HOME">../logs</property>
</properties>
<Appenders>
<!-- ===== Console ===== -->
<Console name="STDOUT-inner">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n"/>
</Console>
<Rewrite name="STDOUT">
<AppenderRef ref="STDOUT-inner"/>
<PropertiesRewritePolicy>
<Property name="sensitive.pattern.file">${sys:user.dir}/se.properties</Property>
</PropertiesRewritePolicy>
</Rewrite>
<!-- ===== Rolling File ===== -->
<RollingFile name="FILE-inner"
fileName="${LOG_HOME}/app.log"
filePattern="${LOG_HOME}/app-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="10MB"/>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<Rewrite name="FILE">
<AppenderRef ref="FILE-inner"/>
<PropertiesRewritePolicy>
<Property name="sensitive.pattern.file">${sys:user.dir}/se.properties</Property>
</PropertiesRewritePolicy>
</Rewrite>
<!-- ===== Error HTML ===== -->
<File name="HTML-inner" fileName="${LOG_HOME}/error-log.html">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<HtmlLayout title="Error Log" locationInfo="true"/>
</File>
<Rewrite name="HTML">
<AppenderRef ref="HTML-inner"/>
<PropertiesRewritePolicy>
<Property name="sensitive.pattern.file">${sys:user.dir}/se.properties</Property>
</PropertiesRewritePolicy>
</Rewrite>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="STDOUT"/>
<!-- 生产环境按需打开 -->
<!-- <AppenderRef ref="FILE"/> -->
<!-- <AppenderRef ref="HTML"/> -->
</Root>
</Loggers>
</Configuration>
```
> **注意**:若项目使用 logback 而非 log4j2,需要先排除 `spring-boot-starter-logging`,再添加 `spring-boot-starter-log4j2`。具体操作见下方"日志框架切换"章节。
**2.5 名称同步检查清单**
改造完成后,逐一验证:
```
Task Progress:
- [ ] 每个原 Appender 已改名为 {原名}-inner
- [ ] 每个 RewriteAppender 使用了原 Appender 的名称
- [ ] <root> 和 <logger> 中的 appender-ref 指向 Rewrite 后的名称
- [ ] 无遗漏的 appender-ref 仍指向 -inner 名称
```
---
### Task 3: 在启动类添加注解
**3.1 定位启动类**
查找项目中的 Spring Boot 启动类(含 `@SpringBootApplication` 注解的类)。
**3.2 添加日志脱敏注解**
在启动类上添加 `@EnableLogDesensitize` 注解:
```java
import org.jeecg.common.desensitization.annotation.EnableLogDesensitize;
@Slf4j
@SpringBootApplication
@EnableLogDesensitize // 启用日志脱敏
public class JeecgSystemApplication {
// ...
}
```
**3.3 注解定义**
若项目中不存在 `@EnableLogDesensitize` 注解,需创建:
```java
package org.jeecg.common.desensitization.annotation;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(LogDesensitizeConfig.class)
public @interface EnableLogDesensitize {
}
```
**3.4 配置类**
若项目中不存在 `LogDesensitizeConfig`,需创建:
```java
package org.jeecg.common.desensitization.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LogDesensitizeConfig {
// 加载 se.properties 中的脱敏规则
// 注册 Log4j2 RewritePolicy(若需要编程式注册)
}
```
> 如果项目已通过 log4j2.xml 中的 `PropertiesRewritePolicy` 直接加载 `se.properties`,此配置类可简化为空壳,仅作为注解的 Import 目标。
---
### Task 4: 生成 se.properties 配置文件
**4.1 文件位置**
在启动模块的 `src/main/resources/` 下创建 `se.properties`。
**4.2 默认脱敏规则**
```properties
# ============================================
# 日志脱敏规则配置 (se.properties)
# 格式: sensitive.{规则名}={正则表达式}|{替换模板}
# ============================================
# 手机号脱敏: 138****1234
sensitive.mobile=(\\d{3})\\d{4}(\\d{4})|$1****$2
# 身份证号脱敏: 110***********1234
sensitive.idcard=(\\d{3})\\d{11}(\\d{4})|$1***********$2
# 邮箱脱敏: t****@example.com
sensitive.email=(.{1})[^@]*(@.*)|$1****$2
# 银行卡脱敏: 6222****1234
sensitive.bankcard=(\\d{4})\\d+(\\d{4})|$1****$2
# 中文姓名脱敏: 张*
sensitive.chinese_name=([\\u4e00-\\u9fa5]){1}([\\u4e00-\\u9fa5])+|$1*
# 地址脱敏: 保留前6个字符
sensitive.address=(.{6}).*|$1****
# 固定电话脱敏: 010****1234
sensitive.phone=(\\d{3,4})\\d+(\\d{4})|$1****$2
```
**4.3 规则格式说明**
```
sensitive.{规则名}={正则表达式}|{替换模板}
```
- **规则名**:唯一标识,用于日志标记
- **正则表达式**:Java 正则语法,用 `()` 分组捕获
- **替换模板**:`$1`、`$2` 引用正则分组,其余字符原样输出
- 规则按声明顺序依次匹配,互不干扰
**4.4 自定义规则扩展**
用户可按需添加新规则,格式同上。例如:
```properties
# 自定义: 订单号脱敏(保留前4后4)
sensitive.order_no=(.{4}).+(.{4})|$1****$2
```
---
## 日志框架切换(仅在需要时执行)
若项目当前使用 logback 且需切换到 log4j2 才能使用 RewriteAppender:
**Step 1: 排除 logback**
在启动模块 pom.xml 中:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
```
**Step 2: 添加 log4j2 starter**
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
```
**Step 3: 删除或重命名 logback-spring.xml**
避免两个日志框架冲突。
---
## 验证清单
完成全部配置后,逐项验证:
```
Task Progress:
- [ ] log4j-core 依赖已存在且版本 ≥ 2.17.1
- [ ] log4j2 配置文件中每个 Appender 都有对应的 Rewrite 包装
- [ ] 所有 appender-ref 引用名称已同步更新为 Rewrite 后的名称
- [ ] 启动类已添加 @EnableLogDesensitize 注解
- [ ] se.properties 已生成且正则语法正确
- [ ] mvn clean compile 编译通过
- [ ] 启动应用后日志中敏感数据已被 masking
```
---
## 异常处理
| 场景 | 处理方式 |
|------|---------|
| log4j-core 版本低于 2.17.1 | 必须升级,存在 Log4Shell 安全漏洞 |
| 项目同时存在 logback 和 log4j2 | 确认实际生效的日志框架,避免配置冲突 |
| RewriteAppender 不生效 | 检查 log4j2 配置文件是否被正确加载(文件名、classpath 位置) |
| se.properties 正则语法错误 | 启动时会报 PatternSyntaxException,按提示修正正则 |
| 切换日志框架后启动失败 | 检查是否有其他组件依赖 logback 特有 API |
---
## 常见陷阱提醒
1. **logback 与 log4j2 共存冲突**:Spring Boot 默认使用 logback,切换到 log4j2 必须排除 `spring-boot-starter-logging`,否则会出日志重复输出或启动异常
2. **RewriteAppender 名称必须匹配**:如果 `<root>` 中引用的是 `STDOUT`,但 Rewrite 后的 Appender 名称不是 `STDOUT`,日志将丢失且无报错
3. **se.properties 路径问题**:`${sys:user.dir}` 指向 JVM 启动目录,可能不是项目根目录。建议用 `${sys:user.dir}/se.properties` 或 classpath 方式加载
4. **正则性能**:复杂正则在高频日志场景下可能影响性能,建议用 `JMH` 或简单压测验证
5. **HTML 格式日志**:RewriteAppender 对 HTMLLayout 输出的脱敏效果有限,HTML 标签可能干扰正则匹配
6. **版本管理**:log4j-core 版本应跟随 Spring Boot BOM,不要硬编码版本号
版权归属:
天明
许可协议:
本文使用《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》协议授权
评论区