Goでのログ出力に標準logとcologを使う

# Goの標準logの問題

Goの標準ライブラリには、logパッケージ(標準log)というものがあり、単純なログ出力であれば、これで十分です。
標準logは、Javaのslf4jのような、よくあるログライブラリのように`Debug`とか`Info`といった、ログレベル別の関数を持ちません。あるのは以下の3系統のみです。

- Print系
    - ログを出力する。
- Panic系
    - ログを出力してから`panic`を発生させる
- Fatal系
    - ログを出力してから`os.Exit(1)`を呼んで、アプリケーションを終了させる

これで分かるとおり、通常のログ出力には、Print系しか使えないため、ある程度大規模化、複雑化したアプリケーションで、ログレベルに応じた出力をしようとすると、標準logでは機能不足になります[^1]。

[^1]: そもそもログレベルというものが必要なのかとかという議論は別にあるでしょう。

# 標準以外のログライブラリを使った場合の問題

この標準logの機能不足を解決するために、いくつものログライブラリが作られました。代表的なものとしてはDockerでも使われている[logrus](https://github.com/Sirupsen/logrus)でしょうか。
ですが、このような標準以外のログライブラリを使うと、以下のような問題が起きます。

- 標準logを含め、利用するライブラリが混在しないように気を使う
    - 共通ライブラリを作った場合、そこでも同じログライブラリを使わないと面倒
    - 強い言い方をすると関連するもの全てがログライブラリに汚染されてしまう
- そのログライブラリの使い方を覚える必要がある
    - チーム開発の場合、全員に周知する必要がある

レビューで弾けとか、それくらい覚えろという意見もあるかと思いますが、Webアプリでさえシンプルに`net/http`だけで済ませたい自分としては、ログ出力についても同様にシンプルに済ませたいのです。

# cologを使う

そこで[colog](https://github.com/comail/colog)の登場です。

https://github.com/comail/colog

cologは*prefix-based leveled execution log*(colog作者の造語？)なログライブラリで、出力される文字列のプリフィクスをログレベルに利用するログライブラリです。
似たような仕組みのライブラリには[hashicorp/logutils](https://github.com/hashicorp/logutils)がありますが、cologの方が拡張性に優れています。

## 使い方

cologの使い方は簡単で、`colog.Register()`を呼び出した後から、cologが有効になり、標準logの出力をフックして、出力するようになります。

ログレベルの指定は、出力されるログの先頭に`error:`のようにプリフィクスを置くことで行います。
デフォルトで規定されているログレベルは以下の6つです。

- trace
- debug
- info
- warn
- error
- alert

プリフィクス無しの場合のログレベルは`colog.SetDefaultLevel(colog.Level)`で指定できます。また`colog.SetMinLevel(colog.Level)`で、出力対象となるログレベルの最小値が指定できます。

出力フォーマットは`colog.Formatter`インターフェースの実装で決定されます。デフォルトではStdFormatterとJSONFormatterが提供されていますが、当然自分で書くことも可能です。`colog.Formatter`には標準logのSetFlagで指定出来るものと同じ設定値が渡せるようになっており、これで日時などの出力制御が出来ます。

コード例は以下の通りです。ログ出力そのものは標準logを使っていることに注目してください。

```go
package main

import (
   "log"

   "github.com/comail/colog"
)

func main() {
   colog.SetDefaultLevel(colog.LDebug)
   colog.SetMinLevel(colog.LTrace)
   colog.SetFormatter(&colog.StdFormatter{
      Colors: true,
      Flag:   log.Ldate | log.Ltime | log.Lshortfile,
   })
   colog.Register()
   
   log.Printf("trace: this is a trace log.")
   log.Printf("debug: this is a debug log.")
   log.Printf("info: this is an info log.")
   log.Printf("warn: this is a warning log.")
   log.Printf("error: this is an error log.")
   log.Printf("alert: this is an alert log.")
   
   log.Printf("this is a default level log.")
}
```

これを実行すると、このように出力されます。

<img width="571" alt="colog_example.png" src="https://qiita-image-store.s3.amazonaws.com/0/17916/92d4ec50-31ba-ea6a-938f-ffcaadb10d8b.png">

例なので、デフォルトのログレベルを全て色付きで出力してみましたが、通常の本番時には色を無くしたり、SetMinLevelを変更することになるでしょう。

## hook

cologには特定のログレベルに対して、フックを設定することができます。
これを使えば、他の関数を呼び出したり、外部サービスにログを飛ばすことが出来ます。
colog作者によって作られた、cologのログをlogrusに渡す[cologrus](https://github.com/comail/cologrus)なんてものもあります。

# まとめ

cologを使うと、標準logを使ってログを出力するだけで、それなりにリッチなログ出力を行えます。
ログレベルに対応したプリフィクスを覚えておく必要がありますが、特に変わった語彙ではないので、実際には覚えるまでもないでしょう。この程度のプリフィクスであれば、ログが汚れるということもないと思います[^2]。
cologの設定は、具体的なアプリケーションで行うだけなので、共通ライブラリはcologに依存させる必要はなく、外部ライブラリへの依存性を抑えることができます。

作者のブログ記事にはベンチマークもあるので、こちらも参考にしてください。
https://texlution.com/post/colog-prefix-based-logging-in-golang/

それでは良いGo & logライフを。

[^2]: 既に複雑なプリフィクスで運用している場合は使えないですが、、、
----