Edited at

Go の expvar パッケージを使ってアプリケーションのメトリクスを公開する

More than 3 years have passed since last update.

expvar パッケージは手軽にメトリクスを公開するのに使えて便利なのですが、複数の値を公開するための例があまり公開されてなくて分かりにくかったので書いておきます。


expvar.Map を使う

mychat というチャットアプリを作ってると仮定して、送受信メッセージ数と、同時接続数を公開してみます。 /debug/vars を見た時に、次のような形でメトリクスを公開します。

...

mychat: {
"conns": 42,
"msg_recv": 10480,
"msg_sent": 88946
},
...

メトリクスのためだけにパッケージを作っておきましょう。


src/mychat/metrics/metrics.go

package metrics

import (
"expvar"
)

var (
Map = expvar.NewMap("mychat") // expvar.NewXxx() は直接公開するメトリクスを作成する。

Conns = new(expvar.Int) // Map に入れる値などは expvar.NewInt() ではなく new(expvar.Int) で作る

MessageRecv = new(expvar.Int)
MessageSent = new(expvar.Int)
)

func init() {
// init() で Map に 値を登録していく
Map.Set("conns", Conns)
Map.Set("msg_recv", MessageRecv)
Map.Set("msg_sent", MessageSent)
}



値を更新する

metrics パッケージのグローバル変数として公開されている *expvar.Int 型を直接カウンタとして使います。


src/mychat/handler.go

package mychat

import (
"log"

"mychat/metrics"
)

// ...

func (c *conn) readloop() {
metrics.Conns.Add(1)
defer metrics.Conns.Add(-1)
for {
msg, err := c.read()
if err != nil {
if err != io.EOF {
log.Error(err)
}
break
}

metrics.MessageRecv.Add(1)
c.handleMessage(msg)
}
}

// ...



まとめ

一番重要なのは、 expvar.NewInt(name) と new(expvar.Int) の違い (トップレベルで公開されるかどうか) です。

Go で NewXxx() という関数を見つけると new(Xxx) をしちゃいけない気がするかもしれませんが、実際には許されていて NewXxx() は汎用コンストラクタではなく特定用途向けという事が多いので、ちゃんとドキュメントを読むか、最悪ソースを読みましょう。 (一番いいのはドキュメントを読んだ上でソースコードを確認することです。)

実は今回の例は Int.Add しか使ってないので、 Map.Add を使うだけでも実現可能です。

しかしカウンタじゃなくて現在値を表す場合に Map.SetInt.Set のラッパーではなく Map.Set("rooms", len(room)) のような使い方はできません。

また、メトリクス名のリファクタリングや、 Map の入れ子構造をリファクタリングしたくなったときのことを考えると、この例のように独立したパッケージを用意して、メトリクス値ごとに変数を用意することをおすすめします。