本文目录导读:

- 使用日志框架的级别过滤(最常用)
- 使用 Markers 或 MDC 进行上下文过滤
- 使用正则表达式或字符串匹配(自定义过滤器)
- 使用框架内置的高级过滤器(如 ThrottleFilter)
- 使用 AOP(面向切面编程)过滤日志
- 生产环境的最佳实践:动态切换过滤
- 总结对比
使用日志框架的级别过滤(最常用)
这是最基础、最标准的方法,通过配置日志级别(TRACE, DEBUG, INFO, WARN, ERROR),框架会自动过滤掉低于设定级别的日志。
案例:使用 Logback
配置文件 logback.xml
<configuration>
<!-- 定义全局日志级别为 INFO,意味着 TRACE 和 DEBUG 日志不会被打印 -->
<root level="INFO"/>
<!-- 针对某个特定包(如 com.example.dao)设置为 DEBUG 级别 -->
<logger name="com.example.dao" level="DEBUG" additivity="false">
<appender-ref ref="FILE"/>
</logger>
<!-- 针对另一个包(如 com.example.service)只显示 ERROR 级别以上的日志 -->
<logger name="com.example.service" level="ERROR"/>
</configuration>
Java代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public void process() {
log.trace("这是一个跟踪日志"); // 不会被打印(如果全局级别是 INFO)
log.info("这是一个信息日志");
log.error("这是一个错误日志");
}
}
结果: 只有 INFO 及以上的日志会被输出;但 com.example.dao 包下的 DEBUG 日志会被单独保留(输出到文件)。
使用 Markers 或 MDC 进行上下文过滤
当需要基于特定的业务维度(如用户ID、订单号)筛选日志时,使用 MDC(Mapped Diagnostic Context)。
案例:基于用户ID筛选
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public void handleRequest(String userId, String requestData) {
// 将用户ID放入上下文中
MDC.put("userId", userId);
MDC.put("requestType", "API_CALL");
try {
log.info("开始处理用户请求: {}", requestData);
// ... 业务逻辑
log.debug("中间处理细节: {}", someDetail);
} catch (Exception e) {
log.error("处理用户请求失败", e);
} finally {
// 务必清理上下文,防止内存泄漏
MDC.clear();
}
}
}
配合 Logback 配置(使用过滤器):
<appender name="USER_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>
// 只记录 userId = "user123" 的日志
return mdc.get("userId") != null && mdc.get("userId").equals("user123");
</expression>
</evaluator>
<OnMismatch>DENY</OnMismatch> <!-- 不匹配则拒绝 -->
<OnMatch>ACCEPT</OnMatch> <!-- 匹配则接受 -->
</filter>
<encoder>
<pattern>%date [%thread] %-5level %logger{36} - [%X{userId}] %msg%n</pattern>
</encoder>
</appender>
效果: 只有用户ID为 user123 的日志才会被写入该文件,其他用户的日志被过滤掉。
使用正则表达式或字符串匹配(自定义过滤器)
当需要基于日志消息内容(不仅仅是级别)过滤时,可以编写自定义的 TurborFilter 或实现 Logback 的 Filter。
案例:过滤掉所有包含“健康检查”或“心跳”的日志
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class HealthCheckFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
// 如果日志消息包含“健康检查”或“心跳”,则拒绝输出
if (event.getMessage().contains("健康检查") || event.getMessage().contains("心跳")) {
return FilterReply.DENY;
}
return FilterReply.NEUTRAL; // 其他日志正常处理
}
}
配置中引用:
<appender name="MAIN_LOG" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.example.filter.HealthCheckFilter"/>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
使用框架内置的高级过滤器(如 ThrottleFilter)
对于生产环境中的紧急问题排查,可以使用 Logback 的 TurboFilter 实现基于频率的过滤。
案例:限制某个异常日志的打印频率(防止打爆磁盘)
<configuration>
<turboFilter class="ch.qos.logback.classic.turbo.DuplicateMessageFilter">
<!-- 在5秒内,相同内容的日志最多打印10次 -->
<cacheSize>100</cacheSize>
<allowedRepetitions>10</allowedRepetitions>
<cacheExpirationTimeInSeconds>5</cacheExpirationTimeInSeconds>
</turboFilter>
</configuration>
使用 AOP(面向切面编程)过滤日志
如果你需要更复杂的业务逻辑过滤(例如根据方法参数、返回值),可以使用 Spring AOP。
案例:只记录返回值为 null 的方法日志
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
@Around("execution(* com.example.service.*.*(..))")
public Object filterLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
// 只记录返回值为 null 的方法调用
if (result == null) {
log.warn("方法 {} 返回了 null,参数为: {}", joinPoint.getSignature(), joinPoint.getArgs());
}
return result;
}
}
生产环境的最佳实践:动态切换过滤
在生产环境中,通常推荐使用 Elasticsearch + Kibana 或 Splunk 等日志聚合平台,而不是在应用层做复杂的过滤,但依然可以结合 MDC 实现按需过滤:
// 在入口处动态设置
MDC.put("traceId", UUID.randomUUID().toString());
// 在出口处清理
然后在日志系统中(如 Kibana)通过 traceId 字段精确筛选出一次完整的请求链路。
总结对比
| 方法 | 适用场景 | 优缺点 |
|---|---|---|
| 日志级别过滤 | 区分调试/生产环境的日志量 | 简单高效,但不能基于内容过滤 |
| MDC 上下文过滤 | 按用户、请求ID、事务ID筛选 | 灵活,便于链路追踪和问题排查 |
| 自定义过滤器 | (如排除心跳) | 高度定制,但需维护 |
| AOP 过滤 | 按方法执行结果过滤 | 适合业务级监控,但有一定的性能影响 |
| 外部日志平台 | 全量收集后在线搜索 | 最适合生产环境,但需要额外组件 |
推荐方案:
- 开发阶段:使用级别过滤 + MDC
- 生产环境:全量收集到 Elasticsearch 或 Loki,通过 UI 进行动态过滤,同时在应用层只保留 ERROR 和 WARN 级别的日志到本地文件以备紧急查看。