alglogは、中規模のC++商用プロジェクトをターゲットとした、シンプルなロガーライブラリです。
FetchContent_Declare(
alglog
GIT_REPOSITORY https://github.com/kuguma/alglog.git
GIT_TAG <version_tag>
)
FetchContent_MakeAvailable(alglog)- 3種類の顧客用ログレベルと、4種類のデバッグ用ログレベル
- 使いやすい同期ロギングと、高速な非同期ロギングの双方をサポート
- クラスプラットフォーム
- カスタムMITライセンス:バイナリ配布の場合、権利表示の必要なし。
- C++17から利用可能
fmtlibを利用した便利なフォーマット- グローバルロガーなし:複数プロジェクトで採用しても競合しません
- 日本語のドキュメント😊
- 顧客に公開したいログ(例えばverboseモードでの出力)と、デバッグ用のログを1つのライブラリでサポートできます。
- 顧客ログには、ソース情報は含まれません。
- デバッグログはマクロを経由して呼び出すことで、ソース情報を含めることができます。ログ呼び出しはリリースビルドでは消滅します。
ALGLOG_DEFAULT_LOG_SWITCHをOFFに設定して、自分でカスタムすることもできます。
- 同期モードでは、ログの記録と同時に出力を行いますが、標準出力やファイルへの書き込みは大きなオーバーヘッドになります。
- 非同期モードでは、ログの記録とは独立して、後ほど任意のタイミングで出力を行うことができます。これにより、ロギングのオーバーヘッドを最小限に抑えてプログラムを実行できます。
- boostやfmtlibと同様に、バイナリ配布の場合は権利表記が必要ありません。
- プロジェクトのローンチ後でも、導入に際して顧客説明が必要なく、上司への説明が簡単です。これは開発者にとって嬉しい点です。
- もちろん、柔軟にOSSを採用できるプロジェクトでは、メンテナンスされており、より洗練されたロガー(例えば
spdlogやquillなど )の採用を推奨します。
- 多くのロガーでは、includeと同時にグローバルロガーが定義されます。これは一見便利なように見えますが、複数のプロジェクトから利用するとよく衝突します。
- alglogでは、代わりにプロジェクトロガーのテンプレートを提供することで、便利かつ副作用の少ないライブラリを目指しています。
alglogは、次のように設計されています。設計はspdlogに影響を受けています。
ロガーはlogger、sink、valve、およびformatterから構成されています。
-
ロガーにログを書き込むと、まずログは
logger内に蓄積されます。デフォルトでは同期モードで、蓄積と同時に出力が行われます。
非同期モードでalglogを利用するには、loggerのコンストラクタに
trueを与えます。この場合、蓄積されたログは手動でlogger.flush()を呼び出してフラッシュする必要があります。定期的に出力したい場合、alglog::flusherを利用できます(定期的にflushを行うスレッドが起動します)。 -
flush()されたログは、loggerが接続しているsinkを通過し、出力されます。sinkはvalveと呼ばれる出力条件判定ラムダ関数を持ち、その条件を満たす場合のみlogはsinkを通過します。組み込みで以下の
sinkが提供されています。alglog::builtin::file_sinkalglog::builtin::print_sink
また、自分で
alglog::sinkクラスを継承し、logger.connect_sink()を使用して任意のロガーに出力することもできます。 -
sinkから出力されるとき、sinkは自身が持つformatterを介してログを整形します。sink.formatterはpublicなラムダ変数であり、自分で作成してsinkに上書き設定することもできます(自作sinkの場合、formatterを無視してもかまいません)。
alglogのロガーはそのまま使うこともできますが、ソースローケーションの埋め込みを行うためにはマクロを経由する必要があります。
alglog-project-logger-template.hをフォークして、プロジェクト用のロガーを作成することを推奨します。
MyLogInfo("hello world");
MyLogDebug("The answer is {}.", 42);
std::vector<int> vec = {1,2,3,4,5};
MyLogTrace("vector = {}", vec);alglogは、顧客公開用に3段階、デバッグ用に4段階のログレベルを持ちます。
顧客公開用の3段階はリリースビルドでも消滅せず、ソース情報等の細かい情報は含まれません。
デバッグ用の4段階は、リリースビルドではバイナリから消滅します。
enum class level{
// -------------------------------------------------------------------------------------------------- ↓リリースビルドに含まれる
error = 0, // ユーザー向けエラー情報ログ:APIの投げた例外を補足する形などを想定。
alert, // ユーザー向け警告ログ:ユーザーの意図しないフォールバック等が行われた場合の出力利用を想定。
info, // ユーザー向け情報提供ログ:API呼び出し履歴などを想定。
// -------------------------------------------------------------------------------------------------- ↓デバッグビルドに含まれる
critical, // 致命的な内部エラー:assertと組み合わせて使うと効果的。
warn, // assertを掛けるまでではないが、何か嫌な感じのことが起こってるときに出す。
debug, // 理想的には、このログを眺めるだけでプログラムの挙動の全体の流れを理解できるようになっていると良い。
// -------------------------------------------------------------------------------------------------- ↓デバッグビルドかつALGLOG_TRACEのときに含まれる
trace // 挙動を追うときに使う詳細なログ。機能開発中や、込み入ったバグを追いかけるときに使う。
};alglogの全てのマクロは正論理です。殆どの機能は、マクロを用いて明示的に有効化する必要があります。
例えば、ALGLOG_<LOG_LEVEL>_ONをdefineすると、該当するログレベルのログ出力が有効になります。
通常、これらのマクロの宣言はcmakeにより自動的に行われます。
alglogをcmakeのプロジェクトとして追加せず、ヘッダのコピーを直接プロジェクトに配置する方法でこのライブラリを使用する場合は、適宜必要なマクロをdefineしてください。
マクロの名前は、cmakeのoptionの名前と同じです。
option(ALGLOG_DEFAULT_LOG_SWITCH "Enable default log switch" ON) # デフォルトではリリースでERROR,ALERT,INFOが残る
option(ALGLOG_GETPID "Enable process ID retrieval" ON)
option(ALGLOG_GETTID "Enable thread ID retrieval" ON)
option(ALGLOG_AUTO_THREAD_PRIORITY "Enable automatic thread priority adjustment for flusher thread" ON)
option(ALGLOG_CONTAINER_STD_LIST "Use container of std::list with std::mutex" OFF)
option(ALGLOG_CONTAINER_MPSC_RINGBUFFER "Use container of mpsc ring buffer" ON)とりあえず動作確認がしたい場合は、以下のスニペットを利用できます。 製品コードではプロジェクトロガーを利用するようにしてください。
#define ALGLOG_DIRECT_INCLUDE_GUARD // このマクロを宣言しないで直接includeした場合、エラーになります。
#include <alglog.h>
auto logger = alglog::builtin::get_default_logger();
logger->info("hello world");
logger->debug("The answer is {}.", 42);
std::vector<int> vec = {1,2,3,4,5};
logger->trace("vector = {}", vec);alglogでは、alglog.hをラップしたヘッダ(プロジェクトロガーヘッダ)を作成し、マクロ経由で呼び出してもらうことを想定しています。
詳細はalglog-project-logger-template.hを参照してください。
// ---------------- mylogger.h ----------------
#pragma once
#define ALGLOG_DIRECT_INCLUDE_GUARD
#include <alglog.h>
namespace my_project{
class Logger {
private:
Logger() : logger(std::make_shared<alglog::logger>(true)), flusher(std::make_unique<alglog::flusher>(logger))
{
// modify this
logger->connect_sink( std::make_shared<alglog::builtin::print_sink>() );
logger->connect_sink( std::make_shared<alglog::builtin::file_sink>("my_project.log") );
flusher->start();
};
~Logger() = default;
public:
std::shared_ptr<alglog::logger> logger;
std::unique_ptr<alglog::flusher> flusher;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
Logger(Logger&&) = delete;
Logger& operator=(Logger&&) = delete;
static Logger& get() {
static Logger instance;
return instance;
}
};
}
#define MyLogError(...) my_project::Logger::get().logger->error(__VA_ARGS__)
#define MyLogAlert(...) my_project::Logger::get().logger->alert(__VA_ARGS__)
#define MyLogInfo(...) my_project::Logger::get().logger->info(__VA_ARGS__)
#define MyLogCritical(...) my_project::Logger::get().logger->critical(ALGLOG_SR, __VA_ARGS__)
#define MyLogWarn(...) my_project::Logger::get().logger->warn(ALGLOG_SR, __VA_ARGS__)
#define MyLogDebug(...) my_project::Logger::get().logger->debug(ALGLOG_SR, __VA_ARGS__)
#define MyLogTrace(...) my_project::Logger::get().logger->trace(ALGLOG_SR, __VA_ARGS__)
// ---------------- something.cpp ----------------
#define ALGLOG_DIRECT_INCLUDE_GUARD
#include <mylogger.h>
// ... other includes ...
auto lgr = std::make_shared<alglog::logger>(true);
auto psink = std::make_shared<builtin::print_sink>();
psink.valve = alglog::builtin::debug_level_output;
lgr->connect_sink(psink);
lgr->connect_sink( std::make_shared<builtin::file_sink>("my_logger.log") );
auto flusher = std::make_unique<alglog::flusher>(lgr);
flusher.start(500);
// loggerの出力先は、alglog::sinkを継承した自作クラスを用いてカスタムできます。
// あるsinkが出力を行うかどうかは、sink.valveにラムダを設定することで制御できます。sinkにカスタムvalveを設定することで解決できます。
const auto keyword_valve = [](const log_t& l){
if (l.find("keyword")){
return true;
}
return false;
};C++でロガーライブラリを構築する場合、以下のような制約と向き合う必要があります。
- C++20より前では、マクロを使わないとソース情報を取得できない。
- デバッグにおいては、ソース情報は必須である。
- マクロにはnamespaceがない。
従って、C++20未満の環境では、残念ながらマクロを使ったログ呼び出しを基本として設計を行う必要があります。
同じロギングライブラリが複数プロジェクトで使用される場合、よく問題になるのは設定の衝突です。
あらゆるプロジェクトが、自分の都合の良いようにライブラリを使おうとすることを想定する必要があります。2つのプロジェクトが、お互いに自分だけの出力を有効化するように設定を行っていたらどうなるでしょうか?
このような問題を避けるため、alglogではグローバルロガーを実装していません。
グローバル化はユーザーがそれぞれの責任で行います。(namespaceの利用を強く推奨します)
$ cd alglog
$ python test/main.pyテストの拡充を歓迎します!