laslowh
laslowh

Reputation: 8604

How to allow C libraries to use caller's logging?

This style-guide for Linux libraries suggests "Allow the app to hook the libs logging into its logging facility." By what mechanism is this typically done? A function that takes a pointer to a logging function? Can you show examples?

Upvotes: 1

Views: 584

Answers (2)

Brian Campbell
Brian Campbell

Reputation: 332836

Here's an example from SQLite. You can set up how it logs by calling sqlite3_config() with a CONFIG_LOG argument. You pass in a function pointer, and a void pointer. The function pointer takes three arguments; a void pointer, an integer, and a char *. When SQLite needs to log something, it calls your function pointer, passing in the void pointer that you passed to CONFIG_LOG, a result code, and a string containing the log message.

SQLITE_CONFIG_LOG

The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a function with a call signature of void(*)(void*,int,const char*), and a pointer to void. If the function pointer is not NULL, it is invoked by sqlite3_log() to process each logging event. If the function pointer is NULL, the sqlite3_log() interface becomes a no-op. The void pointer that is the second argument to SQLITE_CONFIG_LOG is passed through as the first parameter to the application-defined logger function whenever that function is invoked. The second parameter to the logger function is a copy of the first parameter to the corresponding sqlite3_log() call and is intended to be a result code or an extended result code. The third parameter passed to the logger is log message after formatting via sqlite3_snprintf(). The SQLite logging interface is not reentrant; the logger function supplied by the application must not invoke any SQLite interface. In a multi-threaded application, the application-defined logger function must be threadsafe.

You can see the actual implementation of sqlite3_log() in SQLite's printf.c. It uses snprintf() (their wrapper around it, that is) to print into a buffer on the stack, since logging functions may be called when you can't call malloc, and then pass the result back into the function pointer the user has configured, along with the value they provided and the error code.

/*
** 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);
  }
}

Or you could take a look at libpng. By default, it logs errors to stderr, but you can provide your own callback to override that. You call png_set_error_fn(), passing your png_struct, a void pointer, and two callbacks, one for errors and one for warnings. It will then call your function pointers with two arguments; the png_struct, via which you can access your void pointer with png_get_error_ptr(), and a char *. Again, libpng handles the snprintf(), and just passes a single char * to the logging callback.

Upvotes: 2

James M
James M

Reputation: 16718

For example, in the configuration for your library, you could have a user-defined function pointer:

struct my_lib_config
{
    void (* log) (const char * message);
};

With a sensible default:

void log_default (const char * message)
{
    fprintf (stderr, "%s\n", message);
}

if (!config.log)
    config.log = log_default;

With this your library would log to stderr by default, unless the application sets the log function pointer to their own function.

Upvotes: 0

Related Questions