Introduction
As well as providing a C++ API, the Pantheios
logging API library also provides a C API for logging in C compilation units. This article provides a quick tutorial on how to use the C API for adding logging to your C programs, as well as offers some contrasts between the C and C++ APIs. This tutorial will not cover issues such as configuring and linking to Pantheios
; readers will be helped by first consulting the introductory article on building and using Pantheios.
1: Initialisation
Use of the Pantheios
C API first requires explicit initialisation, via the functions pantheios_init()
and pantheios_uninit()
. pantheios_uninit()
must be called once for each invocation of pantheios_init()
that yields a return value >= 0. A negative value indicates that initialisation failed, in which case the only function that may be called is pantheios_getInitErrorString()
. Both code paths are shown in the following example:
#include <pantheios/pantheios.h>
#include <stdio.h>
#include <stdlib.h>
extern const char PANTHEIOS_FE_PROCESS_IDENTITY[] = "pantheios-C";
int main(int argc, char** argv)
{
int panres = pantheios_init();
if(panres < 0)
{
fprintf(stderr, "Failed to initialise the Pantheios libraries: %s\n",
pantheios_getInitErrorString(panres));
return EXIT_FAILURE;
}
else
{
pantheios_uninit();
return EXIT_SUCCESS;
}
}
The requirement for explicit initialisation is in contrast to the C++ API. where initialisation is automatic upon inclusion of pantheios/pantheios.hpp.
(If your link unit contains one or more C++ compilation units that include pantheios/pantheios.hpp, and your link unit is not a DLL, and you've not defined PANTHEIOS_NO_AUTO_INIT
, then you can omit explicit initialisation in the main C source file. However, it's best to do it anyway: it's idiomatic for using Pantheios
in C, and you might later remove, or rewrite in C, the C++ compilation unit(s), and then find that your program mysteriously fails to run, or even to tell you why!)
2: Using the C API
The Pantheios C API is based on the printf()
-family of functions. This has consequences for syntax, robustness and the genericity and extensibility of the API.
The main logging function in the API is pantheios_logprintf()
. (The long name is to avoid any nameclashes in the global C namespace, since it's highly likely that there'll be logprintf()
functions out there.) pan_sev_t
is a typedef to a 32-bit signed integer.
PANTHEIOS_CALL(int) pantheios_logprintf(pan_sev_t severity
, char const* format
, ...);
Syntactically, the specification of format strings and arguments is identical to printf()
, as in:
int i = 10;
double d = 9.9;
pantheios_logprintf(PANTHEIOS_SEV_NOTICE, "i=%d, d=%G", i, d);
The only differences are that pantheios_logprintf()
takes a severity level as its first parameter, and it's not necessary to specify a carriage return ('\n'
) in the format string.
There are two other functions in the Pantheios
C API: pantheios_logvprintf()
and pantheios_logputs()
.
PANTHEIOS_CALL(int) pantheios_logvprintf( pan_sev_t severity
, char const* format
, va_list args);
PANTHEIOS_CALL(void) pantheios_logputs( pan_sev_t severity
, char const* message);
The former takes an array of arguments in the same way as does vprintf()
. The latter is a logging analogue for puts()
, which processes the single C-style string
directly through the Pantheios
Core and out to the back-end(s). Consequently it is recommended for use when programs are experiencing unexpected behaviour as a best-chance attempt at writing to the log before termination. (Note: As is the case with any function in such circumstances, success is not guaranteed.)
3: Argument Types
The second consequence of the printf()
-like syntax of the C API is the restriction to types that printf()
understands: integers, floating-point types, and C-style string
s. This is in stark contrast to the Pantheios
C++ API, which understands a great many types out of the box, and is infinitely extensible.
It also means that the C API is not type-safe. Passing an integer to pantheios_logprintf()
when a C-style string
is expected is just as likely to crash the process as it is for printf()
. Once again, this is in contrast with the C++ API, which is 100% type-safe.
4: Logging Custom Types
Unlike the C++ API, there is no assistance available in the C API for the logging of custom types. Consider the following example, where an IPv4 address argument is logged on entry to a function:
int connect_to_peer(struct in_addr const* addr)
{
pantheios_logprintf(PANTHEIOS_SEV_DEBUG
, "connect_to_peer(%u.%u.%u.%u)"
, (NULL == addr) ? 0 : ((addr->s_addr & 0x000000ff) >> 0)
, (NULL == addr) ? 0 : ((addr->s_addr & 0x0000ff00) >> 8)
, (NULL == addr) ? 0 : ((addr->s_addr & 0x00ff0000) >> 16)
, (NULL == addr) ? 0 : ((addr->s_addr & 0xff000000) >> 24));
. . .
This is a lot of heavy boilerplate, and must be repeated (carefully) in each place an in_addr
instance must be logged. Contrast this with the C++ API, which understands the in_addr
type along with many others, and can be readily extended to work with any type you wish:
int connect_to_peer(struct in_addr const* addr)
{
pantheios::log_DEBUG("connect_to_peer(", addr, ")");
. . .
An alternative approach for C, which offers greater robustness and transparency of the application code, is to use a helper converter function, as in:
char const* convert_addr(char* buff, size_t cchBuff, struct in_addr const* addr);
int connect_to_peer(struct in_addr const* addr)
{
char buff[16];
pantheios_logprintf(PANTHEIOS_SEV_DEBUG
, "connect_to_peer(%s)"
, convert_addr(&buff[0], STLSOFT_NUM_ELEMENTS(buff), addr));
. . .
The downside is that the conversion always takes place, regardless of whether logging at the Debug level is currently enabled or not. With the previous explicit form, no conversion is undertaken until after the severity level is checked.
The downloadable project contains implementations of both these approaches, along with the main program, to illustrate the differences between them.
Summary
We've seen how to use the Pantheios
C API, how to initialise it (including reporting errors in the initialisation), how to log basic types, and how to log custom types.
We've seen that when logging custom types with the C API, you are forced to make compromises between efficiency and reuse and expressiveness. With the C++ API no such compromises are necessary - it is 100% type-safe and only ever performs conversions when they're going to be used. Naturally, the advice from the Pantheios
team is to prefer to use the C++ API when you can (in C++ compilation units); when you can't the C API offers many, but not all, of the benefits of Pantheios
.
That's a brief introduction to using Pantheios
with the be.WindowsConsole
backend, with and without callback functionality.
There's a whole lot more to the world of Pantheios
, and in future articles I will explain more features, as well as cover best-practice and discuss how Pantheios
offers 100% type-safety with unbeatable performance.
Please feel free to post questions on the forums on the Pantheios project site on SourceForge.
History
- 20th June, 2008: Initial version