背景
公司使用的
Protobuf
与Grpc-java
相关框架,对于调用第二/三方接口的请求日志统一打印一直是一个问题
思路一:
如下图所示:
Protobuf
生成的xxxGrpc相关类把请求参数和响应参数放在了requestMarshaller
和responseMarshaller
里面,我们只需要拿到Marshaller
并重写Marshaller
的stream()
和parse()
即可,然后在start()
或者初始化Grpc时向MethodDescriptor.Builder
提供自己的Marshaller
Grpc类
Marshaller接口
初始化并装载Grpc
package com.ypshengxian.store.server.infrastructure.marshaller;
import io.grpc.MethodDescriptor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 描述:
* 思路:proto文件生成的所有Grpc类都有SERVICE_NAME(文件唯一全限定名)
* 且所有文件都将请求和响应放在了RequestMarshaller和ResponseMarshaller中
*
* Created by zjw on 2021/9/9 13:52
*/
@Slf4j
public class GlobalMarshallerInitializer {
private static final String SERVICE_NAME = "SERVICE_NAME";
private static final String requestMarshaller = "requestMarshaller";
private static final String responseMarshaller = "responseMarshaller";
private GlobalMarshallerInitializer(){
throw new IllegalStateException("Utility class");
}
/**
* 把grpc中的requestMarshaller注册到JsonLoggerMarshaller进行json日志输出
* @param serviceClass
*/
public static void initiateJsonLoggerMarshaller(Class<?> serviceClass) {
try {
Field serviceNameField = serviceClass.getField(SERVICE_NAME);
if (serviceNameField == null) return;
for (Method m : serviceClass.getDeclaredMethods()) {
if (Modifier.isStatic(m.getModifiers()) && m.getReturnType() == MethodDescriptor.class) {
// 为各RPC调用方法生成默认的MethodDescriptor,默认MethodDescriptor中的Marshaller使用的是二进制格式传输报文
MethodDescriptor<?, ?> md = (MethodDescriptor<?, ?>) m.invoke(null);
Field requestMarshallerField = md.getClass().getDeclaredField(requestMarshaller);
Field responseMarshallerField = md.getClass().getDeclaredField(responseMarshaller);
// 使用反射机制设置输入与输出Marshaller
requestMarshallerField.setAccessible(true);
requestMarshallerField.set(md, new JsonLoggerMarshaller(md.getRequestMarshaller()));
requestMarshallerField.setAccessible(false);
responseMarshallerField.setAccessible(true);
responseMarshallerField.set(md, new JsonLoggerMarshaller(md.getResponseMarshaller()));
responseMarshallerField.setAccessible(false);
log.info("initialization success~" + md.getFullMethodName());
}
}
} catch (Exception e) {
log.error("JsonLoggerMarshaller initialization fail~" + serviceClass.getName(), e);
}
}
}
重写Grpc的Marshaller接口
package com.ypshengxian.store.server.infrastructure.marshaller;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import com.google.protobuf.util.JsonFormat.Printer;
import com.ypshengxian.store.server.infrastructure.utils.EnvLogPrintUtil;
import io.grpc.MethodDescriptor.Marshaller;
import java.io.InputStream;
/**
* 描述:打印
* Created by zjw on 2021/9/9 14:54
*/
public class JsonLoggerMarshaller<T extends Message> implements Marshaller<T> {
private final Marshaller<T> baseMarshaller;
private final Printer printer = JsonFormat.printer().omittingInsignificantWhitespace();
public JsonLoggerMarshaller(Marshaller<T> baseMarshaller) {
this.baseMarshaller = baseMarshaller;
}
public InputStream stream(T value) {
try {
String info = printer.print(value);
EnvLogPrintUtil.info("input:" + info);
} catch (Exception e) {
EnvLogPrintUtil.error("print InputStream error", e);
}
return baseMarshaller.stream(value);
}
public T parse(InputStream stream) {
T msg = baseMarshaller.parse(stream);
try {
String info = printer.print(msg);
EnvLogPrintUtil.info("output:" + info);
} catch (Exception e) {
EnvLogPrintUtil.error("InputStream parse error", e);
}
return msg;
}
}
使用
在Rpc注册到Spring容器时初始化Grpc,这样此Grpc在调用其他接口时便会相关请求、响应
@Bean
public UserCartGrpc.UserCartBlockingStub userCartBlockingStub(ManagedChannelFactory factory) {
GlobalMarshallerInitializer.initiateJsonLoggerMarshaller(UserCartGrpc.class);
return UserCartGrpc.newBlockingStub(factory.create(UserCartGrpc.SERVICE_NAME)).withInterceptors(new GrpcClientInterceptor());
}
思路二
在将Grpc注册到Spring容器的时候可以使用拦截器,在拦截器中打印相关请求与响应。tips:客户端拦截器实现
ClientInterceptor
,服务端拦截器实现ServerInterceptor
重写ClientInterceptor
package com.ypshengxian.store.server.infrastructure.config;
import com.ypshengxian.store.server.infrastructure.utils.EnvLogPrintUtil;
import io.grpc.*;
import lombok.extern.slf4j.Slf4j;
/**
* 描述:用来打印请求与响应日志
* Created by zjw on 2021/9/10 09:52
*/
@Slf4j
public class GrpcClientInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
// MethodDescriptor.Marshaller<ReqT> requestMarshaller = method.getRequestMarshaller();
// next.newCall(method, callOptions);
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
next.newCall(method, callOptions)) {
@Override
public void sendMessage(ReqT message) {
EnvLogPrintUtil.info("interceptCall sending message, method:{} message:{} ", method.getFullMethodName(), message.toString());
super.sendMessage(message);
}
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
ClientCall.Listener<RespT> listener = new ForwardingClientCallListener<RespT>() {
@Override
protected Listener<RespT> delegate() {
return responseListener;
}
@Override
public void onMessage(RespT message) {
EnvLogPrintUtil.info("interceptCall received message:{} ", message.toString());
super.onMessage(message);
}
};
super.start(listener, headers);
}
};
}
}
使用
在将Grpc注册到Spring容器的时候使用我们自定义的拦截器
@Bean
public UserCartGrpc.UserCartBlockingStub userCartBlockingStub(ManagedChannelFactory factory) {
return UserCartGrpc.newBlockingStub(factory.create(UserCartGrpc.SERVICE_NAME)).withInterceptors(new GrpcClientInterceptor());
}