Logging¶
#include "libstephen/log.h"
Logging is a very useful way to record messages about your program’s
operation so you can debug it later. Since C’s debugging tools are
rather difficult (encouraging so-called printf
debugging), having a
sane logging setup that can be turned on or off easily is important.
Libstephen provides a very nice logging system that satisfies these
requirements. It is inspired by Python’s logging system, but somewhat
simpler.
History¶
When I first created Libstephen, I wanted a way to produce debugging
printouts and disable them when I didn’t need them. I came up with a
macro (SMB_DP
) that would expand to printf
when DEBUG
was
defined, or else expand to nothing. This worked well (so long as I
didn’t rely on any side effects of expressions within the arguments!).
However, as I started to link to and use Libstephen within other
projects, I ran into issues with this system.
First was that if client code linked to a debug build of Libstephen, it could get a flood of output from my own logging (that was enabled for debugging). Second, I began to need more control over logging. There was no control for enabling logging for just certain modules, or just messages of a certain priority. That is what inspired me to build a Python-like logging framework.
Goals¶
I focused on the following development goals:
- In the common case, the logger should be a drop-in replacement for
printf
. It shouldn’t require much extra typing or arguments. - There should be levels of “priority”, from fine grained debugging output, to critical errors only. You should be able to set how much your logger shows.
- Logging output should be able to be handled by multiple handlers (files). These handlers should be allowed to set their own log levels in addition to the logger.
- Log messages should tell you exactly where in the code they came from. In Emacs’ compilation mode, you should be able to hit enter on a log message and go directly to its source.
Concepts¶
The logging system is made up of loggers (smb_logger
), which are
collections of log handlers (smb_loghandler
), along with a log
level. Log handlers have their own log level, and they have a file
associated with them. The logging system also has a single “default”
logger, which is used when you don’t specify a logger. At startup, the
default logger will report any level message to stderr
.
The log levels are defined as macros expanding to integers:
#define LEVEL_NOTSET 0
#define LEVEL_DEBUG 10
#define LEVEL_INFO 20
#define LEVEL_WARNING 30
#define LEVEL_ERROR 40
#define LEVEL_CRITICAL 50
You can log at any integer level, so these levels are just suggestions, but they come with nice macros for logging.
Functions & Macros¶
You can create a smb_logger
using the default lifetime functions
(sl_*
), which take no extra arguments. You can use the following
functions to adjust loggers:
void sl_set_level(smb_logger *l, int level);
void sl_add_handler(smb_logger *l, smb_loghandler h, smb_status *status);
void sl_clear_handlers(smb_logger *l);
void sl_set_default_logger(smb_logger *l);
Each of these functions can take NULL
in order to reference the
default logger. As you can see, you can also set the default logger so
that code that logs to the default logger will use your own instead. If
you call sl_set_default_logger(NULL)
, the default logger should be
completely reset to defaults.
Once you have your loggers (which you don’t have to do immediately,
since you could just use the default logger), you can use the LOG
macro to begin logging:
LOG(pointer to logger, log level, "format string", format args...)
This is a macro, so in general avoid using side effects within the arguments. The current implementation will not evaluate expressions more than once, but I make no guarantee about that.
If you’re using the default logger, you have the following shortcuts:
DDEBUG("format string", format args ...)
DINFO("format string", format args ...)
DWARNING("format string", format args ...)
DERROR("format string", format args ...)
DCRITICAL("format string", format args ...)
Think of the D
as standing for Default. If you are using any other
logger, you have these shortcuts:
LDEBUG(logger, "format string", format args ...)
LINFO(logger, "format string", format args ...)
LWARNING(logger, "format string", format args ...)
LERROR(logger, "format string", format args ...)
LCRITICAL(logger, "format string", format args ...)
Think of the L
as standing for Logger. You have to specify your
logger.
All of these macros expand to call a single function:
void sl_log(smb_logger *l, char *file, int line, const char *function, int level, ...);
You don’t want to call this function yourself, since most of those arguments (file, line, function) are provided by preprocessor arguments.