Die Boost C++ Bibliotheken

Kapitel 62. Boost.Log

Boost.Log ist die Logging-Bibliothek in Boost. Sie unterstützt zahlreiche Backends, um Daten in verschiedenen Formaten zu loggen. Backends werden über Frontends angesprochen, die Dienste bündeln und Logeinträge auf unterschiedliche Weise an Backends weiterreichen. So existiert zum Beispiel ein Frontend, das Logeinträge asynchron in einem Thread weitergibt. Frontends können Filter besitzen, um bestimmte Logeinträge zu ignorieren. Und sie definieren, wie Logeinträge als String formatiert werden. All diese Funktionen lassen sich beliebig erweitern, was Boost.Log zu einer umfangreichen und mächtigen Bibliothek macht.

Beispiel 62.1. Backend, Frontend, Core und Logger
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::log;

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);

  core::get()->add_sink(sink);

  sources::logger lg;

  BOOST_LOG(lg) << "note";
  sink->flush();
}

Beispiel 62.1 stellt die wesentlichen Bestandteile von Boost.Log vor. Wenn Sie mit dieser Bibliothek arbeiten, kommen Sie mit Backends, Frontends, dem Core und Loggern in Berührung:

Beachten Sie, wie Backend, Frontend, Core und Logger im Detail zusammenspielen. Das Frontend boost::log::sinks::asynchronous_sink ist ein Template, das das Backend boost::log::sinks::text_ostream_backend als Template-Parameter erhält. Daraufhin wird eine Instanz des Frontends erstellt. Dazu wird auf boost::shared_ptr zugegriffen. Das ist notwendig, da Frontends nur über diesen Smartpointer im Core registriert werden können: Der Aufruf von boost::log::core::add_sink() verlangt einen boost::shared_ptr.

Da das Backend ein Template-Parameter des Frontends ist, kann es nur konfiguriert werden, nachdem die Instanz des Frontends erstellt wurde. Wie dies im Detail geschieht, hängt vom Backend ab. Das Backend boost::log::sinks::text_ostream_backend bietet die Methode add_stream() an, um Streams hinzuzufügen. So kann dieses Backend sogar mit mehr als einem Stream konfiguriert werden. Andere Backends bieten andere Methoden zur Konfiguration an. Hier hilft nur ein Blick in die Dokumentation.

Um Zugriff auf das Backend zu erhalten, bieten alle Frontends die Methode locked_backend() an. Die Methode heißt locked_backend(), weil sie einen Zeiger zurückgibt, der einen synchronisierten Zugriff auf das Backend bietet, solange der Zeiger existiert. Sie können über locked_backend() von verschiedenen Threads aus auf das Backend zugreifen, ohne sich um eine Synchronisierung kümmern zu müssen.

Einen Logger wie boost::log::sources::logger können Sie über den Standardkonstruktor erstellen. Er greift über boost::log::core::get() automatisch auf den Core zu, um Logeinträge weiterzureichen.

Sie können auf Logger auch ohne Makro zugreifen. Logger sind gewöhnliche Objekte mit Methoden, die Sie aufrufen können. Makros wie BOOST_LOG machen es jedoch einfacher, Logeinträge zu schreiben. Ohne Makros könnte ein Logeintrag nicht in einer Codezeile geschrieben werden.

Beachten Sie, dass Beispiel 62.1 am Ende der Funkion main() boost::log::sinks::asynchronous_sink::flush() aufruft. Da das Frontend asynchron ist und Logeinträge in einem Thread schreibt, ist dieser Aufruf zwingend notwendig. Er sorgt dafür, dass der Thread alle gepufferten Logeinträge ans Backlog weiterreicht und diese geschrieben werden. Ohne den Aufruf von flush() könnte das Beispielprogramm beendet werden, bevor note ausgegeben wird.

Beispiel 62.2. boost::sources::severity_logger mit einem Filter
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::log;

bool only_warnings(const attribute_value_set &set)
{
  return set["Severity"].extract<int>() > 0;
}

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);
  sink->set_filter(&only_warnings);

  core::get()->add_sink(sink);

  sources::severity_logger<int> lg;

  BOOST_LOG(lg) << "note";
  BOOST_LOG_SEV(lg, 0) << "another note";
  BOOST_LOG_SEV(lg, 1) << "warning";
  sink->flush();
}

