You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebFlux性能日志切面异常时无法记录结束信息求助

问题根源分析

你的代码只在Mono/Flux成功完成时才会执行日志逻辑:

  • 当Mono/Flux抛出异常时,flatMap(Mono场景)和collectList().flatMapMany(Flux场景)这些操作符不会被触发,因为错误信号会跳过正常的流处理逻辑,直接终止流,导致logExecutionTime完全没机会执行。
  • 同时未停止的StopWatch也会一直处于运行状态,无法记录准确耗时。
解决方案:用doFinally统一处理最终逻辑

响应式流中,doFinally操作符会在流无论成功完成、出错还是被取消时都执行,完美适配这种需要统一记录结束日志的场景。同时不需要修改原流的结构(比如避免用collectList把Flux转成Mono,破坏原有流式特性)。

修改后的完整代码

@Aspect
@Component
@Slf4j
public class LoggingAspect
{
    @Around("execution(* com.company.service..*(..)))")
    public Object logEndpointPerformance(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
    {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();

        final StopWatch stopWatch = new StopWatch();
        log.debug("Start: {}.{}", className, methodName);
        stopWatch.start();
        
        Object result = proceedingJoinPoint.proceed();

        if(result instanceof Mono){
            return ((Mono<?>) result)
                    .doFinally(signalType -> logExecutionTime(className, methodName, stopWatch));
        }
        else if(result instanceof Flux){
            return ((Flux<?>) result)
                    .doFinally(signalType -> logExecutionTime(className, methodName, stopWatch));
        }
        else{
            logExecutionTime(className, methodName, stopWatch);
            return result;
        }
    }

    private void logExecutionTime(final String className, final String methodName, final StopWatch stopWatch){
        if(stopWatch.isRunning()){
            stopWatch.stop();
        }
        log.debug("Exit: {}.{}", className, methodName);
        log.debug("[ {} mls ] lasted execution of {}.{}", stopWatch.getTotalTimeMillis(), className, methodName);
    }
}

关键修改说明

  1. 替换flatMap/collectList为doFinally:
    • 不管Mono/Flux是成功完成还是抛出异常,doFinally都会执行,确保Exit日志和耗时记录不会丢失。
    • 保留了原响应式流的特性:Flux不需要先收集所有元素再返回,依然是流式输出,不会改变业务逻辑。
  2. 增加StopWatch运行状态判断:
    • 在logExecutionTime里检查stopWatch.isRunning(),避免重复停止或者未启动就停止的情况,确保耗时计算准确。
  3. 优化日志格式:用SLF4J的占位符{}代替字符串拼接,更高效且避免潜在的性能问题。

额外注意事项

  • 如果需要区分成功/失败/取消场景的日志,可以通过doFinally的SignalType参数判断(比如SignalType.ON_COMPLETE/SignalType.ON_ERROR/SignalType.CANCEL),按需调整日志内容。
  • 原代码中collectList会导致Flux必须等待所有元素生成完成后才返回,这可能不符合响应式编程的流式设计初衷,修改后避免了这个问题。

内容的提问来源于stack exchange,提问作者AD90

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:03:25