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

GraphQLAsyncExceptionHandler的onException日志丢失TraceId求助

解决Micronaut+New Relic下GraphQL异常处理器日志丢失TraceId的问题

问题原因

当GraphQL DataFetcher同步抛出异常时,异常会被框架捕获并在独立线程中交给自定义异常处理器处理,此时原Transaction上下文已终止,New Relic无法自动关联TraceId到新线程的日志中。直接使用txn.getToken().link()无效的原因是:Token需要在事务活跃时创建并在新线程中显式关联,但异常抛出后原事务已结束,Token无法正常链接。

解决方案

方案1:手动传递TraceId到异常处理器

通过在DataFetcher中捕获异常并附加TraceId,再在异常处理器中提取并注入MDC,确保日志带上TraceId:

修改DataFetcher代码

@Override
@Trace(dispatcher = true)
public CompletableFuture<DataFetcherResult<SomePojo>> get(DataFetchingEnvironment env) throws Exception {
    NewRelic.setTransactionName(null, getTypeName() + "/" + getFieldName());
    UserContext userContext = userContextFactory.createContextWithWritePermissions(env);
    String traceId = NewRelic.getAgent().getTraceMetadata().getTraceId();
    
    try {
        log.info("test log 1"); // <- log line 1
        SomeDto dto = objectMapper.convertValue(env.getArgument("content"), SomeDto.class);
        // ... 后续业务代码
        return CompletableFuture.completedFuture(DataFetcherResult.success(null));
    } catch (Exception e) {
        // 将TraceId封装到自定义异常中
        throw new TraceableException(traceId, "数据转换失败", e);
    }
}

// 新增自定义异常类
class TraceableException extends RuntimeException {
    private final String traceId;

    public TraceableException(String traceId, String message, Throwable cause) {
        super(message, cause);
        this.traceId = traceId;
    }

    public String getTraceId() {
        return traceId;
    }
}

修改异常处理器代码

@Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
    Throwable exception = handlerParameters.getException();
    String traceId = null;

    // 从自定义异常中提取TraceId
    if (exception instanceof TraceableException) {
        traceId = ((TraceableException) exception).getTraceId();
    }

    // 将TraceId注入MDC,确保日志能带上
    if (traceId != null) {
        MDC.put("traceId", traceId);
    }

    log.info("log line 2");
    
    // 清理MDC,避免污染后续请求
    if (traceId != null) {
        MDC.remove("traceId");
    }

    // 必须返回结果,否则框架会抛出异常
    return DataFetcherExceptionHandlerResult.newResult().build();
}

方案2:利用New Relic Token关联事务上下文

在DataFetcher创建Transaction Token并附加到异常,在异常处理器中显式链接事务,恢复TraceId关联:

修改DataFetcher代码

@Override
@Trace(dispatcher = true)
public CompletableFuture<DataFetcherResult<SomePojo>> get(DataFetchingEnvironment env) throws Exception {
    NewRelic.setTransactionName(null, getTypeName() + "/" + getFieldName());
    UserContext userContext = userContextFactory.createContextWithWritePermissions(env);
    Transaction txn = NewRelic.getAgent().getTransaction();
    Token transactionToken = txn.getToken(); // 创建事务Token
    
    try {
        log.info("test log 1"); // <- log line 1
        SomeDto dto = objectMapper.convertValue(env.getArgument("content"), SomeDto.class);
        // ... 后续业务代码
        transactionToken.expire(); // 正常执行后过期Token
        return CompletableFuture.completedFuture(DataFetcherResult.success(null));
    } catch (Exception e) {
        throw new TokenizedException(transactionToken, "数据转换失败", e);
    }
}

// 新增带Token的自定义异常类
class TokenizedException extends RuntimeException {
    private final Token transactionToken;

    public TokenizedException(Token transactionToken, String message, Throwable cause) {
        super(message, cause);
        this.transactionToken = transactionToken;
    }

    public Token getTransactionToken() {
        return transactionToken;
    }
}

修改异常处理器代码

@Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
    Throwable exception = handlerParameters.getException();
    Token token = null;

    if (exception instanceof TokenizedException) {
        token = ((TokenizedException) exception).getTransactionToken();
    }

    if (token != null && !token.isExpired()) {
        token.link(); // 将当前线程关联到原事务
    }

    log.info("log line 2");

    if (token != null) {
        token.expire(); // 使用后过期Token
    }

    return DataFetcherExceptionHandlerResult.newResult().build();
}

额外注意事项

  1. 确保异常处理器必须返回DataFetcherExceptionHandlerResult,否则GraphQL框架会抛出未处理的异常。
  2. 检查New Relic Java代理版本(建议v7.0+),确保兼容Micronaut的异步线程处理。
  3. 如果使用MDC传递TraceId,需确保日志配置中包含%X{traceId}字段,例如Logback配置:
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %X{traceId}%n</pattern>
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 10:35:18