Google App Engine for Go のローカルサーバでデバッグをする #golang #GAE

August 23, 2016

この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。

はじめに

Google App Engine for Go (GAE/Go) を使っていて、ローカルでデバッグがしたくなることがあるかと思います。 Goにはいくつかデバッガがあり、IDEが対応しているものもあります。 ここでは Delve というデバッガを使ってデバッグする方法を紹介します。

delve2.gif

Delveは動いているGoのプロセスにアタッチしてデバッグすることができます。 そのため、GAE/Goのローカルサーバのプロセスにもアタッチしてデバッグすることができます。 しかし、GAE/Goのローカルサーバのプロセスは、複数動いていたり、切り替わったりするため、一筋縄ではいきません。 ここでは、以下の記事を基に、ターミナル上でローカルサーバのデバッグをやってみます。

Delveのインストール

Delveのインストールについては ネットにたくさん記事がありますので、そちらを参考にしてみてください。

また、英語ですが本家のドキュメントを見ると間違いないかと思います。

インストールに成功すると、dlvコマンドが使えるようになります。

$ dlv help
Delve is a source level debugger for Go programs.

Delve enables you to interact with your program by controlling the execution of the process,
evaluating variables, and providing information of thread / goroutine state, CPU register state and more.

The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.

Usage:
  dlv [command]

Available Commands:
  attach      Attach to running process and begin debugging.
  connect     Connect to a headless debug server.
  debug       Compile and begin debugging main package in current directory, or the package specified.
  exec        Execute a precompiled binary, and begin a debug session.
  run         Deprecated command. Use 'debug' instead.
  test        Compile test binary and begin debugging program.
  trace       Compile and begin tracing program.
  version     Prints version.

Flags:
      --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.
      --api-version=1: Selects API version when headless.
      --build-flags="": Build flags, to be passed to the compiler.
      --headless[=false]: Run debug server only, in headless mode.
      --init="": Init file, executed by the terminal client.
  -l, --listen="localhost:0": Debugging server listen address.
      --log[=false]: Enable debugging server logging.

Use "dlv [command] --help" for more information about a command.

Delveの使い方

使い方は、本家のドキュメントを見ると確実です。 GDBを使ったことがある方は馴染みやすいコマンド体系だと思います。以下の記事も参考になります。

GAEで使う方法

「はじめに」でも書きましたが、以下の記事を参考にしています。

delveAppengineのインストール

Delveは起動済みのGoのプロセスにアタッチすることができます。 しかし、GAEのローカルサーバのプロセスは変わっていくので、それに追従するためにdelveAppengineを使います。

go getしてきます。

$ go get github.com/dbenque/delveAppengine

なお、こちらのPRがマージされるまでは、最新のDelveとバージョンがあわなくてうまくいかないので、注意してください。 PRがマージされる前は以下のようにしておくとよいです。 [追記 824] マージされてました。特に何もせずgo getすれば使えます。

$ go get github.com/tenntenn/delveAppengine
$ cd $GOPATH/src/github.com/tenntenn/delveAppengine
$ git checkout update-delve
$ go install github.com/tenntenn/delveAppengine

デバッグの仕方

デバッグは以下の手順で行います。 ここでは、GAE/GoのSDKに付いているデモのHelloWorldを使って説明します。

  1. ローカルサーバの起動
  2. delveAppengineの起動
  3. Delveクライアントの起動

まずは以下のようにローカルサーバを起動します。 オプションに--max_module_instances=1を指定しているのは、複数のプロセスを立ち上げないためです。 なお、path/to/go_appengineはGAE/GoのSDKをインストールしたディレクトリに置き換えてください。

$ cd path/to/go_appengine
$ dev_appserver.py --max_module_instances=1 demos/helloworld/app.yaml

続いて、delveAppengineを起動します。 Delveはデバッグサーバとしても起動できます、delveAppengineは内部でDelveのデバッグサーバを立ち上げ、ローカルサーバのプロセスが変わるとアタッチし直します。 なお、私のMacOSXでは、sudoしないとうまくいきませんでした。

$ sudo delveAppengine

最後に、Delveをクライントモードで接続します。 delveAppengineは、デフォルトでlocalhost:2345でデバッグサーバを立てています。 dlv connectを使えばデバッグサーバに接続することができます。 起動に成功すると、以下のようにプロンプトが表示されます。

[追記 826] app.yamlがあるディレクトリに移動してdlvを起動しないとソースコードがうまく表示されないかもしれません。

$ dlv connect localhost:2345
(dlv)

それでは、ブレークポイントを仕掛けてみます。 なお、bはブレークポイントを仕掛けるコマンドbreakのエイリアスで、cは処理を継続させるcontinueのエイリアスです。

(dlv) b handle
Breakpoint 1 set at 0x63fc8 for main07214.handle() helloworld.go:16
(dlv) c

http://localhost:8080にアクセスすると、ブレークポイントで止まります。

> main07214.handle() helloworld.go:16 (hits goroutine(6):1 total:1) (PC: 0x63fc8)
    11:
    12: func init() {
    13:         http.HandleFunc("/", handle)
    14: }
    15:
=>  16: func handle(w http.ResponseWriter, r *http.Request) {
    17:         fmt.Fprint(w, "<html><body>Hello, World! 세상아 안녕!</body></html>")
    18: }

リクエストのメソッドを調べて見ましょう。 なお、nは次の行に移るコマンドnextのエイリアスで、pは変数を表示するコマンドprintのエイリアスです。

(dlv) n
> main07214.handle() helloworld.go:17 (PC: 0x63fcf)
    12: func init() {
    13:         http.HandleFunc("/", handle)
    14: }
    15:
    16: func handle(w http.ResponseWriter, r *http.Request) {
=>  17:         fmt.Fprint(w, "<html><body>Hello, World! 세상아 안녕!</body></html>")
    18: }
(dlv) p r.Method
"GET"

うまく"GET"と表示されました。

ここでは、dlvコマンドをクライアントとして使いましたが、元の記事を参考にvscodeを使っても良さそうです。 他のエディターでもDelveのリモートデバッグに対応しているものであれば同じようにデバッグできると思います。 残念ながらvim-goは対応していませんでした。。

私もLuaのデバッガを作ったことがありますが、一番大変なのはGUIでした。 結局、私の場合はGUIは公開するまでにいたらなかったですが。。 まぁ、ターミナルでもここまでできたら嬉しいですよね!