背景
- 主にグラフィックス系 C++11 アプリでロギングほしい
- 演算メインのときではあまり print しないが, データロードとかセーブのときとかにログを出したい
- デバッグ用にベクトルの値とかをプリントしたい
- ログ処理速度はそんなに高速である必要はない. しかしコンパイルは早くしたい
- すごい機能がいっぱい欲しいわけでなく, python の "{}" のような表記が使えれば OK
既存ライブラリ
- spdlog https://github.com/gabime/spdlog : "{}" 記法が使える. 今まで 3, 4 年ほどメインで使っているが, テンプレートいっぱい使っているのでコンパイル遅い(実際には spdlog が利用している fmtlib が主にコンパイル遅い. non-header only にするとコンパイル早くなると記述があるが, やっぱりそれでも遅い).
- あと, なぜか Android NDK r20+ clang で C++14 モードだとコンパイルに失敗する
- glog : ビルドシステムとコードが古臭い. ストリーム記法しかない. 他のサードパーティライブラリが glog 使っていると, メインのアプリと干渉したり, またマクロ名が衝突したりすることがあり面倒になる
- Ceres solver から抜き出した miniglog https://github.com/tzutalin/miniglog はそこそこいい感じだが, いずれにせよストリーム記法しかなくて不便
=> 本当に欲しい機能というはそんなにあるわけでないので, 自作しました.
nanolog
# include "nanolog.hh"
// logging method
NANOLOG_TRACE("The answer is {}", 42);
NANOLOG_DEBUG("The answer is {}", 42);
NANOLOG_INFO("The answer is {}", 42);
こんな感じで使えます.
デフォルトでは自前処理ですが({}
を operator<<
で得た文字列で置き換えるだけ. コンパイル時引数型チェックなどはしない), backend として pprintpp と fmtlib を使えます.
pprintpp
python の "{}" 表記を実現するのに,
Typesafe Python Style Printf Formatting for C++
https://github.com/tfc/pprintpp
をバックエンドに使っています.
(fmtlib backend もあります. コンパイルは pprintpp のほうが fmtlib に比べて 4~5 倍は早いです)
printpp も template で "{}" をパースしたりしてコンパイル型チェックしてくれます.
実装がテンプレートバリバリでよくわからない ので, みなさん是非解析してみてください.
実装的には, AUTOFORMAT マクロで, ("{}", 1) が与えたれたときに, フォーマット文字列を "%d" にコンパイル時に置き換えるというのをしています.
ただ, pprintpp ではいくつか制約があります. TODO にあるように文字列型がうまく扱えません. また対応していない型だと seg fault したりします. うまくいかなければ fmtlib を使うようにしましょう.
NANOLOG_XXX
__LINE__
, __func__
あたりはマクロを使わないと実現できないので, ログ関数のエントリはマクロになっています.
nanolog の内部実装的には, printf するところだけ mutex で lock_guard 入れて multi-thread safe にしているという単純なものです.
TODO
-
pprintpp で
const char *
を print すると, 文字列ではなくアドレスが表示されるのを直す - 精度はあまり求めないので, 高速な unix epoch time -> 時刻文字列生成を行う(既存のライブラリで, locale を考慮するといろいろ遅くなるみたいな噂を聞いた)
-
ログが多量に生成されるのを防ぐために,
__FILE__
,__LINE__
の値を見て, 前回から log 出したあと N 秒内のメッセージは suppress するとか, M 回表示したらそれ以降は suppress するとかの機能を実装する- グラフィックスアプリで, pixel ごとに log を出す場合にエラーメッセージが大量に出力されてしまうのを防ぐときなどに役立つ.