Logback 是一个强大且灵活的日志框架,适用于各种规模的应用程序。通过自定义 Appender,可以实现复杂的日志处理逻辑,如敏感信息脱敏和异步写入,从而提升系统的安全性和性能。
工作流程
图片
为什么需要自定义 Appender?
在某些情况下,默认的 Appender 无法满足特定需求,例如:
- 自动脱敏敏感信息。
- 异步处理日志以提高性能。
- 将日志发送到外部系统或服务。
代码实操
<!-- Disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
<!-- Logback Classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
创建自定义Appender
创建一个名为SensitiveDataMaskingAppender的类,该类继承自AppenderBase<ILoggingEvent>,并在其中使用Disruptor队列进行异步处理。
package com.example.demo.logging;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import org.slf4j.MDC;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
publicclass SensitiveDataMaskingAppender extends AppenderBase<ILoggingEvent> {
// 正则表达式模式用于匹配身份证号码
privatestaticfinal Pattern ID_CARD_PATTERN = Pattern.compile("\\d{15}(\\d{2}[A-Za-z])?");
// 正则表达式模式用于匹配手机号码
privatestaticfinal Pattern PHONE_NUMBER_PATTERN = Pattern.compile("(\\+86)?(1[3-9]\\d{9})");
private RingBuffer<Event> ringBuffer;
@Override
public void start() {
super.start();
// 创建事件工厂
EventFactory<Event> factory = Event::new;
// 设置环形缓冲区大小,必须是2的幂
int bufferSize = 1024;
// 使用缓存线程池
Executor executor = Executors.newCachedThreadPool();
// 创建Disruptor实例
Disruptor<Event> disruptor = new Disruptor<>(factory, bufferSize, executor,
ProducerType.MULTI, new BusySpinWaitStrategy());
// 设置事件处理器
disruptor.handleEventsWith(new EventHandler<Event>() {
@Override
public void onEvent(Event event, long sequence, boolean endOfBatch) throws Exception {
// 脱敏日志消息
String logMessage = maskSensitiveData(event.getLogMessage());
// 打印脱敏后的日志消息到控制台
System.out.println(logMessage);
}
});
// 获取RingBuffer
ringBuffer = disruptor.getRingBuffer();
// 启动Disruptor
disruptor.start();
}
@Override
protected void append(ILoggingEvent eventObject) {
// 获取下一个序列号
long sequence = ringBuffer.next();
try {
// 根据序列号获取事件对象
Event event = ringBuffer.get(sequence);
// 设置日志消息
event.setLogMessage(eventObject.getMessage());
} finally {
// 发布事件
ringBuffer.publish(sequence);
}
}
/**
* 脱敏日志消息中的敏感信息
* @param message 日志消息
* @return 脱敏后的日志消息
*/
private String maskSensitiveData(String message) {
Matcher idCardMatcher = ID_CARD_PATTERN.matcher(message);
while (idCardMatcher.find()) {
// 替换身份证号码中间部分为星号
String maskedIdCard = idCardMatcher.group().substring(0, 6) + "********" + idCardMatcher.group().substring(14);
message = message.replace(idCardMatcher.group(), maskedIdCard);
}
Matcher phoneNumberMatcher = PHONE_NUMBER_PATTERN.matcher(message);
while (phoneNumberMatcher.find()) {
// 替换手机号码中间部分为星号
String maskedPhoneNumber = phoneNumberMatcher.group().substring(0, 3) + "****" + phoneNumberMatcher.group().substring(7);
message = message.replace(phoneNumberMatcher.group(), maskedPhoneNumber);
}
return message;
}
// 定义事件类
privatestaticclass Event {
private String logMessage;
public String getLogMessage() {
return logMessage;
}
public void setLogMessage(String logMessage) {
this.logMessage = logMessage;
}
}
}
配置Logback使用自定义Appender
在src/main/resources/logback-spring.xml文件中配置自定义appender:
<configuration>
<!-- 自定义Appender配置 -->
<appender name="SENSITIVE_MASKING_APPENDER" class="com.example.demo.logging.SensitiveDataMaskingAppender">
</appender>
<!-- 根Logger配置 -->
<root level="info">
<appender-ref ref="SENSITIVE_MASKING_APPENDER"/>
</root>
</configuration>
创建Controller、Service和Repository层
Controller层
创建一个简单的控制器来测试日志记录功能。
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
publicclass UserController {
// 获取日志记录器
privatestaticfinal Logger logger = LoggerFactory.getLogger(UserController.class);
/**
* 处理/user请求,记录用户信息并返回响应
* @param idCard 用户身份证号码
* @param phoneNumber 用户手机号码
* @return 响应字符串
*/
@GetMapping("/user")
public String getUserInfo(@RequestParam String idCard, @RequestParam String phoneNumber) {
// 记录用户信息到日志
logger.info("User Info - ID Card: {}, Phone Number: {}", idCard, phoneNumber);
return"User info logged";
}
}
启动类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
测试
curl "http://localhost:8080/user?idCard=123456123456123456&phnotallow=13800138000"
控制台日志
User Info - ID Card: 123456********56, Phone Number: 138****8000