Beispiel 62.2 baut auf dem vorherigen auf, ersetzt jedoch den Logger boost::sources::logger mit boost::sources::severity_logger. Dieser Logger fügt jedem Logeintrag ein Attribut hinzu, das das Loglevel beschreibt. Sie können das Makro BOOST_LOG_SEV verwenden, um das Loglevel explizit zu setzen.

Der Typ des Loglevels hängt vom Template-Parameter ab, den Sie an boost::sources::severity_logger übergeben. Im obigen Beispiel ist dies int. Daher werden an BOOST_LOG_SEV Zahlen wie 0 und 1 übergeben. Wird BOOST_LOG verwendet, ist das Loglevel automatisch auf 0 gesetzt.

Beispiel 62.2 ruft außerdem set_filter() auf, um einen Filter im Frontend zu registrieren. Es handelt sich hierbei um eine Funktion, die für jeden Logeintrag aufgerufen wird. Gibt die Funktion true zurück, wird der Logeintrag ans Backend weitergereicht. Andernfalls wird der Logeintrag ignoriert. Das Beispielprogramm definiert eine entsprechende Filterfunktion only_warnings() mit einem Rückgabewert vom Typ bool.

only_warnings() erwartet als Parameter ein boost::log::attribute_value_set. Dies ist der Typ, der Logeinträge repräsentiert, während sie im Logging-Framework herumgereicht werden. Boost.Log kennt mit boost::log::record noch einen anderen Typ für Logeinträge. boost::log::record speichert jedoch hauptsächlich ein boost::log::attribute_value_set und bietet eine entsprechende Methode attribute_values() an, um eine Referenz auf das boost::log::attribute_value_set zu erhalten. Filterfunktionen erhalten direkt ein boost::log::attribute_value_set und keinen boost::log::record.

boost::log::attribute_value_set heißt so, weil die Klasse Schlüssel/Wert-Paare speichert. Sie können sich die Klasse als std::unordered_map vorstellen.

Logeinträge bestehen aus Attributen. Attribute haben einen Namen und sind auf einen Wert gesetzt. Sie können Attribute selbst erstellen. Attribute können aber auch automatisch erstellt werden – zum Beispiel von Loggern. Dies ist der Grund, warum Boost.Log überhaupt unterschiedliche Logger anbietet. boost::log::sources::severity_logger fügt jedem Logeintrag ein Attribut namens Severity hinzu. Dieses Attribut speichert das Loglevel. So ist es möglich, im Filter das Loglevel daraufhin zu überprüfen, ob es größer als 0 ist.

boost::log::attribute_value_set bietet verschiedene Methoden an, um auf Attribute zuzugreifen. Die Methoden ähneln denen von std::unordered_map. So ist zum Beispiel der Operator operator[] überladen. Wird ihm ein Attributname übergeben, gibt er den Wert des Attributs zurück. Existiert das Attribut nicht, wird es automatisch erstellt.

Attributnamen haben den Typ boost::log::attribute_name. Diese Klasse bietet jedoch einen Konstruktor an, der einen String akzeptiert, so dass der String im Beispiel 62.2 direkt an operator[] übergeben werden kann.

Attributwerte haben den Typ boost::log::attribute_value. Diese Klasse stellt verschiedene Methoden zur Verfügung, um den Wert des Attributs in dem ihm eigenen Typ zu erhalten. Da das Loglevel ein int-Wert ist, wird beim Aufruf von extract() int als Template-Parameter übergeben.

Neben extract() bietet boost::log::attribute_value die Methoden extract_or_default() und extract_or_throw() an. extract() gibt einen mit einem Standardkonstruktor initialisierten Wert zurück, wenn die Typumwandlung nicht gelingt – bei int 0. extract_or_default() gibt im Fehlerfall einen Standardwert zurück, der als Parameter an die Methode übergeben wird. extract_or_throw() wirft im Fehlerfall eine Ausnahme vom Typ boost::log::runtime_error.

Boost.Log bietet mit boost::log::visit() auch eine Visitor-Funktion an. Sie können diese Funktion anstelle von extract() verwenden, wenn Sie typsicher auf den Attributwert zugreifen möchten.

