Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Using Callback Back-ends with the Pantheios Logging API Library

4.88/5 (29 votes)
19 Jun 2008CPOL11 min read 2   246  
A tutorial on using the Pantheios logging API library with Visual Studio, and customising output behaviour using callback back-ends.

Introduction

A user of the Pantheios logging API library wished to know how to use the callback functionality of the be.WindowsConsole back-end. This article describes how to do that, and also provides a general tutorial for how to select Pantheios back-ends in a Windows application. I'm going to be using Visual Studio 2005; users of other IDEs should be able to readily translate the necessary steps to their chosen toolset(s). The article takes the form of a series of instructions, and pertinent information about the steps involved are given as we go, in a loosely test-driven manner, as follows:

  • Create the project, and adjust the character-encoding settings
  • Write some logging code
  • Install and build Pantheios (and STLSoft)
  • Satisfy the compiler (by adjusting project settings) to let it know where to find the library headers
  • Satisfy the linker (by adjusting project settings) to let it know where to find the object libraries
  • Run the program
  • Incorporate callback functionality to customise the behaviour of be.WindowsConsole.

Step 1: Create a Windows Console Project

Open up Visual Studio and select File-New Project. In the Project Types tree, open the Visual C++ root node, and select the Win32 sub-node. From the Templates list select Win32 Console Application. Then select a project location and name: we'll be using H:\dev\examples\Pantheios and LogToConsole for the purposes of this tutorial. Figure 1 demonstrates all the steps.

Figure 1 - Creating the Visual Studio Project

Creating the Visual Studio Project

From the Win32 Application Wizard - LogToConsole window that appears next, select Application Settings from the tree on the left-hand side, and you'll see a dialog like that shown in Figure 2.

Figure 2 - Selecting the application settings

Selecting the application settings

If you want to change any of these settings, except Application type, feel free. Click Finish and generate the project. Now we have a project that contains the three source files stdafx.h, stdafx.cpp and LogToConsole.cpp, and we can start writing code.

The final action is to change the generated project to use multibyte character-encoding — TCHAR === char — rather than the default widestring character-encoding — TCHAR === wchar_t — because the current version of Pantheios does not support the widestring encoding. (This is a feature slated to be added in version 1.2, which should be out around the end of this year.) Right click on the LogToConsole node in the solution explorer, and select Properties, and select the General sub-node under the C/C++ sub-node of the Configuration Properties node, as shown in Figure 3. Remember to select the All Configurations option in the Configuration: combo box.

Figure 3 - Setting the character encoding

Setting the character encoding

Step 2: Create a Windows Console Project

The Pantheios Application Layer provides for logging of statements of between and 1 and 32 elements, at arbitrary severity level, as in:

C++
pantheios::log(pantheios::alert, "fatal processing condition");

pantheios::log(pantheios::debug, "the time is ", tm, "; and the text is", text);

We'll start with a Debug-level log statement to record the entry to main(), as in:

C++
int _tmain(int argc, _TCHAR* argv[])
{
    pantheios::log(pantheios::debug, "Entering main(", pantheios::args(argc, argv,
        pantheios::args::arg0FileOnly), ")");

    return 0;
}

When we now compile this, we will naturally get compile errors because we've not included the appropriate header files.

First, we must include the Pantheios Application Layer main header, pantheios/pantheios.hpp. We also include the pantheios::args inserter class header, pantheios/inserters/args.hpp. Compiling now gives:

h:\dev\examples\pantheios\logtoconsole\logtoconsole\logtoconsole.cpp(6) : fatal error C1083: Cannot open include file: 'pantheios/pantheios.hpp': No such file or directory

Clearly, we need to tell the compiler where to find the Pantheios headers.

Step 3: Installing and Building Pantheios

