Chapter 1. Developer's Guide

Table of Contents

1.1. Logging
1.1.1. Diagnostic Data
1.1.2. Formatter
1.1.3. Appender
1.1.4. Logger
1.2. Argument Formatting
1.3. Chainsaw As Logging Host
1.4. Working With Embedded Systems
1.4.1. The Problem
1.4.2. The Solution
1.5. Collecting Data From Testing Frameworks
1.5.1. CppUnit
1.5.2. Boost
1.5.3. Qt

1.1. Logging

The client library log4j from the Apache Logging Framework contains a quite large number of features. log4sendpp adopts the basic working methods and implements the most important features. For that reason it should not be too difficult to switch to a more feature rich logging libary like log4cxx if you need to.

All features are basically grouped into for categories:

  • Diagnostic data encompass basic data like a user defined message and the according sourcefile and line number. But there is also extended data to describe the current context of the running process and even thread.

  • A formatter converts the diagnostic data into a representation that can be sent to some consumer. A consumer may be a remote host or a simple data storage like a regular file.

  • An appender sends the converted diagnostic data to the according consumer. An appender can write into a regular file or send over a network.

  • A logger finally ties everything together and provides simple means to send logging data.

1.1.1. Diagnostic Data

There are several ways to handle diagnostic data which are grouped into the following two types:

  • Local information about a few of the current statements. This information is encapsulated in class LocationInformation.

  • The general context which can describe more of the current programm flow. This extended information is handled with the classes MDC, NDC, Property and ThreadDictionary which are explained below.

LocationInformation

This class contains information about the location from where the log messsage came.

NDC

Stands for Nested Diagnostic Context. Here you can append descriptions about the course of your programm flow. Every time you send a log message the whole content of the NDC is sent. An NDC is organized like a stack. So it is possbile to remove descriptions one after the other when the program leaves a context.

Property

A property is a simple pair of a name and a value. You can used whatever name you wish to, but some properties are already in use. application and hostname contain the values you passed to initialize log4sendpp. Similar to the former NDC all properties are included when you send a log message.

MDC

MDC stand for Mapped Diagnostic Context. It works similar to Property but is intended for example to help distinguish the flow of threads. All MDC values are included when you send a log message.

ThreadDictionary

This dictionary helps to distinguish all the running threads by assigning a nickname to a platform dependent thread identifier. This nickname is then used in a LocationInformation.

1.1.2. Formatter

log4sendpp offers two formatters to cover the common use cases. Mainly intended for logging to files in a format used by syslog is SimpleFormatter

1

At the beginning of each line there is always the timestamp and the log level.

2

A log message can spawn multiple lines. A block always starts with an opening bracket and ends with a closing. On multiple lines a dot denotes the continuation lines.


  [ 18:05:37,4001ERROR ] first message

  [ 18:05:37,400 ERROR . second message
  . 18:05:37,400 ERROR .2second line
  . 18:05:37,400 ERROR . third line
  . 18:05:37,400 ERROR ] forth line

The second format is mainly used when messages are sent to logging applications like Chainsaw which can receive messages in XML format over a network or read regular files.

An example created by XmlFormatter could look like this:


  <log4j:event logger="category2" timestamp="1529829438"
                  sequenceNumber="3" level="INFO" thread="threadname">
    <log4j:message>second message</log4j:message>
    <log4j:throwable>throwablename</log4j:throwable>
    <log4j:NDC>my-ndc</log4j:NDC>
    <log4j:MDC>
      <log4j:data name="mdc1" value="val1" />
      <log4j:data name="mdc2" value="val2" />
    </log4j:MDC>
    <log4j:locationInfo file="filename" line="1234"
                           class="" method="methodname" />
    <log4j:properties>
      <log4j:data name="application" value="test" />
      <log4j:data name="hostname" value="hostname" />
      <log4j:data name="prop1" value="val1" />
    </log4j:properties>
  </log4j:event>

1.1.3. Appender

Appenders are used to forward the log messages to their destination. log4sendpp contains two of the most important classes:

  • A FileAppender appends the log messages at the end of a local regular file

  • A TcpIpAppender sends the messages over a TCP/IP network to a local or remote log host.

  • An ostream_Appender redirects messages to a standard std::stream. This class may be useful to bridge a gap to other frameworks like Boost.Test.

1.1.4. Logger

For covenient access all the logger helper classes are tied together by a Logger. You can either use a predefined default object with a TcpIpAppender and a FileAppender:


  Logger &logger = Logger::logger();
  logger.ndc().push("trivial context");
  logger.info("info message");
  logger.warn("warn message");

or you can create your own setting:


  Logger mylogger;
  mylogger.setPrimaryAppender(new TcpIpAppender(
                                new XmlFormatter("appname", "localhost"),
                                "localhost"));
  mylogger.setSecondaryAppender(FileAppender app (
                                  new SimpleFormatter("appname"),
                                  "testfile.txt"));

By default two appenders are used. The second is only used if the first fails for some reason. This way you can preferably log over a network and still store everything to a file when there is not network available or the remote log host is not running.

If you want to add location information to trace the file and line it is more convenient to use the logging macros instead of the plain method calls. These macros already contain this information and also request the current nickname from the thread dictionary:


    LOG4SENDPP_TRACE("trace by macro\nline2");
    LOG4SENDPP_DEBUG("debug by macro\nline2\nline3");

There are some more lower level macros which might become interesting if you wish to build your own macro for logging information.


    LOG4SENDPP_MACRO_TI("message throwinfo", "exception name", Logger::Info);
    LOG4SENDPP_MACRO_LI("message location", "category", Logger::Error);
    LOG4SENDPP_MACRO_LTI("message", "category", "exception name", Logger::Error);

An possible application is to create a macro which throws an exception. Before actually throwing the exception you could send the log message to be informed about every thrown exception and the according location.


#define THROW_ASSERTION(txt) \
  { \
    LOG4SENDPP_MACRO_TI(txt, L"Assertion", Logger::LogError); \
    throw Assertion(txt, __FILE__, __LINE__) ; \
  }

[Note]Error messages might be confusing

All these macros surround the according parameters with a do { ... } while(false) sequence. This is neccessary to be able to use the macros like normal function calls. Otherwise there might be situations in if - statements leading to forbidden code.

Unfortunately your compiler may issue confusing error messages regarding this do .. while statement if you place incorrect code into the macro.