概要
言語標準のロガーもデファクトスタンダードのロギングライブラリも(多分)無いC言語ですが、流石にprintf("test\n");
だったりをあちこちに挿入するのは見分けつかない上に不毛だったので、printfと同じ感覚でファイル名、関数名、行番号を勝手に装飾してくれる様にマクロを定義してみました。
制限
-
__func__
はC99仕様です。レガシーな環境では対応状況に注意してください。 - 後述するテクニックで0個以上の可変引数マクロに対応しているため引数の受付数が有限個です。以下のコードでは1行を80文字に収めるために0〜4個のフォーマット指定子まで対応です。
コード
#include <stdio.h>
#include <errno.h>
#include <string.h>
/* 作成したマクロ */
#define FATALMSG(...) MSG("FATAL",__VA_ARGS__)
#define ERRORMSG(...) MSG("ERROR",__VA_ARGS__)
#define WARNMSG(...) MSG("WARN",__VA_ARGS__)
#define INFOMSG(...) MSG("INFO",__VA_ARGS__)
#define DEBUGMSG(...) MSG("DEBUG",__VA_ARGS__)
#define _LOG(msg) "[%s] %s %d: %s() - "msg
#define _FMT __FILE__,__LINE__,__func__
#define MSG(...) _MSG(__VA_ARGS__,_MSG4,_MSG3,_MSG2,_MSG1,_MSG0)(__VA_ARGS__)
#define _MSG(_1,_2,_3,_4,_5,_6,name,...) name
#define _MSG0(lv,msg) fprintf(stderr,_LOG(msg),lv,_FMT)
#define _MSG1(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
#define _MSG2(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
#define _MSG3(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
#define _MSG4(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
/* 使用例 */
int test_function(void)
{
FILE *fp;
fp = fopen("file_never_exist", "r");
if(fp == NULL) {
ERRORMSG("%s\n", strerror(errno));
return -1;
}
fclose(fp);
return 0;
}
int main(int argc, char **argv) {
INFOMSG("test\n");
test_function();
INFOMSG("test\n");
return 0;
}
$ ./sample
[INFO] sample.c 35: main() - test
[ERROR] sample.c 27: test_function() - No such file or directory
[INFO] sample.c 37: main() - test
行数が分かるので簡単なprintfデバッグならtest1, test2...の様に出力文字列を区別する必要が無くて楽です。また失敗した関数も推測可能なので雑にstrerror(errno)
を突っ込んでも問題ない点でperror()
より使い勝手が良く感じます。
使用したテクニック等
事前定義マクロ/識別子
後発(C99)の__func__は事前定義識別子となり、取扱いが若干異なる点に注意。
可変引数マクロ
昔からある奴です。ただし__VA_ARGS__
は...
の引数が0の場合に上手く動作しません。これはprintf()
の様に「フォーマット指定子が無い場合は文字列をそのまま出力する」という挙動で困ります。C++20の__VA_OPT__
ないしはgccの##__VA_ARGS__
で対処できますがコンパイラ互換性が微妙…
マクロオーバーロード
上記の__VA_ARGS__
の問題にこれで対処しました。ちゃんとした名称は不明だが可変引数マクロの文脈でよく見ます。競プロのrepマクロでも見たことあります。
上記コードから抜粋すると以下の部分です。
#define MSG(...) _MSG(__VA_ARGS__,_MSG4,_MSG3,_MSG2,_MSG1,_MSG0)(__VA_ARGS__)
#define _MSG(_1,_2,_3,_4,_5,_6,name,...) name
#define _MSG0(lv,msg) fprintf(stderr,_LOG(msg),lv,_FMT)
#define _MSG1(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
#define _MSG2(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
#define _MSG3(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
#define _MSG4(lv,msg,...) fprintf(stderr,_LOG(msg),lv,_FMT,__VA_ARGS__)
引数の数に対応した各内部マクロを用意して、引数の格納順をうまく利用してそれぞれに分岐させる感じです。
上記のコードではフォーマット指定子が0〜4個までの場合に対応していますが、1行を端末サイズ(80文字)に収める都合なのでもっと増やしても構いません。
念の為
C++ならデファクトスタンダードと言えずとも良い感じのロギングライブラリもあるだろうしマクロを使ってまでやることじゃないと思います。