Help us understand the problem. What is going on with this article?

独自のfmt.Formatterを実装する

More than 3 years have passed since last update.

fmt.Formatter interfaceを実装すると独自で定義した型がfmt.Sprintffmt.Fprintfに渡されたときのverb(verbとは例えば%s%vなど)ごとの出力をカスタマイズすることができる.

これを実装しているのは以前 Golangでエラー時にスタックトレースを表示するで紹介したpkg/errors パッケージである.pkg/errorsで生成したエラーは%sverbを使うと通常の表示,%+vverbを使うとスタックトレース形式の表示という切り替えが可能になっている.これは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

(ちなみにcontextKeyServerContextKeyLocalAddrContextKeyの実装に使われている)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away