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.
#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:
Backends bestimmen, wohin Daten geschrieben werden. Im obigen Beispiel wird das Backend boost::log::sinks::text_ostream_backend
verwendet. Dieses Backend muss mit einem Stream vom Typ std::ostream
initialisiert werden und leitet Logeinträge an diesen weiter.
Frontends stellen das Verbindungsstück zwischen dem Core und einem Backend dar. Sie übernehmen verschiedene Aufgaben, die deswegen nicht in jedem Backend implementiert werden müssen. So können zum Beispiel Filter zu einem Frontend hinzugefügt werden. Das Frontend kann dann bestimmte Logeinträge ignorieren und leitet sie nicht ans Backend weiter.
Im obigen Beispiel wird das Frontend boost::log::sinks::asynchronous_sink
verwendet. Ein Frontend muss zwingend verwendet werden, auch wenn wie hier keine Filter zum Einsatz kommen. boost::log::sinks::asynchronous_sink
basiert auf einem Thread, um Logeinträge asynchron an ein Backend weiterzureichen. Dadurch wird unter Umständen eine höhere Performance erreicht. Schreibvorgänge finden jedoch verzögert statt.
Der Core ist der zentrale Baustein in Boost.Log, durch den alle Logeinträge geroutet werden. Er ist als Singleton implementiert. Über boost::log::core::get()
kann jederzeit ein Zeiger auf den Core erhalten werden.
Es ist notwendig, Frontends dem Core hinzuzufügen, damit Logeinträge an sie weitergereicht werden. Ob Logeinträge überhaupt weitergereicht werden, hängt vom Filter im Core ab. Filter können also nicht nur in Frontends, sondern auch im Core registriert werden. Während der Filter im Core global ist, sind Filter in Frontends lokal. Wird also ein Logeintrag vom Core gefiltert, wird er an keinen Frontend weitergereicht. Wird ein Logeintrag in einem Frontend gefiltert, kann er durchaus von anderen Frontends verarbeitet und an deren Backends weitergereicht werden.
Der Logger ist der Bestandteil von Boost.Log, mit dem Sie die meiste Zeit in Berührung kommen. Während Sie Backend, Frontend und Core nur verwenden, um das Logging-Framework zu initialisieren, greifen Sie immer dann auf einen Logger zu, wenn Sie einen Logeintrag vornehmen möchten. Der Logger leitet den Logeintrag an den Core weiter.
Im obigen Beispielprogramm hat der Logger den Typ boost::log::sources::logger
. Es handelt sich hierbei um den einfachsten Logger. Wenn Sie einen Logeintrag vornehmen möchten, greifen Sie auf das Makro BOOST_LOG
zu und übergeben den Logger als Parameter. Den Logeintrag können Sie so vornehmen, wie wenn Sie Daten auf einen Stream vom Typ std::ostream
ausgeben.
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.
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.
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
.
#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.
#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.
#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.
#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.
#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.
#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.
#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.