Wenn Sie Beispiel 62.2 ausführen, wird warning ausgegeben. Diese Meldung ist die einzige, die einen Loglevel größer als 0 hat und daher vom Filter nicht ausgesiebt wird.

Beispiel 62.3. Mit set_formatter() die Formatierung eines Logeintrags ändern
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::log;

void severity_and_message(const record_view &view, formatting_ostream &os)
{
  os << view.attribute_values()["Severity"].extract<int>() << ": " <<
    view.attribute_values()["Message"].extract<std::string>();
}

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);
  sink->set_formatter(&severity_and_message);

  core::get()->add_sink(sink);

  sources::severity_logger<int> lg;

  BOOST_LOG_SEV(lg, 0) << "note";
  BOOST_LOG_SEV(lg, 1) << "warning";
  sink->flush();
}

Beispiel 62.3 basiert auf dem vorherigen. Im Gegensatz zu diesem wird auch das Loglevel ausgegeben.

Frontends bieten eine Methode set_formatter() an, der eine Formatierungsfunktion übergeben werden kann. Wird ein Logeintrag von einem Frontend nicht gefiltert, wird er an die Formatierungsfunktion weitergereicht. Diese wandelt den Logeintrag in einen String um, der vom Frontend ans Backend weitergegeben wird. Rufen Sie set_formatter() nicht auf und definieren Sie keine Formatierungsfunktion, wird standardmäßig nur das ans Backend weitergereicht, was rechts von einem Makro wie BOOST_LOG steht.

Die Formatierungsfunktion, die im Beispiel 62.3 an set_formatter() übergeben wird, heißt severity_and_message(). Sie erwartet zwei Parameter vom Typ boost::log::record_view und boost::log::formatting_ostream. boost::log::record_view ist eine View auf einen Logeintrag. Eine View ist vergleichbar mit der bereits erwähnten Klasse boost::log::record. Der entscheidende Unterschied ist, dass eine View keine Änderungen am Logeintrag zulässt und daher konstant ist.

boost::log::record_view bietet eine Methode attribute_values() an, über die eine konstante Referenz auf das boost::log::attribute_value_set erhalten werden kann. boost::log::formatting_ostream ist der Stream, über den der String gebildet wird, der ans Backend weitergereicht werden soll.

Innerhalb von severity_and_message() wird auf die beiden Attribute Severity und Message zugegriffen. Die entsprechenden Werte werden mit extract() extrahiert und in den Stream geschrieben. Severity gibt wie bekannt das Loglevel als int zurück. Message ermöglicht einen Zugriff auf all das, was rechts von einem Makro wie BOOST_LOG steht. Dass die beiden Attribute Severity und Message heißen, muss der Dokumentation entnommen werden.

Beispiel 62.3 verwendet im Gegensatz zum vorherigen keinen Filter. Wenn Sie das Beispielprogramm ausführen, werden zwei Logeinträge geschrieben: 0: note und 1: warning.

Beispiel 62.4. Logeinträge filtern und formatieren mit Lambda-Funktionen
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::log;

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);
  sink->set_filter(expressions::attr<int>("Severity") > 0);
  sink->set_formatter(expressions::stream <<
    expressions::attr<int>("Severity") << ": " << expressions::smessage);

  core::get()->add_sink(sink);

  sources::severity_logger<int> lg;

  BOOST_LOG_SEV(lg, 0) << "note";
  BOOST_LOG_SEV(lg, 1) << "warning";
  BOOST_LOG_SEV(lg, 2) << "error";
  sink->flush();
}

Beispiel 62.4 verwendet sowohl eine Filter- als auch eine Formatierungsfunktion. Die Funktionen sind diesmal als Lambda-Funktionen implementiert – nicht als C++11-Lambda-Funktionen, sondern als Lambda-Funktionen basierend auf Boost.Phoenix.

Boost.Log bietet im Namensraum boost::log::expressions Hilfsmittel für Lambda-Funktionen an. So können Sie über boost::log::expressions::stream auf den Stream und über boost::log::expressions::smessage auf das, was rechts von einem Makro wie BOOST_LOG steht, zugreifen. Sie können auch boost::log::expressions::attr() verwenden, um auf ein beliebiges Attribut zuzugreifen. So hätte im Beispiel anstelle von smessage auch attr<std::string>("Message") verwendet werden können.

