このLinux ライブラリのスタイル ガイドでは、「アプリがライブラリ ログをログ機能にフックできるようにする」ことを提案しています。これは通常、どのようなメカニズムで行われますか? ロギング関数へのポインタを取る関数? 例を示してもらえますか?
2 に答える
これは SQLite の例です。引数を指定sqlite3_config()
して呼び出すことで、ログの記録方法を設定できます。CONFIG_LOG
関数ポインターと void ポインターを渡します。関数ポインターは 3 つの引数を取ります。void ポインター、整数、およびchar *
. SQLite が何かをログに記録する必要がある場合、SQLite は関数ポインタを呼び出し、渡した void ポインタCONFIG_LOG
、結果コード、およびログ メッセージを含む文字列を渡します。
SQLITE_CONFIG_LOG
SQLITE_CONFIG_LOG オプションは 2 つの引数を取ります。
void(*)(void*,int,const char*)
、および void へのポインター。関数ポインタが NULL でない場合、sqlite3_log() によって呼び出され、各ロギング イベントが処理されます。関数ポインタが NULL の場合、sqlite3_log() インターフェイスはノーオペレーションになります。SQLITE_CONFIG_LOG の 2 番目の引数である void ポインターは、関数が呼び出されるたびに、アプリケーション定義のロガー関数に最初のパラメーターとして渡されます。ロガー関数の 2 番目のパラメーターは、対応する sqlite3_log() 呼び出しの最初のパラメーターのコピーであり、結果コードまたは拡張結果コードを意図しています。ロガーに渡される 3 番目のパラメーターは、sqlite3_snprintf() によってフォーマットされた後のログ メッセージです。SQLite ロギング インターフェイスは再入可能ではありません。アプリケーションによって提供されるロガー関数は、SQLite インターフェイスを呼び出してはなりません。マルチスレッドアプリケーションでは、
の実際の実装はsqlite3_log()
SQLiteのprintf.c
. snprintf()
を呼び出すことができないときにロギング関数が呼び出される可能性があるため、(その周りのラッパー、つまり)を使用してスタック上のバッファーに出力しmalloc
、ユーザーが構成した関数ポインターに結果を渡します。彼らが提供した値とエラーコード。
/*
** This is the routine that actually formats the sqlite3_log() message.
** We house it in a separate routine from sqlite3_log() to avoid using
** stack space on small-stack systems when logging is disabled.
**
** sqlite3_log() must render into a static buffer. It cannot dynamically
** allocate memory because it might be called while the memory allocator
** mutex is held.
*/
static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){
StrAccum acc; /* String accumulator */
char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */
sqlite3StrAccumInit(&acc, zMsg, sizeof(zMsg), 0);
acc.useMalloc = 0;
sqlite3VXPrintf(&acc, 0, zFormat, ap);
sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode,
sqlite3StrAccumFinish(&acc));
}
/*
** Format and write a message to the log if logging is enabled.
*/
void sqlite3_log(int iErrCode, const char *zFormat, ...){
va_list ap; /* Vararg list */
if( sqlite3GlobalConfig.xLog ){
va_start(ap, zFormat);
renderLogMsg(iErrCode, zFormat, ap);
va_end(ap);
}
}
または、libpngをご覧ください。デフォルトでは、エラーは stderr に記録されますが、それをオーバーライドする独自のコールバックを提供できます。を呼び出して、void ポインターと 2 つのコールバック (1 つはエラー用、もう 1 つは警告用png_set_error_fn()
) を渡します。png_struct
次に、2 つの引数を使用して関数ポインターを呼び出します。を使用して void ポインターにpng_struct
アクセスできます。繰り返しますが、libpng は , を処理し、ロギング コールバックに1 つを渡すだけです。png_get_error_ptr()
char *
snprintf()
char *
たとえば、ライブラリの構成では、ユーザー定義の関数ポインターを使用できます。
struct my_lib_config
{
void (* log) (const char * message);
};
賢明なデフォルトで:
void log_default (const char * message)
{
fprintf (stderr, "%s\n", message);
}
if (!config.log)
config.log = log_default;
log
これにより、アプリケーションが関数ポインタを独自の関数に設定しない限り、ライブラリはデフォルトで stderr にログを記録します。