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

如何覆盖Micrometer Core的@TimedAspect适配WebFlux及GraphQL错误指标捕获

WebFlux + GraphQL 下 Micrometer Timed 指标的错误捕获与自定义属性方案

一、解决响应式场景下 TimedAspect 错误捕获失效问题

原生TimedAspect的try/catch只能捕获同步方法异常,Mono/Flux的异步错误根本进不到同步try块里。别硬改原生Aspect,换响应式思路处理:

1. 自定义响应式适配的 Timed Advice

自己写环绕通知,专门处理返回Publisher类型的方法:

@Aspect
@Component
public class ReactiveTimedAspect {
    private final MeterRegistry meterRegistry;
    private final Clock clock;

    public ReactiveTimedAspect(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.clock = Clock.SYSTEM;
    }

    @Around("@annotation(timed)")
    public Object timedMethod(ProceedingJoinPoint pjp, Timed timed) throws Throwable {
        Timer.Sample sample = Timer.start(clock);
        Object result = pjp.proceed();
        
        if (result instanceof Publisher) {
            return ((Publisher<?>) result)
                .doOnSuccess(v -> recordSuccess(sample, timed, pjp))
                .doOnError(e -> recordError(sample, timed, pjp, e));
        } else {
            recordSuccess(sample, timed, pjp);
            return result;
        }
    }

    private void recordSuccess(Timer.Sample sample, Timed timed, ProceedingJoinPoint pjp) {
        Timer timer = buildTimer(timed, pjp, "success");
        sample.stop(timer);
    }

    private void recordError(Timer.Sample sample, Timed timed, ProceedingJoinPoint pjp, Throwable e) {
        Timer timer = buildTimer(timed, pjp, "error");
        sample.stop(timer);
    }

    private Timer buildTimer(Timed timed, ProceedingJoinPoint pjp, String status) {
        return Timer.builder(timed.value().isEmpty() ? pjp.getSignature().getName() : timed.value())
                .tags(timed.extraTags())
                .tag("status", status)
                .tag("method", pjp.getSignature().getName())
                .tag("class", pjp.getTarget().getClass().getSimpleName())
                .register(meterRegistry);
    }
}

这个Advice会在Mono/Flux的onSuccess/onError信号触发时,分别记录成功/错误状态的指标,完美适配响应式场景。

2. 禁用原生 TimedAspect

避免两个Aspect冲突,在启动类排除原生自动配置:

@SpringBootApplication(exclude = {TimedAspectAutoConfiguration.class})
public class AppConfig {
}

二、在 GraphQL Resolver 中添加自定义指标属性

1. 手动构建 Timer 绑定自定义 Tag

在Resolver里直接创建Timer,把Resolver类名、GraphQL操作名等作为Tag:

@Component
public class UserResolver {
    private final Timer getUserTimer;

    public UserResolver(MeterRegistry meterRegistry) {
        this.getUserTimer = Timer.builder("graphql.resolver.execution")
                .tag("resolver", "UserResolver")
                .tag("operation", "getUser")
                .register(meterRegistry);
    }

    public Mono<User> getUser(String id) {
        return Mono.fromCallable(() -> {
                    // 业务逻辑
                    return new User(id, "test");
                })
                .transformDeferred(Micrometer.metricsTimer(getUserTimer));
    }
}

用Micrometer.metricsTimer把Timer绑定到Mono上,自动统计耗时和错误。

2. 用 GraphQL Instrumentation 全局拦截

想统一处理所有Resolver,实现GraphQL的Instrumentation:

@Component
public class GraphQLMetricsInstrumentation implements Instrumentation {
    private final MeterRegistry meterRegistry;

    public GraphQLMetricsInstrumentation(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @Override
    public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
        String operationName = parameters.getOperationName() != null ? parameters.getOperationName() : "unknown";
        Timer.Sample sample = Timer.start(Clock.SYSTEM);
        
        return new SimpleInstrumentationContext<>() {
            @Override
            public void onCompleted(ExecutionResult result, Throwable t) {
                String status = t != null ? "error" : "success";
                Timer timer = Timer.builder("graphql.operation.execution")
                        .tag("operation", operationName)
                        .tag("status", status)
                        .tag("query_type", parameters.getQuery().getDefinitions().get(0).getClass().getSimpleName())
                        .register(meterRegistry);
                sample.stop(timer);
            }
        };
    }

    @Override
    public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters) {
        String fieldName = parameters.getField().getName();
        String resolverName = parameters.getField().getDefinition().getResolverName() != null ? 
                parameters.getField().getDefinition().getResolverName() : "unknown";
        
        Timer.Sample sample = Timer.start(Clock.SYSTEM);
        
        return environment -> {
            try {
                Object result = dataFetcher.get(environment);
                if (result instanceof Publisher) {
                    return ((Publisher<?>) result)
                            .doOnSuccess(v -> recordTimer("success"))
                            .doOnError(e -> recordTimer("error"));
                } else {
                    recordTimer("success");
                    return result;
                }
            } catch (Exception e) {
                recordTimer("error");
                throw e;
            }
        };

        void recordTimer(String status) {
            Timer timer = Timer.builder("graphql.resolver.field")
                    .tag("resolver", resolverName)
                    .tag("field", fieldName)
                    .tag("status", status)
                    .register(meterRegistry);
            sample.stop(timer);
        }
    }
}

这个Instrumentation会分别统计GraphQL整体操作和每个Resolver字段的耗时,自动添加操作名、字段名等自定义Tag。

三、解决直方图无 GraphQL 场景数据的问题

1. 开启 Spring GraphQL 内置指标支持

如果用Spring GraphQL,先打开内置Metrics开关:

spring.graphql.metrics.enabled=true

但内置指标维度有限,建议配合上面的自定义Instrumentation使用。

2. 配置直方图统计自定义 Tag

在application.properties里指定要纳入直方图的Tag维度,确保GraphQL相关Tag被统计:

# 开启HTTP请求直方图
management.metrics.distribution.percentiles-histogram.http.server.requests=true
# 自定义GraphQL指标的直方图
management.metrics.distribution.percentiles-histogram.graphql.resolver.field=true
management.metrics.distribution.percentiles-histogram.graphql.operation.execution=true
# 设置统计分位数
management.metrics.distribution.percentiles=0.5, 0.95, 0.99

3. 避免指标被合并到 /graphql 端点

原生WebFlux会把所有/graphql下的请求合并成一个端点指标,必须用自定义Tag(比如operation、resolver)区分不同GraphQL操作,这样直方图才会展示每个操作的独立数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 23:22:25