Wenn Sie Beispiel 62.4 ausführen, wird 1: warning und 2: error ausgegeben.

Beispiel 62.5. Schlüsselwörter für Attribute definieren
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::log;

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);
  sink->set_filter(severity > 0);
  sink->set_formatter(expressions::stream << severity << ": " <<
    expressions::smessage);

  core::get()->add_sink(sink);

  sources::severity_logger<int> lg;

  BOOST_LOG_SEV(lg, 0) << "note";
  BOOST_LOG_SEV(lg, 1) << "warning";
  BOOST_LOG_SEV(lg, 2) << "error";
  sink->flush();
}

Boost.Log ermöglicht es, eigene Schlüsselwörter zu definieren. So können Sie über das Makro BOOST_LOG_ATTRIBUTE_KEYWORD Schlüsselwörter erstellen, über die auf Attribute zugegriffen werden kann, ohne Attributnamen wiederholt als String an boost::log::expressions::attr() übergeben zu müssen.

Im Beispiel 62.5 wird auf das Makro BOOST_LOG_ATTRIBUTE_KEYWORD zugegriffen, um ein Schlüsselwort severity zu definieren. Das Makro erwartet drei Parameter: Zuerst den Namen des Schlüsselworts, dann den Namen des Attributs als String und abschließend den Typ des Attributs. In den Lambda-Funktionen, die im Beispiel für den Filter und die Formatierung eingesetzt werden, kann auf das neue Schlüsselwort zugegriffen werden. So ist es nicht nur möglich, auf von Boost.Log zur Verfügung gestellte Schlüsselwörter wie boost::log::expressions::smessage zuzugreifen – es können auch eigene definiert werden.

In allen bisherigen Beispielen waren Attribute automatisch vorhanden. Es konnte in Filter- oder Formatierungsfunktionen auf sie zugegriffen werden, weil Boost.Log sie automisch zur Verfügung stellte. Im Beispiel 62.6 sehen Sie, wie Sie eigene Attribute erstellen.

Beispiel 62.6. Eigene Attribute definieren
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::log;

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(counter, "LineCounter", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "Timestamp",
  boost::posix_time::ptime)

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);
  sink->set_filter(severity > 0);
  sink->set_formatter(expressions::stream << counter << " - " << severity <<
    ": " << expressions::smessage << " (" << timestamp << ")");

  core::get()->add_sink(sink);
  core::get()->add_global_attribute("LineCounter",
    attributes::counter<int>{});

  sources::severity_logger<int> lg;

  BOOST_LOG_SEV(lg, 0) << "note";
  BOOST_LOG_SEV(lg, 1) << "warning";
  {
    BOOST_LOG_SCOPED_LOGGER_ATTR(lg, "Timestamp", attributes::local_clock{})
    BOOST_LOG_SEV(lg, 2) << "error";
  }
  BOOST_LOG_SEV(lg, 2) << "another error";
  sink->flush();
}

Sie erstellen globale Attribute, indem Sie die Methode add_global_attribute() für den Core aufrufen. Ein globales Attribut heißt so, weil es zu jedem Logeintrag automatisch hinzugefügt wird.

add_global_attribute() erwartet zwei Parameter: Sie müssen den Namen und den Typ des neuen Attributs angeben. Der Name wird als String übergeben. Für den Typ müssen Sie auf eine Klasse aus dem Namensraum boost::log::attributes zugreifen. So stellt Boost.Log zahlreiche Klassen zur Verfügung, die Attribute auf unterschiedliche Werte setzen können. Im Beispiel 62.6 wird die Klasse boost::log::attributes::counter verwendet, um ein Attribut namens LineCounter für jeden Logeintrag auf eine eindeutige Zahl zu setzen. Dieses neue Attribut nummeriert alle Logeinträge beginnend bei 1 durch.

Beachten Sie, dass add_global_attribute() keine Template-Methode ist. boost::log::attributes::counter wird nicht als Template-Parameter angegeben. Attributtypen müssen instanziiert und als Objekt übergeben werden.

Beispiel 62.6 verwendet ein zweites selbst erstelltes Attribut namens Timestamp. Dieses wird jedoch nicht als globales Attribut erstellt. Es handelt sich hierbei um ein Attribut mit einem begrenzten Gültigkeitsbereich.

