如何使用 gRPC 拦截器在 Spring-Boot 应用程序中附加/更新日志记录 MDC

Jim*_* M. 9 slf4j mdc spring-boot grpc grpc-java

问题

我有一个 Spring-Boot 应用程序,我也在其中启动了 gRPC 服务器/服务。servlet 和 gRPC 代码都将请求发送到一个公共对象来处理请求。当请求进来时,我想更新日志以显示唯一的“ID”,以便我可以通过系统跟踪请求。

在 Spring 方面,我设置了一个“过滤器”,它更新日志记录 MDC 以向日志请求添加一些数据(请参阅此示例)。这工作正常

在 gRPC 方面,我创建了一个“ServerInterceptor”并将其添加到服务中,而拦截器被调用,更新 MDC 的代码不会粘住,因此当请求通过 gRPC 服务时,我没有打印出 ID日志。我意识到这与我在一个线程中拦截调用并且它在另一个线程中由 gRPC 调度这一事实有关,我似乎无法弄清楚如何在执行工作的线程中拦截调用或添加 MDC 信息,以便将其正确传播到执行工作的线程。

我试过的

我做了很多搜索,很惊讶没有找到这个问题/答案,我只能假设我的查询技巧缺乏:(

我对 gRPC 相当陌生,这是我正在编写的第一个拦截器。我已经尝试以几种不同的方式添加拦截器(通过 ServerInterceptors.intercept、BindableService instance.intercept)。

我看过LogNet 的 Spring Boot gRPC Starter,但我不确定这是否能解决问题。

这是我在拦截器类中添加的代码

@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<ReqT, RespT> call, final Metadata headers, final ServerCallHandler<ReqT, RespT> next) {
    try {
        final String mdcData = String.format("[requestID=%s]",
            UUID.randomUUID().toString());
    MDC.put(MDC_DATA_KEY, mdcData);
    return next.startCall(call, headers);
    } finally {
        MDC.clear();
    }
}
Run Code Online (Sandbox Code Playgroud)

预期结果

当请求通过 RESTful API 传入时,我会看到这样的日志输出

2019-04-09 10:19:16.331 [requestID=380e28db-c8da-4e35-a097-4b8c90c006f4] INFO 87100 --- [nio-8080-exec-1] c.c.es.xxx: processing request step 1
2019-04-09 10:19:16.800 [requestID=380e28db-c8da-4e35-a097-4b8c90c006f4] INFO 87100 --- [nio-8080-exec-1] c.c.es.xxx: processing request step 2
2019-04-09 10:19:16.803 [requestID=380e28db-c8da-4e35-a097-4b8c90c006f4] INFO 87100 --- [nio-8080-exec-1] c.c.es.xxx: Processing request step 3
...
Run Code Online (Sandbox Code Playgroud)

当请求通过 gRPC 服务时,我希望得到类似的输出。

谢谢

Jim*_* M. 7

由于没有人回复,我不断尝试并为我的interceptCall函数提出了以下解决方案。我不是 100% 确定这为什么有效,但它适用于我的用例。

    private class LogInterceptor implements ServerInterceptor {
        @Override
        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<ReqT, RespT> call,
                                                                     final Metadata headers,
                                                                     final ServerCallHandler<ReqT, RespT> next) {
            Context context = Context.current();
            final String requestId = UUID.randomUUID().toString();
            return Contexts.interceptCall(context, call, headers, new ServerCallHandler<ReqT, RespT>() {
                @Override
                public ServerCall.Listener<ReqT> startCall(ServerCall<ReqT, RespT> call, Metadata headers) {

                    return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
                        /**
                         * The actual service call happens during onHalfClose().
                         */
                        @Override
                        public void onHalfClose() {
                            try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put("requestID",
                                    UUID.randomUUID().toString())) {
                                super.onHalfClose();
                            }
                        }
                    };
                }
            });
        }
    }
Run Code Online (Sandbox Code Playgroud)

在我的 application.properties 中,我添加了以下内容(我已经有了)

logging.pattern.level=[%X] %-5level

'%X' 告诉日志系统打印所有CloseableThreadContext键/值。

希望这可以帮助别人。