I'll assume you don't already have Pantheios, so we'll cover the whole process of obtaining the necessary libraries, installing and building them. First, I must point out that Pantheios depends on several other open-source libraries (all sharing the simplified BSD license). All of these except one are bundled with the Pantheios distribution. That one is STLSoft, a 100% header-only open-source library that provides STL extensions and various other compiler-/platform-discriminating features that enable other open-source libraries to be implemented simply. (It also uses the simplified BSD license.) You'll need to download and install both STLSoft and Pantheios; only the latter needs to be built, of course. Here are all the steps:

  1. Download the latest STLSoft distribution. At the time of writing, this is 1.9.45.
  2. Unzip to a directory of your choice. For this article we'll assumed h:\3pty\stlsoft-1.9.45.
  3. Create an environment variable STLSOFT and assign it the value h:\3pty\stlsoft-1.9.45. This environment variable is used in the makefiles of the Pantheios distribution (and those of all other STLSoft-dependent libraries).
  4. Download the latest Pantheios main library distribution. At the time of writing, this is 1.0.1 beta 139.
  5. Unzip to a directory of your choice. For this article we'll assume h:\3pty\pantheios-1.0.1-beta139.
  6. Create an environment variable PANTHEIOS_ROOT and assign it the value h:\3pty\pantheios-1.0.1-beta139. (In case you're interested, it's my convention to use an environment variable XYZ_ROOT for a library XYZ that will build libraries and therefore have a lib sub-directory in addition to the include one. STLSOFT doesn't follow this because it's header only and only has the include sub-directory.)
  7. Open the Visual Studio 2005 Command Prompt. (This is to be found in the Visual Studio Tools sub-menu of the Microsoft Visual Studio 2005 menu.) Change directory to h:\3pty\pantheios-1.0.1-beta139\build\vc8.
  8. Type "nmake build" to build the Pantheios libraries. (Be prepared for 3-5 minutes of building, depending on the speed of your system.) Optionally, if you want to run the tests you can type "nmake test.unit" to run the unit tests, or "nmake test.component" to run the component tests, or just type "nmake test" to run both sets.

At the end of this series of steps, you're ready to use Pantheios (and STLSoft) in your Visual C++ projects.

Step 4: Incorporate the Pantheios Directories in your Project

All we need to do now to satisfy the needs of the compiler is to specify the include directories $(PANTHEIOS_ROOT)/include and $(STLSOFT)/include in the project settings, as shown in Figure 4.

Figure 4 - Setting the project's INCLUDE directories

Setting the project's INCLUDE directories

Again, remember to set it for all configurations. (It's also a good idea to set the warning level to maximum, /W4, and to treat warnings as errors, /WX.)

If you compile the project again, it compiles successfully. However, we now get linker errors, along the following lines:

LogToConsole.obj : error LNK2019: unresolved external symbol "public: __thiscall pantheios::args::args(int,char const * const *,int)" (??0args@pantheios@@QAE@HPBQBDH@Z) referenced in function _main
LogToConsole.obj : error LNK2019: unresolved external symbol _pantheios_isSeverityLogged referenced in function "int __cdecl pantheios::log<char const [15],class pantheios::args,char const [2]>(int,char const (&)[15],class pantheios::args const &,char const (&)[2])" (??$log@$$BY0P@$$CBDVargs@pantheios@@$$BY01$$CBD@pantheios@@YAHHAAY0P@$$CBDABVargs@0@AAY01$$CBD@Z)
LogToConsole.obj : error LNK2019: unresolved external symbol _pantheios_log_3_no_test referenced in function "int __cdecl pantheios::internal::log_dispatch_3(int,unsigned int,char const *,unsigned int,char const *,unsigned int,char const *)" (?log_dispatch_3@internal@pantheios@@YAHHIPBDI0I0@Z)
. . .
LogToConsole.obj : error LNK2019: unresolved external symbol _pantheios_uninit referenced in function "public: __thiscall pantheios_initialiser::~pantheios_initialiser(void)" (??1pantheios_initialiser@@QAE@XZ)
H:\dev\examples\Pantheios\LogToConsole\Debug\LogToConsole.exe : fatal error LNK1120: 9 unresolved externals

The compiler doesn't know where to find the Pantheios libraries. To fix this we again need to change the project settings, as shown in Figure 5.

Figure 5 - Setting the project's LIB directories

Setting the project's LIB directories

However, this alone does not solve the problem, because we've not yet told the project which libraries to link to. (This is a more complex issue than is usual with most libraries because Pantheios is designed as a logging API library, and the user is expected to provide the transport mechanism in the form of a "back-end". A number of stock back-ends are provided as a convenience. A full discussion of that is outside the scope of this article. Since the remit of this article is to use be.WindowsConsole, we'll restrict ourselves to that.)

The first decision to make when it comes to linking is to decide whether you want to use explicit or implicit linking. Of course, if your compiler does not support implicit linking, then that's an easy decision. Since Visual C++ does support it, that's what we'll use in this tutorial. Explicit linking will be discussed in a future article.

When incorporating implicit linking into a project, I prefer to separate it from the functional code by placing it all in an implicit link file implicit_link.cpp. Right click on the Source Files folder in the Solution Explorer view and select Add-New Item. Then select C++ file (.cpp) and name it, as shown in Figure 6.

Figure 6 - Adding a new source file to the project

Adding a new source file to the project

Add the following lines in the implicit_link.cpp file:

C++
#include <pantheios/implicit_link/core.h>
#include <pantheios/implicit_link/fe.simple.h>
#include <pantheios/implicit_link/be.WindowsConsole.h>

(You'll also have to add #include <stdafx.h>, before any of the others, if you opted for pre-compiled headers in the project wizard.)

The first header is for linking in the Pantheios core — you'll always need this. The second is for the fe.simple front-end, one of several stock Pantheios front-ends. This one allows logging at all levels in a debug build, and at all levels except Debug and Informational in a release build. The last header is for the be.WindowsConsole back-end, which writes out to the Windows console with severity-based colouring, as we'll see shortly.

With these final modifications we have one last linker error to deal with:

pantheios.1.fe.simple.vc8.dll.debug.lib(fe.simple.dll.debug.obj) : error LNK2001: unresolved external symbol _PANTHEIOS_FE_PROCESS_IDENTITY

Each of the stock front-ends references an application-defined const char array that defines the application identity. So in the main application file, LogToConsole.cpp, we add the following:

C++
extern "C" const char PANTHEIOS_FE_PROCESS_IDENTITY[] = "LogToConsole";

Note that the application identity may not contain any whitespace characters.

Step 5: Running the Program

If you build and run the application in debug configuration, you'll see a console like that shown in Figure 7:

Figure 7 - Running the application with the first logging statement

Running the application with the first logging statement

Let's now expand the functionality of the program and get output at all stock severity levels to demonstrate the severity-specific colouring of be.WindowsConsole. The full implementation of main() now looks like the following:

C++
int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
        pantheios::log(pantheios::debug, "Entering main(", pantheios::args(argc,
            argv, pantheios::args::arg0FileOnly), ")");

        pantheios::log_DEBUG("debug");
        pantheios::log_INFORMATIONAL("informational");
        pantheios::log_NOTICE("notice");
        pantheios::log_WARNING("warning");
        pantheios::log_ERROR("error");
        pantheios::log_CRITICAL("critical");
        pantheios::log_ALERT("alert");
        pantheios::log_EMERGENCY("EMERGENCY");

        return EXIT_SUCCESS;
    }
    catch(std::bad_alloc&)
    {
        pantheios::log_ALERT("out of memory");
    }
    catch(std::exception& x)
    {
        pantheios::log_CRITICAL("Exception: ", x);
    }
    catch(...)
    {
        pantheios::puts(pantheios::emergency, "Unexpected unknown error");
    }

    return EXIT_FAILURE;
}

(Note that, for concision, I've used the severity-specific log overloads, log_DEBUG(), log_ALERT(), etc. These are exactly equivalent to using log() and specifying the given severity explicitly.)

If we run this we get the output shown in Figure 8.

Figure 8 - Running the application with all logging statements

Running the application with all logging statements

Step 6: Customising the Output via Callback

You would have noticed from Figures 7 and 8 that each logging statement is prefixed by a sequence of fields, comprising process identity, date/time, and severity level. Many of the Pantheios stock back-ends do the same, including: be.file, be.fprintf, be.speech and be.WindowsDebugger. This format is a best guess default for those users who don't want to write their own back-ends. However, several back-ends still offer some degree of configurability, in the form of the so-called "Callback" variants. Let's see how this works by changing our application to use the callback version of be.WindowsConsole.

The first thing we need to do is change the entry in implicit_link.cpp, from:

C++
#include <pantheios/implicit_link/core.h>
#include <pantheios/implicit_link/fe.simple.h>
#include <pantheios/implicit_link/be.WindowsConsole.h>

to :

C++
#include <pantheios/implicit_link/core.h>
#include <pantheios/implicit_link/fe.simple.h>
#include <pantheios/implicit_link/be.WindowsConsole.WithCallback.h>

Now when we build the project, we'll get a linker error:

error LNK2019: unresolved external symbol _pantheios_be_WindowsConsole_getAppInit referenced in function "int __cdecl pantheios_be_WindowsConsole_init_(char const *,int,struct pan_be_WindowsConsole_init_t const *,void *,void * *)"

That's because the callback version of be.WindowsConsole is implemented in terms of an application-defined function, pantheios_be_WindowsConsole_getAppInit(). This function's prototype is defined in the header file pantheios/backends/bec.WindowsConsole.h, and looks like this:

C++
PANTHEIOS_CALL(void) pantheios_be_WindowsConsole_getDefaultAppInit(
    pan_be_WindowsConsole_init_t* init) /* throw() */;

We need to #include this header in our application file, and then implement this function. For pedagogical purposes, let's hide the process identity and the date, use high-resolution timing, and colour the Debug and Notice severity levels the same colour as Informational. This is achieved as follows:

C++
PANTHEIOS_CALL(void) pantheios_be_WindowsConsole_getAppInit(int /* backEndId */, 
    pan_be_WindowsConsole_init_t* init) /* throw() */
{
    init->flags |=  PANTHEIOS_BE_INIT_F_NO_PROCESS_ID;
    init->flags |=  PANTHEIOS_BE_INIT_F_HIDE_DATE;
    init->flags |=  PANTHEIOS_BE_INIT_F_HIGH_RESOLUTION;

    init->colours[PANTHEIOS_SEV_DEBUG] = init->colours[PANTHEIOS_SEV_INFORMATIONAL];
    init->colours[PANTHEIOS_SEV_NOTICE] = init->colours[PANTHEIOS_SEV_INFORMATIONAL];
}

We are only using one back-end in this tutorial, so we don't need to pay attention to the backEndId parameter; that'll be covered in a future article. All customisations occur via the init parameter.

The colours are altered by means of copying the (already assigned) colour value in the colours member of pan_be_WindowsConsole_init_t structure at index [PANTHEIOS_SEV_INFORMATIONAL] to the elements at indexes [PANTHEIOS_SEV_DEBUG] and [PANTHEIOS_SEV_NOTICE].

This code alters the log statement details via the three flags. All three are common flags (defined in pantheios/backend.h, which is #included from pantheios/backends/bec.WindowsConsole.h) that are recognised by several stock back-ends:

  • PANTHEIOS_BE_INIT_F_NO_PROCESS_ID omits the process identity field
  • PANTHEIOS_BE_INIT_F_HIDE_DATE causes the date part of the date/time field to be suppressed
  • PANTHEIOS_BE_INIT_F_HIGH_RESOLUTION uses the best resolution available on a given platform/operating-system

Some back-ends also have specific flags. For example, be.WindowsConsole defines PANTHEIOS_BE_WINDOWSCONSOLE_F_NO_COLOURS, which causes the use of all severity-specific colouring to be suppressed.

The results of all the modifications to the runtime behaviour can be seen in Figure 9.

Figure 9 - Running the application with all logging statements after customising the output characteristics

Running the application with all logging statements after customising the output characteristics

Summary

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 covering best-practice and discussing why Pantheios offers 100% type-safety with unbeatable performance.

Please feel free to post questions on the forums on the Pantheios project site on SourceForge.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)