Für das Attribut Timestamp kommt das Makro BOOST_LOG_SCOPED_LOGGER_ATTR zum Einsatz. Mit diesem Makro wird einem Logger ein Attribut hinzugefügt. Der Logger muss als erster Parameter ans Makro übergeben werden. Der zweite Parameter ist der Name des Attributs, der dritte Parameter der Typ. Als Typ ist für Timestamp boost::log::attribute::local_clock angegeben. Diese Klasse sorgt dafür, dass das Attribut Timestamp für jeden Logeintrag auf die aktuelle lokale Uhrzeit gesetzt wird.

Beachten Sie, dass das Attribut Timestamp ausschließlich dem Logeintrag error hinzugefügt wird. Am Ende des Gültigkeitbereichs, in dem BOOST_LOG_SCOPED_LOGGER_ATTR verwendet wird, wird das Attribut automatisch vom Logger entfernt. BOOST_LOG_SCOPED_LOGGER_ATTR entspricht einem Aufruf von add_attribute() und remove_attribute().

Wie im vorherigen Beispiel wird auch im Beispiel 62.6 auf das Makro BOOST_LOG_ATTRIBUTE_KEYWORD zugegriffen, um Schlüsselwörter für die neu erstellten Attribute zu definieren. In der Formatierungsfunktion wird auf die Schlüsselwörter zugegriffen, um Zeilennummer und Zeitpunkt auszugeben. Für Logeinträge, für die kein Attribut Timestamp definiert ist, ist timestamp leer und entspricht einem leeren String.

Beispiel 62.7. Hilfsfunktionen für Filter und Formatierungen
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <iomanip>

using namespace boost::log;

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(counter, "LineCounter", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "Timestamp",
  boost::posix_time::ptime)

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);
  sink->set_filter(expressions::is_in_range(severity, 1, 3));
  sink->set_formatter(expressions::stream << std::setw(5) << counter <<
    " - " << severity << ": " << expressions::smessage << " (" <<
    expressions::format_date_time(timestamp, "%H:%M:%S") << ")");

  core::get()->add_sink(sink);
  core::get()->add_global_attribute("LineCounter",
    attributes::counter<int>{});

  sources::severity_logger<int> lg;

  BOOST_LOG_SEV(lg, 0) << "note";
  BOOST_LOG_SEV(lg, 1) << "warning";
  {
    BOOST_LOG_SCOPED_LOGGER_ATTR(lg, "Timestamp", attributes::local_clock{})
    BOOST_LOG_SEV(lg, 2) << "error";
  }
  BOOST_LOG_SEV(lg, 2) << "another error";
  sink->flush();
}

Boost.Log bietet zahlreiche Hilfsfunktionen für Filter und Formatierungen an. So wird im Beispiel 62.7 die Funktion boost::log::expressions::is_in_range() verwendet, um Logeinträge zu filtern, deren Loglevel nicht in eine bestimmte Bandbreite fällt. boost::log::expressions::is_in_range() erwartet das Attribut als ersten und die Unter- und Obergrenze als zweiten und dritten Parameter. Die Obergrenze ist der Wert, der gerade nicht mehr zur Bandbreite zählt.

In der Formatierungsfunktion kommt die Funktion boost::log::expressions::format_date_time() zum Einsatz. Sie kann verwendet werden, um einen Zeitpunkt zu formatieren. So wird diese Funktion im Beispiel 62.7 eingesetzt, um lediglich die Uhrzeit auszugegeben.

Beachten Sie, dass Sie in einer Formatierungsfunktion auch Stream-Manipulatoren aus der Standardbibliothek verwenden können. So kommt im Beispiel 62.7 std::setw() zum Einsatz, um eine Feldgröße für den LineCounter zu setzen.

Beispiel 62.8. Mehrere Logger, Frontends und Backends
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/channel_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/utility/string_literal.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <string>

