libqb  1.0.3
Logging

The logging API provides four main parts (basics, filtering, threading & blackbox).

The idea behind this logging system is not to be prescriptive but to provide a set of tools to help the developer achieve what they want quickly and easily.

Basic logging API.
Call qb_log() to generate a log message. Then to write the message somewhere meaningful call qb_log_ctl() to configure the targets.

Simplest possible use:

main() {
qb_log_init("simple-log", LOG_DAEMON, LOG_INFO);
// ...
qb_log(LOG_WARNING, "watch out");
// ...
}
void qb_log_init(const char *name, int32_t facility, uint8_t priority)
Init the logging system.
#define qb_log(priority, fmt, args...)
This is the main function to generate a log message.
Definition: qblog.h:501
void qb_log_fini(void)
Logging system finalization function.
Note
In practice, such a minimalistic approach hardly caters real use cases. Following section discusses the customization. Moreover when employing the log module is bound to its active use (some log messages are assuredly emitted within the target compilation unit), it's quite vital to instrument the target side with QB_LOG_INIT_DATA() macro placed in the top file scope in exactly one source file (preferably the main one) to be mixed into the resulting compilation unit. This is a self-defensive measure for when the linker-assisted collection of callsite data silently fails, which could otherwise go unnoticed, causing troubles down the road, but alas it cannot discern misuse of QB_LOG_INIT_DATA() macro in no-logging context from broken callsite section handling assumptions owing to overboard fancy linker – situation that the self-check aims to detect in the first place.
Configuring log targets.
A log target can be syslog, stderr, the blackbox, stdout, or a text file. By default, only syslog is enabled. While this is customary for daemons, it is rarely appropriate for ordinary programs, which should promptly disable that when other targets (read on) are to be utilized:
#define QB_FALSE
Definition: qbdefs.h:47
@ QB_LOG_CONF_ENABLED
Definition: qblog.h:573
int32_t qb_log_ctl(int32_t target, enum qb_log_conf conf_type, int32_t arg)
Main logging control function.

To enable a target do the following:

#define QB_TRUE
Definition: qbdefs.h:48
@ QB_LOG_BLACKBOX
Definition: qblog.h:553

syslog, stderr, the blackbox, and stdout are static (they don't need to be created, just enabled or disabled). However, you can open multiple logfiles (falling within inclusive range QB_LOG_TARGET_DYNAMIC_START up to QB_LOG_TARGET_DYNAMIC_END). To do this, use the following code:

mytarget = qb_log_file_open("/var/log/mylogfile");
int32_t qb_log_file_open(const char *filename)
Open a log file.

Once your targets are enabled/opened, you can configure them as follows: Configure the size of blackbox:

@ QB_LOG_CONF_SIZE
Definition: qblog.h:576

Make logging to file threaded:

@ QB_LOG_CONF_THREADED
Definition: qblog.h:577

Sometimes, syslog daemons are (pre)configured to filter messages not exceeding a particular priority. When this happens to be the logging target, the designated priority of the message is passed along unchanged, possibly resulting in message loss. For messages up to LOG_DEBUG importance, this can be worked around by proportionally bumping the priorities to be passed to syslog (here, the step is such that LOG_DEBUG gets promoted to LOG_INFO):

LOG_INFO - LOG_DEBUG);
@ QB_LOG_SYSLOG
Definition: qblog.h:551
@ QB_LOG_CONF_PRIORITY_BUMP
Definition: qblog.h:578

To ensure all logs to file targets are fsync'ed (new messages expressly transferred to the storage device as they keep coming, otherwise defaults to QB_FALSE):

@ QB_LOG_CONF_FILE_SYNC
Definition: qblog.h:580
Filtering messages.
To have more power over what log messages go to which target you can apply filters to the targets. What happens is the desired callsites have the correct bit set. Then when the log message is generated it gets sent to the targets based on which bit is set in the callsite's "target" bitmap. Messages can be filtered based on the:
  1. filename + priority
  2. function name + priority
  3. format string + priority

