gRPCサーバーのデバッグとServer Reflection

gRPCはRPCの1つでProtocol Buffersでシリアライズ化することで高速な通信を実現させています。そのため、通常のREST-likeなWEB APIデバッグでつかうcurlやPostmanのようなGUIツールでデバッグすることができません。

そこで今回はgRPCで開発するときに役に立つデバッグの方法を紹介します。

CLIクライアント

一番基本となるデバッグ方法は自由にリクエストを送ることだと思います。
以下のようにいくつかCLIからgRPCのリクエストを送ることができるツールがあります。

公式が提供しているツールを使うのもいいですが、curl-likeなほうが使い慣れた人が多いと思うのでgrpcurlがオススメです。
このようにしてリクエストを送ることができます。

$ grpcurl -plaintext -import-path ./api -proto route_guide.proto -d '{"latitude": 10, "longitude": 20}' localhost:10000 RouteGuide.GetFeature

Server Reflection

gRPCのはデバッグ用の機能としてServer Reflection Protocolというものがあります。

通常は上記の例のように、リクエストを送ってレスポンスを表示するには関連する.protoファイルを指定する必要があります。
しかし、Server Reflection Protocolを使えばサーバーから必要な情報を取得することができ、.protoファイルが無くても実行することができます。

この機能を使うにはサーバーにReflectionの設定をする必要がありますが、数行に追加で済みます。

import (
    "google.golang.org/grpc"
+   "google.golang.org/grpc/reflection"
)

    grpcServer := grpc.NewServer()
+   reflection.Register(grpcServer)
    ...
    _ = grpcServer.Serve(lis)

これでServer Reflectionの対応は完了です。

また、Server Reflectionに対応することで以下のようにサーバーに実装されているサービスの一覧や詳細も.protoファイルなしで確認できるようになります。
とくにサービスの一覧の表示は途中から開発に参加したプロジェクトなどではとても役に立つと思います。

$ grpcurl -plaintext localhost:10000 list
RouteGuide
grpc.reflection.v1alpha.ServerReflection
$ grpcurl -plaintext localhost:10000 list RouteGuide
RouteGuide.GetFeature
$ grpcurl -plaintext localhost:10000 describe RouteGuide.GetFeature
RouteGuide.GetFeature is a method:
rpc GetFeature ( .Point ) returns ( .Feature );

環境変数

サーバー側のデバッグ情報として環境変数を設定することで追加のデバッグ情報を出力することができます。
これは各言語の実装により指定する項目が変わるので注意する必要があります。

Go

Goでは以下のように環境変数を設定すれば追加のログが出力されます。

$ export GRPC_GO_LOG_VERBOSITY_LEVEL=99
$ export GRPC_GO_LOG_SEVERITY_LEVEL=info

ドキュメントにも少しだけ記述があります。

godoc.org

また、Goの場合はGo自体に実装されたhttp2のデバッグ機能を使うのもオススメです。
こちらはフレームの内容なども出力されるため、とても役に立つと思います。

$ export GODEBUG=http2debug=2
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote SETTINGS len=0
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read SETTINGS len=0
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote SETTINGS flags=ACK len=0
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read SETTINGS flags=ACK len=0
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read HEADERS flags=END_HEADERS stream=1 len=68
2019/09/15 21:41:14 http2: decoded hpack field header field ":method" = "POST"
2019/09/15 21:41:14 http2: decoded hpack field header field ":scheme" = "http"
2019/09/15 21:41:14 http2: decoded hpack field header field ":path" = "/RouteGuide/GetFeature"
2019/09/15 21:41:14 http2: decoded hpack field header field ":authority" = "localhost:10000"
2019/09/15 21:41:14 http2: decoded hpack field header field "content-type" = "application/grpc"
2019/09/15 21:41:14 http2: decoded hpack field header field "user-agent" = "grpc-go/1.21.0"
2019/09/15 21:41:14 http2: decoded hpack field header field "te" = "trailers"
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: read DATA flags=END_STREAM stream=1 len=9 data="\x00\x00\x00\x00\x04\b\n\x10\x14"
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote WINDOW_UPDATE len=4 (conn) incr=9
2019/09/15 21:41:14 http2: Framer 0xc0003001c0: wrote PING len=8 ping="\x02\x04\x10\x10\t\x0e\a\a"

Cベースの実装

Cをベースとした言語(C++, Python, Ruby, Objective-C, PHP, C#)の実装では以下のような環境変数を設定することで追加のログが出力できます。

$ export GRPC_VERBOSITY=DEBUG
$ export GRPC_TRACE=all

Cベースの場合は他にも設定できる項目があり、以下のドキュメントに書かれています。

github.com

また、これらの環境変数はgRPCを内部で使用しているSDKなどでも有効です。
特にGoogleSDKではgRPCがたくさん使われているため、それらのデバッグでも有効活用できると思います。