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

 
GeneralGood start PinmemberDanny-T210-Sep-08 14:02 
Looking forward to more.
 
When? Poke tongue | ;-P
 
Danny T
 
-- Ain't no monolithic framework yet worth the hassles --

GeneralRe: Good start PinmemberMatt (D) Wilson10-Sep-08 14:09 
GeneralRe: Good start PinmemberDanny-T210-Sep-08 14:19 

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