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(); }
额外注意事项
- 确保异常处理器必须返回
DataFetcherExceptionHandlerResult,否则GraphQL框架会抛出未处理的异常。 - 检查New Relic Java代理版本(建议v7.0+),确保兼容Micronaut的异步线程处理。
- 如果使用MDC传递TraceId,需确保日志配置中包含
%X{traceId}字段,例如Logback配置:<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %X{traceId}%n</pattern>
内容的提问来源于stack exchange,提问作者Anil Bhaskar
相关产品推荐
相关产品推荐

