Goのfmtパッケージの出力をカスタマイズする方法とその仕組み
Goについて学び直していて、fmtパッケージのPrintfなどの出力書式をカスタマイズする方法を知った。
なるほどと思ったのと同時に、どういう仕組みなんだろってのが気になったのでコードリーディングしてみたら納得できて面白かったのでまとめておく。
Goのfmtパッケージには以下のようなStringer
インターフェースが定義されている
fmtパッケージで文字列を作成する際に、このStringer
インターフェースを実装しているとそのString
メソッドの実行結果が用いられる。
これを利用してログに吐き出されると困る秘匿情報などをマスキングすることができる
パスワードやクレジットカードのセキュリティ番号など、そのままログに出すみたいなことはさすがにしないと思うが、構造体の中に含まれていて一緒に出力されるみたいなケースはあり得そう
そういった秘匿情報のものは別で型定義をしてStringerインターフェースを満たすようにメソッドを実装しておくと良さそう
この手法自体知らなかったので勉強になったのだが、調べるとそこそこ情報出てくるのでそれなりにメジャーな方法なのだと思う。
ここからが本題
今回はこれがどういう仕組みで行われてるかが気になったのでfmtパッケージの中身をコードリーディングしてみたというお話。
fmt.Printf
関数の終わりまで順に追っていこうと思ったが、さすがに長すぎたので今回知りたかった部分だけピックアップする。
fmtではppというstructが定義されている。Printfメソッドを実行するにあたって必要な情報はこの中で管理してこれを取り回して操作しているという認識。
例えば出力時のフォーマットの形式なんかもここで管理されている。
このpp構造体が持っているメソッドにhandleMethodsというものがある。
その一部を抜粋したものが以下のもの。(本当はもっとコメントとかが書かれているが省略している)
ポイントは6行目のswitch文でp.arg
には今回出力させたい構造体のフィールドが入っている。
p.arg.(type)
は型switchであり、これによってフィールドの型情報を判定している。
そういえばGoではこうやって型情報判定するんだったっけな。全然覚えてなくて一瞬固まった。
8行目にcase Stringer:
とあり、そのフィールドStringer
インターフェースを実装していればここの中の処理が行われる。
その中の処理では、11行目でv.String()
を呼び出している。
つまりStringer
インターフェースを実装していた場合は、ここでその処理が呼ばれていることになる。
その場合は返り値であるhandled
をtrueにして処理を終えている。
ちなみにStringer
インターフェースを実装していない場合はhandled
がfalseで返され、呼び出し元の方でfmtパッケージが持つ文字列の組み立てを行なっていた。
まとめると**Stringer
インターフェースを満たす実装を作成していた場合はそれが呼び出され、そうじゃない場合はfmtパッケージのメソッドが呼び出されるという仕組み**になっていた。
型switchでインターフェースを実装しているかどうかを判定して呼び分けていたというわけ。
知りたいことは知れた、満足である。
実は先程省略した記述の中にerrorインターフェースかどうかを判定している部分がある。
これまで見てきたStringer
インターフェースと同様にerror
インターフェースを実装することでfmt.Errorf
の内容も拡張できる。
error
インターフェースはstringを返すError
メソッドを持つだけである。
こっちのerror
インターフェースを実装してエラーハンドリングを拡張するパターンは見たことあった。
同じような仕組みで動いてたのかと腹落ちした。
実は先ほどの出力でフォーマットを%#v
にすると出力結果がうまくカスタマイズされていない。
%#v
はGoの構文で出力されるフォーマットである。
実装を見てみると、フォーマットが#v
の場合はそもそも先ほどの条件式の中に処理が来ていない。
具体的には以下のようになっている。
4行目でフォーマットが#v
かどうかを判定している(別の場所で#v
の場合はsharpV
をtrue
にする処理がある)
trueだった場合はGoStringer
インターフェースを実装しているかどうかを判定して、実装していた場合はそれを呼び出している。
(ちなみにelseの中が先ほどの条件式部分)
つまり、#v
でも出力をカスタマイズするためにはStringer
インターフェースではなくGoStringer
インターフェースを実装する必要があることがわかる。
無事、%#v
でも出力をカスタマイズすることができた。
エラーをカスタマイズできるのは知ってたが、通常のfmt.Printlnf
もカスタマイズできるのは知らなかったので勉強になった。
実際の実装を見ることで点と点が色々と繋がっていって楽しかった。
fmt.Formatterを実装して%vや%+vをカスタマイズしたり、%3🍺みたいな書式をつくってみよう #golang - Qiitafmtパッケージのインタフェースfmtパッケージにはいくつかインタフェースがあります。例えば、ここではフォーマットに関わる以下の3つについて説明していきましょう。StringerGoStri…qiita.com