So to make all logs from evil_function() go to stderr, do the following:

QB_LOG_FILTER_FUNCTION, "evil_function", LOG_TRACE);
@ QB_LOG_FILTER_ADD
Definition: qblog.h:595
@ QB_LOG_STDERR
Definition: qblog.h:552
int32_t qb_log_filter_ctl(int32_t value, enum qb_log_filter_conf c, enum qb_log_filter_type type, const char *text, uint8_t low_priority)
This allows you modify the 'tags' and 'targets' callsite fields at runtime.
@ QB_LOG_FILTER_FUNCTION
Definition: qblog.h:587
#define LOG_TRACE
Definition: qblog.h:265

So to make all logs from totem* (with a priority <= LOG_INFO) go to stderr, do the following:

QB_LOG_FILTER_FILE, "totem", LOG_INFO);
@ QB_LOG_FILTER_FILE
Definition: qblog.h:586

So to make all logs with the substring "ringbuffer" go to stderr, do the following:

@ QB_LOG_FILTER_FORMAT
Definition: qblog.h:588
Thread safe non-blocking logging.
Logging is only thread safe when threaded logging is in use. If you plan on logging from multiple threads, you must initialize libqb's logger thread and use qb_log_filter_ctl to set the QB_LOG_CONF_THREADED flag on all the logging targets in use.

To achieve non-blocking logging, so that any calls to write() or syslog() will not hold up your program, you can use threaded logging as well.

Threaded logging use:

main() {
qb_log_init("simple-log", LOG_DAEMON, LOG_INFO);
// ...
daemonize();
// call this after you fork()
// ...
qb_log(LOG_WARNING, "watch out");
// ...
}
int32_t qb_log_thread_start(void)
Start the logging pthread.
A blackbox for in-field diagnosis.
This stores log messages in a ringbuffer so they can be written to file if the program crashes (you will need to catch SIGSEGV). These can then be easily printed out later.
Note
the blackbox is not enabled by default.

Blackbox usage:

static void sigsegv_handler(int sig)
{
(void)signal (SIGSEGV, SIG_DFL);
qb_log_blackbox_write_to_file("simple-log.fdata");
raise(SIGSEGV);
}
main() {
signal(SIGSEGV, sigsegv_handler);
qb_log_init("simple-log", LOG_DAEMON, LOG_INFO);
QB_LOG_FILTER_FILE, "*", LOG_DEBUG);
// ...
qb_log(LOG_WARNING, "watch out");
// ...
}
ssize_t qb_log_blackbox_write_to_file(const char *filename)
Write the blackbox to file.
Tagging messages.
You can tag messages using the second argument to qb_logt() or by using qb_log_filter_ctl(). This can be used to add feature or sub-system information to the logs.
const char* my_tags_stringify(uint32_t tags) {
return "libqb";
} else if (tags == 3) {
return "three";
} else {
return "MAIN";
}
}
main() {
// ...
qb_log_tags_stringify_fn_set(my_tags_stringify);
// ...
qb_logt(LOG_INFO, 3, "hello");
qb_logt(LOG_INFO, 0, "hello");
}
#define qb_bit_is_set(barray, bit)
Definition: qbdefs.h:56
void qb_log_format_set(int32_t t, const char *format)
Set the format specifiers.
#define qb_logt(priority, tags, fmt, args...)
This is the function to generate a log message if you want to manually add tags.
Definition: qblog.h:478
void qb_log_tags_stringify_fn_set(qb_log_tags_stringify_fn fn)
Set the callback to map the 'tags' bit map to a string.
#define QB_LOG_TAG_LIBQB_MSG_BIT
Definition: qblog.h:407
uint32_t tags
Definition: qblog.h:6

The code above will produce:

[libqb] some message
[three] info hello
[MAIN ] info hello
See also
qblog.h