Click here to Skip to main content
Click here to Skip to main content

Adding Logging to C Programs with the Pantheios C API

, 23 Jun 2008
Rate this:
Please Sign up or sign in to vote.
A tutorial on using the Pantheios logging API library from C compilation units, and a comparison of the features offered by the C and C++ APIs

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 <span class="code-keyword"><pantheios/pantheios.h></span>

#include <span class="code-keyword"><stdio.h></span>
#include <span class="code-keyword"><stdlib.h></span>

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
    {
        /* . . .  rest of program */

        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 strings. 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]; /* space enough for IPv4 */
    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

License

This article, along with any associated source code and files, is licensed under The BSD License

About the Author

Matt (D) Wilson
Instructor / Trainer
Australia Australia
Software Development consultant, specialising in project remediation.
 
Creator of the FastFormat, Pantheios, STLSoft and VOLE open-source libraries.
 
Author of the books Extended STL, volume 1 (Addison-Wesley, 2007) and Imperfect C++ (Addison-Wesley, 2004).

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 23 Jun 2008
Article Copyright 2008 by Matt (D) Wilson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid