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

An Introduction to Pantheios Back-ends, Part 1: The Back-end API

, 10 Sep 2008
Rate this:
Please Sign up or sign in to vote.
An introduction to the Pantheios logging API library's Back-end API, along with a trivial example to illustrate the concepts

Introduction

If your logging needs are simple, then the stock transports, known-as back-ends, that come with the Pantheios logging API library may serve your needs. But if you're developing programs with high-uptimes, remote control, and other aspects of non-trivial systems that usually require logging, then you will probably need to write custom back-ends. In that case, a detailed understanding of the Pantheios back-end architecture will be essential. This article, the first of a series on Pantheios back-ends, introduces the API, illustrates how to write a very simple custom back-end, and discusses the features of several of the stock back-ends.

Readers may want to check out the previous Pantheios tutorial article on setting up a project and selecting back-ends before reading on.

The Back-end API

The Pantheios architecture is based on four components:

  1. Application Layer - The classes and functions used in application code, responsible for presenting a log statement's elements to the core in a unified form
  2. Core - Handles initialisation of all components, and handles processing of logging statements submitted by the Application Layer
  3. Front-end - Defines the process identity, and arbitrates whether a log statement is to be prepared by the core and emitted to the back-end
  4. Back-end - Emits the prepared logging statement to the transport.

The back-end API consists of three functions:

// declared in pantheios/backend.h
int pantheios_be_init(
  char const* processIdentity
, void*       reserved
, void**      ptoken
);
void pantheios_be_uninit(
  void* token
);
int pantheios_be_logEntry(
  void*       feToken
, void*       beToken
, int         severity
, char const* entry
, size_t      cchEntry
);

pantheios_be_init() and pantheios_be_uninit() are invoked by the core during initialisation/uninitialisation. They are invoked at most once per process, and always (unless someone tries incredibly hard to do something weird) in the main thread. pantheios_be_logEntry() is invoked by the core each time a prepared log statement is to be emitted, on any thread in the process.

A Trivial Back-end

The following hypothetical code shows how these functions might be implemented to log to stdout. First, pantheios_be_init():

// file: hypothetical_backend.c
#include <span class="code-keyword"><pantheios></span>
#include <span class="code-keyword"><pantheios></span>

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

int pantheios_be_init(
  char const* processIdentity
, void*       reserved
, void**      ptoken
)
{
  *ptoken = strdup(processIdentity);
  return (NULL == *ptoken)
    ? PANTHEIOS_INIT_RC_OUT_OF_MEMORY
    : PANTHEIOS_INIT_RC_SUCCESS;
}

This code is invoked by the Pantheios Core during initialisation. It is given the process identity (which is defined by the Front-end; we'll cover this in a future article), which it should copy if it needs it, along with a pointer to a void* within which it may store any value representing its state. This is held on behalf of the back-end by the core, and is passed back into other back-end API functions, as we'll see.

In this case, we'll just copy the process identity, and store that back in *ptoken. If the initialisation is successful, we must return PANTHEIOS_INIT_RC_SUCCESS. The only alternative in this case is to fail if the string cannot be duplicated, returning the indicative error code PANTHEIOS_INIT_RC_OUT_OF_MEMORY. Both error codes are defined in pantheios/error_codes.h, along with a number of other codes representing common (and some uncommon) initialisation failure conditions.

(Note: This implementation uses the non-standard, and therefore non-portable, C function strdup() as a convenience in this case; it's a trivial example, remember.)

In Pantheios, all initialisation is done in pairs, according to the following rule:

Pantheios Initialisation Rule: Any successful call to an initialisation function will always be matched by a call to the corresponding uninitialisation function. An uninitialisation function will never be called if the corresponding initialisation function is unsuccessful.

Bearing this in mind, we can implement pantheios_be_uninit() very simply, as:

void pantheios_be_uninit(
  void* token
)
{
  free(token);
}

The token passed in is the same thing that we wrote to *ptoken in pantheios_be_init(), and we can just free it (on the assumption that our non-standard strdup() allocates using malloc()).

That just leaves the logging function, pantheios_be_logEntry():

int pantheios_be_logEntry(
  void*       feToken
, void*       beToken
, int         severity
, char const* entry
, size_t      cchEntry
)
{
  char const* processIdentity = (char const*)beToken;
  fprintf(stdout, "%s[%d]: %.*s\n", processIdentity, severity, (int)cchEntry, entry);
  return 0;
}

The function takes five parameters:

  1. feToken is the front-end initialisation state. This enables custom front and back-ends to talk to each other. This will be discussed in a future article, and is not considered further here.
  2. beToken is the token we created in pantheios_be_init().
  3. severity is the severity passed in to the logging statement in the application code.
  4. entry is a non-NULL pointer to a nul-terminated C-style string containing the statement text.
  5. cchEntry is the length of the C-style string pointed to by entry.

Providing the string length as well as guaranteeing that the statement string is nul-terminated is somewhat redundant. However, doing so facilitates the easy implementation of back-ends that prefer one form or the other. For example, the be.WindowsDebugger back-end uses the Windows API function OutputDebugStringA(), which takes a pointer to a nul-terminated C-style string. If that back-end had to allocate a buffer of cchEntry + 1, then memcpy() entry into it, and append a nul-terminator before passing to OutputDebugStringA(), that would eat into Pantheios' considerable performance advantages. The converse applies for an output API that requires an explicit length: having to do a strlen() on entry would similarly be a cost we don't want to pay.

In our case, we just pass processIdentity, severity and the statement to fprintf(), which outputs them to standard out in the form: "<processIdentity>[<severity-code>]: <message>"

Because the core maintains the state on behalf of the back-end, the back-end implementation can be very simple, and can, as in this case, be written in C, rather than C++. (Several stock back-ends and front-ends are written in C, partly for the decreased compilation times.) Furthermore, the state can be a lot more complex than a pointer to an allocated block of memory: in a number of stock back-ends it is a pointer to a C++ object, which handles the relative sophistication of the given back-end functionality.

Pantheios' Stock Back-ends

The stock back-ends provided with the current Pantheios distribution are in two groups:

  1. Concrete back-ends
  2. Multiplexing back-ends

The multiplexing back-ends allow for combining two or more concrete back-ends to send logging output to multiple destinations, e.g. console, Syslog and file. These will be discussed in detail in subsequent articles in this series.

The concrete back-ends available are:

  • be.ACE - Outputs using the logging facilities from the Adaptive Communications Environment (ACE) library. This is an example of how Pantheios' superior type-safety and performance can be married to logging libraries with much richer logging facilities.
  • be.COMErrorObject (Windows-only) - Outputs to the COM Error Object. Useful when implementing COM servers, as you can log to file/debugger and update the COM error object, used by automation/scripting clients, in a single statement. A future article will discuss how to do this.
  • be.fail - Always fails initialisation. Used in the automated unit/component testing
  • be.file - Outputs to a file
  • be.fprintf - Outputs to stdout/stderr. This is a full-featured, portable version of the trivial example above
  • be.null - Outputs to the "bit bucket". This is used in performance testing.
  • be.speech (currently Windows-only) - Outputs in the form of speech.
  • be.syslog (UNIX-only) - Outputs by emitting Syslog packets.
  • be.WindowsConsole (Windows-only) - Outputs to the Windows console, with severity-specific colour-coding of statements.
  • be.WindowsDebugger (Windows-only) - Outputs to the Windows debugger using OutputDebugStringA(). This is useful in combination with your other back-ends, as it allows the logging output to be followed from within your IDE.
  • be.WindowsEventLog (Windows-only) - Outputs to the Windows Event Log. Severity levels are translated to Event Log categories (EVENTLOG_ERROR_TYPE, EVENTLOG_WARNING_TYPE, EVENTLOG_INFORMATION_TYPE).
  • be.WindowsMessageBox (Windows-only) - Outputs to a Windows message box. Severity levels are translated into message box types (MB_ICONERROR, MB_ICONWARNING, MB_ICONINFORMATION).

Summary

We've discussed the Pantheios back-end API, and had a look at a trivial implementation of the API, covering how to maintain state and the details of the output function. We've also briefly discussed the stock back-ends and their use.

Building on this base, subsequent articles in this series will cover back-end multiplexing, interaction with custom front-ends, and a deeper look into some of the stock back-ends as a guide to what to do (and what not to do) when implementing your custom back-ends.

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 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.

History

  • 9th September, 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

 
QuestionClarification PinmemberAdi Shavit22-Apr-12 0:15 
Hi Matt,
 
So the implementation goes into a separate .c/.cpp file? No additional includes or headers?
The linking will be done automatically by the linker when this file is included in the project?
 
Thanks,
Adi

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 10 Sep 2008
Article Copyright 2008 by Matt (D) Wilson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid