fmt.Formatter interfaceを実装すると独自で定義した型がfmt.Sprintf
やfmt.Fprintf
に渡されたときのverb(verbとは例えば%s
や%v
など)ごとの出力をカスタマイズすることができる.
これを実装しているのは以前 Golangでエラー時にスタックトレースを表示するで紹介したpkg/errors パッケージである.pkg/errors
で生成したエラーは%s
verbを使うと通常の表示,%+v
verbを使うとスタックトレース形式の表示という切り替えが可能になっている.これはfmt.Formatter
を実装することで実現している.実装は以下のようになっている.
type StackTrace []Frame
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
fmt.Fprintf(s, "\n%+v", f)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
fmt.Fprintf(s, "%v", []Frame(st))
}
case 's':
fmt.Fprintf(s, "%s", []Frame(st))
}
}
実装は単純でswitchを使ってverbごとの出力を定義しているだけである.
最近自分ではgo-httpstatというHTTPリクエストの各ステップのレイテンシ情報をトレースするパッケージを書いている(詳しくは Tracing HTTP request latency in golang).これはもともと以下のように各レイテンシ情報をそれぞれPrint関数に渡して出力する必要があった.
fmt.Printf("DNS lookup: %d ms", int(result.DNSLookup/time.Millisecond))
fmt.Printf("TCP connection: %d ms", int(result.TCPConnection/time.Millisecond))
fmt.Printf("TLS handshake: %d ms", int(result.TLSHandshake/time.Millisecond))
fmt.Printf("Server processing: %d ms", int(result.ServerProcessing/time.Millisecond))
fmt.Printf("Content transfer: %d ms", int(result.ContentTransfer/time.Millisecond))
これは使う側としてはめんどくさいのでfmt.Formatter
を実装して%+v
を指定すれば良いだけにした.
fmt.Printf("%+v\n", result)
DNS lookup: 461 ms
TCP connection: 163 ms
TLS handshake: 427 ms
Server processing: 165 ms
Content transfer: 0 ms
...
具体的な実装は https://github.com/tcnksm/go-httpstat/blob/master/httpstat.go#L84-L139 を参照.
fmt.Stringer
fmt.Fromatter
を使う人はあまりいないと思う.それよりもよく使うのはfmt.Stringerだろう.これは単純に独自の型がfmt
の関数に渡ったときの出力をカスタマイズできる.
例えばnet/http
パッケージには以下のような実装がある.
type contextKey struct {
name string
}
func (k *contextKey) String() string {
return "net/http context value " + k.name
}
これをfmt.Println
に渡すと出力は以下のようになる.
fmt.Println(&contextKey{"http-server"}) // net/http context value http-server
(ちなみにcontextKey
はServerContextKey
やLocalAddrContextKey
の実装に使われている)