gRPC是使用二进制格式传输报文的,有时项目需要记录请求调用输入输出日志,以方便错误排查。但以二进制记录报文的话不方便查看,如果记录的日志是以json或XML的形式,则更方便查看,下面本文将介绍如果将输入输出报文以json的形式记录到日志文件中。
首先,实现一个用户打印日志的Marshaller,它创建时需要传入实现进行报文转换的Marshaller,此Marshaller只是把转换后的object转换成json(解析输入报文后),或者将转换前的object转换成json(生成输出报文前),并打印到日志文件中。
package com.example.tutorial.tool;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import com.google.protobuf.util.JsonFormat.Printer;
import io.grpc.MethodDescriptor.Marshaller;
/**
* 使用JSON格式记录输入与输出日志
*
*/
public class JsonLoggerMarshaller<T extends Message> implements Marshaller<T> {
private static final Logger LOG = Logger.getLogger(JsonLoggerMarshaller.class.getName());
private 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);
LOG.info("output:" + info);
} catch (Exception e) {
LOG.log(Level.SEVERE, "do stream error", e);
}
return baseMarshaller.stream(value);
}
public T parse(InputStream stream) {
T msg = baseMarshaller.parse(stream);
try {
// 记录输入日志
String info = printer.print(msg);
LOG.info("input:" + info);
} catch (Exception e) {
LOG.log(Level.SEVERE, "do parse error", e);
}
return msg;
}
}
然后,使用如下代码将JsonLoggerMarshaller注册到服务中去。
package com.example.tutorial.tool;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.Marshaller;
import io.grpc.MethodDescriptor.PrototypeMarshaller;
public class GlobalMarshallerInitializer {
private static final Logger LOG = Logger.getLogger(GlobalMarshallerInitializer.class.getName());
// 为记录json格式的日志,注册JsonLoggerMarshaller
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("finish to initiate method: " + md.getFullMethodName());
}
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "fail to initiate JsonLoggerMarshaller " + serviceClass.getName(), e);
}
}
}
最后,只需要在服务端代码和客户端代码中,对需要日志功能的服务,调用上述代码进行初始化。
GlobalMarshallerInitializer.initiateJsonLoggerMarshaller(GreeterGrpc.class);