using namespace boost::log;

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string)

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend>
    ostream_sink;
  boost::shared_ptr<ostream_sink> ostream =
    boost::make_shared<ostream_sink>();
  boost::shared_ptr<std::ostream> clog{&std::clog,
    boost::empty_deleter{}};
  ostream->locked_backend()->add_stream(clog);
  core::get()->add_sink(ostream);

  typedef sinks::synchronous_sink<sinks::text_multifile_backend>
    multifile_sink;
  boost::shared_ptr<multifile_sink> multifile =
    boost::make_shared<multifile_sink>();
  multifile->locked_backend()->set_file_name_composer(
    sinks::file::as_file_name_composer(expressions::stream <<
    channel.or_default<std::string>("None") << "-" <<
    severity.or_default(0) << ".log"));
  core::get()->add_sink(multifile);

  sources::severity_logger<int> severity_lg;
  sources::channel_logger<> channel_lg{keywords::channel = "Main"};

  BOOST_LOG_SEV(severity_lg, 1) << "severity message";
  BOOST_LOG(channel_lg) << "channel message";
  ostream->flush();
}

Im Beispiel 62.8 werden mehrere Logger, Frontends und Backends gleichzeitig verwendet. So kommen neben den bekannten Klassen boost::log::sinks::asynchronous_sink, boost::log::sinks::text_ostream_backend und boost::log::sources::severity_logger mit boost::log::sinks::synchronous_sink, boost::log::sinks::text_multifile_backend und boost::log::sources::channel_logger ein neues Frontend, ein neues Backend und ein neuer Logger zum Einsatz.

Das Frontend boost::log::sinks::synchronous_sink bietet einen synchronisierten Zugriff auf ein Backend an. Somit ist es möglich, ein Backend in einer Multithreaded-Anwendung zu verwenden, selbst wenn das Backend nicht explizit für den Einsatz in einer Multithreaded-Anwendung entwickelt ist.

Der Unterschied zu boost::log::sinks::asynchronous_sink ist, dass boost::log::sinks::synchronous_sink nicht auf einem Thread basiert. Logeinträge werden im gleichen Thread ans Backend weitergereicht.

Im Beispiel 62.8 wird boost::log::sinks::synchronous_sink mit dem Backend boost::log::sinks::text_multifile_backend verwendet. Dieses Backend schreibt Logeinträge in eine oder mehrere Dateien. Dabei werden die Dateinamen nach einer Regel gebildet, die über set_file_name_composer() ans Backend übergeben wird. Wenn Sie wie im Beispiel die freistehende Funktion boost::log::sinks::file::as_file_name_composer() verwenden, können Sie die Regel mit den gleichen Bausteinen als Lambda-Funktion bilden, wie sie bei Formatierungsfunktionen zum Einsatz kommen. In diesem Fall wird aus Attributen kein String erstellt, der als Logeintrag geschrieben wird. Der String, der hier entsteht, ist der Dateiname, unter dem Logeinträge abgespeichert werden.

Im Beispiel 62.8 wird auf die beiden Schlüssewörter channel und severity zugegriffen. Diese sind mit Hilfe des Makros BOOST_LOG_ATTRIBUTE_KEYWORD definiert. Sie verweisen auf die gleichnamigen Attribute Channel und Severity. Der Zugriff erfolgt auf beide Schlüsselwörter über den Aufruf von or_default(). Diese Methode macht es möglich, einen Standardwert anzugeben, sollte ein Attribut nicht gesetzt sein. Wird ein Logeintrag geschrieben, ohne dass Channel und Severity gesetzt sind, wird er in einer Datei None-0.log gespeichert. Wird ein Logeintrag mit dem Loglevel 1 geschrieben, wird er in der Datei None-1.log gespeichert. Ein Logeintrag mit Loglevel 1 und Channel Main wird in der Datei Main-1.log gespeichert.

Das Channel-Attribut stammt vom Logger boost::log::sources::channel_logger. Dem Konstruktor dieses Channels muss ein Channel-Name übergeben werden. Der Name kann nicht direkt als String übergeben werden. Boost.Log erwartet, dass der Parameter über einen Namen eindeutig identifiziert wird. So ist es notwendig, keywords::channel = "Main" an den Konstruktor zu übergeben, auch wenn boost::log::sources::channel_logger keine anderen Parameter akzeptiert.

Beachten Sie, dass boost::log::keywords::channel nichts mit den Schlüsselwörtern zu tun hat, wie sie für Attribute verwendet werden.

Sinn und Zweck des Loggers boost::log::sources::channel_logger ist es, Logeinträge von verschiedenen Komponenten in einer Software klar zu trennen. So können Komponenten eigene Instanzen von boost::log::sources::channel_logger verwenden, die jeweils eindeutige Namen erhalten. Greifen Komponenten ausschließlich auf ihren Logger zu, lässt sich nachvollziehen, welche Logeinträge von welchen Komponenten in einer Anwendung stammen.

Beispiel 62.9. Ausnahmen im Logging-Framework zentral verarbeiten
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/utility/exception_handler.hpp>
#include <boost/log/exceptions.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <exception>

using namespace boost::log;

struct handler
{
  void operator()(const runtime_error &ex) const
  {
    std::cerr << "boost::log::runtime_error: " << ex.what() << '\n';
  }

  void operator()(const std::exception &ex) const
  {
    std::cerr << "std::exception: " << ex.what() << '\n';
  }
};

int main()
{
  typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);

  core::get()->add_sink(sink);
  core::get()->set_exception_handler(
    make_exception_handler<runtime_error, std::exception>(handler{}));

  sources::logger lg;

  BOOST_LOG(lg) << "note";
}

Boost.Log bietet die Möglichkeit, Ausnahmen, die im Logging-Framework auftreten, zentral zu verarbeiten. So ist es nicht notwendig, jedes BOOST_LOG in einen try-Block zu setzen, um mit einem catch auf mögliche Ausnahmen zu reagieren.

Im Beispiel 62.9 wird die Methode set_exception_handler() aufgerufen. Der Core bietet diese Methode an, um einen Handler zu registrieren, an den Ausnahmen, die im Logging-Framework auftreten, weitergeleitet werden. Der Handler wird als Funktionsobjekt implementiert. Er muss einen oder mehrere überladene Operatoren operator() definieren, die Parameter mit entsprechenden Ausnahmetypen erwarten. Eine Instanz des Funktionsobjekts wird dann an set_exception_handler() übergeben – jedoch nicht direkt, sondern mit Hilfe der Template-Funktion boost::log::make_exception_handler(). Dieser Funktion müssen alle Ausnahmetypen, die der Handler verarbeiten soll, als Template-Parameter übergeben werden.

Boost.Log bietet mit boost::log::make_exception_suppressor() eine Funktion an, mit der alle Ausnahmen im Logging-Framework ignoriert werden können. Rufen Sie diese Funktion anstelle von boost::log::make_exception_handler() auf.

Beispiel 62.10. Makro zur Definition eines globalen Loggers
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <exception>

using namespace boost::log;

BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(lg, sources::wlogger_mt)

int main()
{
  typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);

  core::get()->add_sink(sink);

  BOOST_LOG(lg::get()) << L"note";
}

In allen bisherigen Beispielen wurden Logger lokal definiert. Möchten Sie einen Logger global definieren, greifen Sie wie im Beispiel 62.10 auf das Makro BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT zu. Übergeben Sie dem Makro als ersten Parameter den Namen und als zweiten Parameter den Typ des Loggers. Sie greifen auf den Logger nicht über den Namen, sondern über get() zu. Sie erhalten ähnlich wie beim Core einen Zeiger auf ein Singleton.

Boost.Log stellt neben BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT weitere Makros wie BOOST_LOG_INLINE_GLOBAL_LOGGER_CTOR_ARGS zur Verfügung, die weitergehende Initialisierungsmöglichkeiten anbieten. So können mit BOOST_LOG_INLINE_GLOBAL_LOGGER_CTOR_ARGS Parameter an den Konstruktor eines globalen Loggers übergeben werden. Allen Makros ist gemein, dass sie eine korrekte Initialisierung globaler Logger garantieren.

Boost.Log bietet weitere Funktionen an, für die sich ein Blick in die Dokumentation lohnt. So ist es zum Beispiel möglich, das Logging-Framework über einen Container zu konfigurieren, der Schlüssel/Wert-Paare als Strings speichert. In diesem Fall werden keine Objekte instanziiert und Methoden aufgerufen. Stattdessen kann zum Beispiel ein Schlüssel Destination auf Console gesetzt werden, was automatisch dazu führt, dass das Backend boost::log::sinks::text_ostream_backend verwendet wird. Über weitere Schlüssel kann das Backend konfiguriert werden. Da der Container auch als INI-Datei serialisiert werden kann, kann die Konfiguration in einer Textdatei erstellt und von dieser geladen werden.