Die Klasse Logger ist ein Tool, mit dem Applikationsinformationen einfach und zentral aufgenommen werden können. Er unterstützt die Ausgabe in mehrere Log-Dateien, die Definition von Prioritäten (log severity) pro Eintrag und die Konfiguration von Schwellwerten. Letztere dienen vor allem dazu, im Produktionsbetrieb nicht benötigte Debug- oder Trace-Ausgabe auszublenden.
Um die Performance einer Anwendung nicht durch häufige, verteilte und konkurrierende File-Zugriffe zu beeinträchtigen, muss der Logger als Singleton-Instanz erzeugt und verwendet. Die Logfile-Einträge werden dann am Ende eines Requests gesammelt in das entsprechende Logfile geschrieben.
Ab Version 1.17 sieht die Logger-Implementierung des APF eine Trennung zwischen dem Logger selbst, der Beschreibung eines Log-Eintrags (LogEntry) und der Persistenz (LogWriter) vor:
Der Logger stellt dabei die zentrale Anlaufstelle für das Framework und darauf aufsetzenden Applikationen dar, Log-Einträge zu schreiben. Das Aussehen eines Log-Eintrags wird durch das Interface LogEntry und die Referenz-Implementierung SimpleLogEntry beschrieben. Wohin ein Eintrag letztlich geschrieben wird, regelt die Implementierung des LogWriter-Interfaces. Diese nutzt der Logger um am Ende der Request-Verarbeitung die Inhalte des Puffers zu persistieren.
Zur Registrierung von LogWriter-Implementierungen stellt der Logger die Methoden
zur Verfügung. Dabei wird jeder LogWriter mit einem eindeutigen Bezeichner ($target) registriert. Der Logger nutzt das Ziel um die eingehenden Log-Einträge der jeweils gewünschten Persistenz-Schicht zuzuordnen. Für dieses Mapping wird dem LogEntry ebenso ein Ziel zugeordnet.
Das Interface eines LogEntry gestaltet sich wie folgt:
interface LogEntry {
const SEVERITY_TRACE = 'TRACE';
const SEVERITY_DEBUG = 'DEBUG';
const SEVERITY_INFO = 'INFO';
const SEVERITY_WARNING = 'WARN';
const SEVERITY_ERROR = 'ERROR';
const SEVERITY_FATAL = 'FATAL';
public function __toString();
public function getLogTarget();
public function getSeverity();
}Die Methoden getSeverity() nutzt der Logger um zu entscheiden, ob ein Eintrag über dem definierten Schwellwert liegt oder verworfen werden soll. Mit dem Rückgabewert der Funktion getLogTarget() weist der Logger den Eintrag dem entsprechenden LogWriter zu.
Die Implementierung eines LogWriters muss folgendes Interface erfüllen:
interface LogWriter {
public function writeLogEntries(array $entries);
public function setTarget($target);
}Mit Hilfe der Methode setTarget() injiziert der Logger den Ziel-Bezeichner. Dieser kann dann von der Implementierung beispielsweise für die Generierung eines Log-Datei-Namens genutzt werden. Die Methode writeLogEntries() ist für die Persistenz der übergebenen Liste von Einträgen zuständig.
Im Vergleich zur Implementierung des Loggers bis einschließlich Version 1.16 kann an Version 1.17 der volle Funktionsumfang des AdvancedLogger bei gleichzeitig flexiblerer Konfiguration genutzt werden. Neben der Konfiguration von unterschiedlichen Persistenz-Möglichkeiten können Sie nun auch das Format von Log-Einträgen über eigene LogEntry-Implementierungen definieren.
Der Logger selbst ist wie in Kapitel 2 beschrieben ein Router für Log-Einträge, der die Persistenz an registrierte LogWriter delegiert. Damit ein Eintrag auch den Weg in eine Log-Datei oder eine Datenbank findet, muss ein persistiert werden kann braucht es einen LogWriter.
Zur Verwaltung von LogWritern bietet der Logger die in den folgenden Kapitel beschriebenen Möglichkeiten.
Bitte beachten Sie, dass im Auslieferungszustand des APF ausschließlich der FileLogWriter registriert ist. Damit verhält sich das Framework wie in Versionen vor 1.17.
Bitte beachten Sie weiterhin, dass die Konfiguration des Standard-Log-Ziels ab Release 1.17 in der Registry im Namespace apf::core und dem Schlüssel InternalLogTarget konfiguriert ist. Bitte nutzen Sie daher ab Version 1.17 folgenden Code um Log-Einträge mit dem Standard-Ziel zu schreiben (beide Varianten möglich):
$logger->logEntry(
Registry::retrieve('apf::core', 'InternalLogTarget'),
'My log message',
LogEntry::SEVERITY_INFO
);
$logger->addEntry(new SimpleLogEntry(
Registry::retrieve('apf::core', 'InternalLogTarget'),
'My log message',
LogEntry::SEVERITY_INFO
));Fatal error: Uncaught exception 'LoggerException' with message 'Log writer with name "mysqlx" is not registered!' in */core/logging/Logger.php on line 433Im Auslieferungszustand des APF wird standardmäßig der FileLogWriter mit dem Bezeichner, der im Registry-Schlüssel InternalLogTarget (Namespace: apf::core) definiert ist (Standard: apf), registriert.
Beabsichtigen Sie eigene LogWriter hinzuzufügen, so können Sie die Methode addLogWriter() nutzen. Dies gilt sowohl für mitgelieferte als auch eigene Implementierungen:
$logger = & Singleton::getInstance('Logger');
import('core::logging::writer', 'StdoutLogWriter');
$logger->addLogWriter(
'stdout',
new StdoutLogWriter()
);Anschließend kann der mit stdout registrierte LogWriter wie folgt genutzt werden:
$logger = & Singleton::getInstance('Logger');
$logger->addEntry(new SimpleLogEntry(
'stdout',
'This is a log message',
LogEntry::SEVERITY_INFO
));Möchten Sie einen bereits registrierten LogWriter konfigurieren, so können Sie dazu die Methode getLogWriter() nutzen. Welche Persistenz-Schichten aktuell registriert sind, können Sie per getRegisteredTargets() abfragen.
Möchten Sie die Konfiguration des Standard-LogWriters verändern, so nutzen Sie bitte folgenden Code-Block als Vorlage:
$standardWriter = $logger->getLogWriter(
Registry::retrieve('apf::core', 'InternalLogTarget')
);
$standardWriter->setHostPrefix($_SERVER['HTTP_HOST']);Details zur Konfiguration von LogWritern entnehmen Sie bitte Kapitel 4.
Mit Hilfe der Methode getLogWriter() ist es möglich, bestehende bzw. bereits registrierte LogWriter als Basis für eigene Ziele zu nutzen. Nutzen Sie bitte folgenden Code-Block um den im Auslieferungszustand registrierten LogWriter unter einem anderen Ziel zu registrieren:
$writer = clone $logger->getLogWriter(
Registry::retrieve('apf::core', 'InternalLogTarget')
);
$logger->addLogWriter('new-target', $writer);Wie in den vorangegangenen Kapitel beschrieben können Sie den geklonten LogWriter natürlich wie gewünscht konfigurieren.
Um vorhandene LogWriter zu entfernen nutzen Sie bitte die Methode removeLogWriter():
$logger->removeLogWriter('mysqlx');Bitte beachten Sie, dass bereits vorhandene Log-Einträge dann nicht mehr persistiert werden können.
In Entwicklungsumgebungen kann es notwendig sein, alle vorhandenen Log-Einträge direkt mit dem Quelltext der Applikation auszugeben. Um alle vorhandenen LogWriter auf die Standard-Ausgabe des Webservers umzulenken, nutzen Sie bitte folgenden Quellcode:
foreach($logger->getRegisteredTargets() as $logTarget) {
$logger->addLogWriter($logTarget, new StdOutLogWriter());
}Zur Konfiguration des Log-Verzeichnisses nutzen Sie bitte den Registry-Wert LogDir aus dem Namespace apf::core. Dieser wird zunächst mit dem absoluten Pfad zum aktuellen Verzeichnis plus Unterordner logs initialisiert. Soll ein anderes Log-Verzeichnis verwendet werden, so kann der Pfad in der Bootstrap-Datei vor dem Erstellen des Front-Controller per
Registry::register('apf::core', 'LogDir', '/Pfad/zu/meinem/Log/Verzeichnis');angepasst werden. Der angegebene Pfad dient der Ablage aller Log-Dateien einer Applikation.
Wie bereits in der Einleitung angesprochen, sammelt der Logger zunächst alle Einträge und persistiert diese erst am Ende des Requests. Bei hohem Log-Aufkommen kann es daher zu hohem Speicherverbrauch der Applikation kommen, die mit den System-Einstellungen von PHP kollidiert (z.B. memory_limit).
Um den Speicherverbrauch zu reduzieren bzw. zu restringieren kann die maximale Puffer-Länge definiert werden. Der Standardwert ist 300, kann jedoch jederzeit durch
$log = &Singleton::getInstance('Logger');
$log->setMaxBufferLength(500);adaptiert werden.
Mit dem Release 1.15 ist es möglich, Schwellwerte bzw. Profile für Log-Einträge zu definieren. Liegt ein Eintrag mit einer definierten severity oberhalb des definierten Schwellwertes bzw. innerhalb des gewählten Profils, so wird der Eintrag in die Log-Datei geschrieben. Falls dies nicht zutrifft, wird er verworfen.
Um den Schwellwert zu definieren, können Sie folgenden Code nutzen:
$log = &Singleton::getInstance('Logger');
$log->setLogThreshold(Logger::$LOGGER_THRESHOLD_WARN);Wie dem Beispiel zu entnehmen ist, definiert der Logger bereits eine Auswahl an Standard-Schwellwerten bzw. -Profilen. Darüber hinaus können Sie beliebige eigene Profile erstellen. Hierzu nutzen Sie bitte die severity-Definitionen des Interfaces LogEntry. Diese sind:
Der folgende Code-Block zeigt ein Beispiel, in dem nur INFO- und TRACE-Einträge in die Log-Datei geschrieben werden:
$log = &Singleton::getInstance('Logger');
$log->setLogThreshold(array(
LogEntry::SEVERITY_TRACE,
LogEntry::SEVERITY_INFO
));Der Logger definiert folgende Standard-Schwellwerte:
Details zur Definition entnehmen Sie bitte der API-Dokumentation.
In geclusterten Umgebungen werden Log-Dateien von verschiedenen Servern oft auf ein gemeinsames Datei-System geschrieben. Vorteil dieser Vorgehensweise ist, dass Log-Dateien zentral ausgewertet und archiviert werden können. Der Nachteil besteht darin, dass die Server des Clusters beim Schreiben der Dateien um Zugriff konkurrieren und sich so die Performance der Anwendung durch langes Warten oder erhöhten Log-Traffic (erfahrungsgemäß) verringert.
Eine oft eingesetzte Lösung ist, in geclusterten Umgebungen die einzelnen Log-Dateien mit einem Präfix zu verstehen, das den Server-Namen enthält. So sind die Dateinamen nicht nur eindeutig, sondern lassen auch die Analyse einer Anwendung auf einem dedizierten Server zu.
Mit dem in Release 1.15 eingeführten host prefix haben Sie die Möglichkeit, einen Server-abhängigen Präfix für eine Log-Datei zu definieren:
$log = &Singleton::getInstance('Logger');
$log->setHostPrefix('node1');$log = &Singleton::getInstance('Logger');
$log->setHostPrefix($_SERVER['SERVER_ADDR']);Mit der Version 1.17 des APF werden die in den folgenden Kapiteln beschriebenen LogWriter-Implementierungen mitgeliefert.
Der FileLogWriter schreibt die übergebenen Log-Einträge in eine Datei. Der Name der Datei setzt sich dabei aus dem aktuellen Datum, einem optionalen Host-Präfix und dem Log-Ziel für das der LogWriter konfiguriert wurde zusammen (Beispiel: 2013_01_20__127_0_0_1__apf.log).
Die folgenden Kapitel beschreiben die Konfiguration des FileLogWriters.
Zur Konfiguration des Log-Verzeichnisses nutzen Sie bitte den Konstruktor (verpflichtend) oder die Methode setLogDir() (optional). Die Registrierung des FileLogWriters kann wie folgt vorgenommen werden:
$logger->addLogWriter('file', new FileLogWriter('/path/to/log/dir'));Die Konfiguration von bestehenden Instanzen lässt sich wie folgt erledigen:
$fileWriter = $logger->getLogWriter('file');
$logger->setLogDir('/path/to/log/dir');Wurde das im Konstruktor oder der Methode setLogDir() angegebene Verzeichnis noch nicht erstellt, so legt der FileLogWriter dieses automatisch an. Um die verwendete Datei-Maske definieren zu können, stellt der FileLogWriter die Methode setLogFolderPermissions() bereit. Nutzen Sie diese wie folgt um die umask festzulegen:
$fileWriter = $logger->getLogWriter('file');
$fileWriter->setLogFolderPermissions(0755);In geclusterten Umgebungen werden Log-Dateien von verschiedenen Servern oft auf ein gemeinsames Datei-System geschrieben. Vorteil dieser Vorgehensweise ist, dass Log-Dateien zentral ausgewertet und archiviert werden können. Der Nachteil besteht darin, dass die Server des Clusters beim Schreiben der Dateien um Zugriff konkurrieren und sich so die Performance der Anwendung durch langes Warten oder erhöhten Log-Traffic (erfahrungsgemäß) verringert.
Eine oft eingesetzte Lösung ist, in geclusterten Umgebungen die einzelnen Log-Dateien mit einem Präfix zu verstehen, das den Server-Namen enthält. So sind die Dateinamen nicht nur eindeutig, sondern lassen auch die Analyse einer Anwendung auf einem dedizierten Server zu.
Mit dem in Release 1.15 eingeführten host prefix haben Sie die Möglichkeit, einen Server-abhängigen Präfix für eine Log-Datei zu definieren:
$fileWriter = $logger->getLogWriter('file');
$fileWriter->setHostPrefix('node1');$fileWriter = $logger->getLogWriter('file');
$fileWriter->setHostPrefix($_SERVER['SERVER_ADDR']);Der StdOutLogWriter schreibt die hinzugefügten Log-Einträge auf die Standard-Ausgabe des Webservers. Dadurch sind die Log-Einträge für den Besucher sichtbar.
Die Registrierung des StdOutLogWriter kann in der Bootstrap-Datei wie folgt erfolgen:
import('core::logging::writer', 'StdOutLogWriter');
$logger->addLogWriter('stdout', new StdOutLogWriter());Log-Einträge mit dem Ziel stdout werden nun zusammen mit dem Quelltext der Applikation ausgegeben.
Der DatabaseLogWriter ermöglicht die Ausgabe der Log-Einträge in eine Datenbank-Tabelle. Erstellen Sie hierzu zunächst eine Tabelle mit dem folgenden SQL-Statement:
CREATE TABLE IF NOT EXISTS `{$this->logTable}` (
`target` varchar(10) NOT NULL default '',
`timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
`severity` varchar(10) NOT NULL default '',
`message` text NOT NULL
);Der Wert {$this->logTable} entspricht dabei dem Namen der Tabelle, mit dem der DatabaseLogWriter registriert werden soll. Auf diese Weise ist es möglich, verschiedene Instanzen des DatabaseLogWriter für unterschiedliche Log-Tabellen zu registrieren.
Um den LogWriter zu verwenden, nutzen Sie bitte folgenden Code-Block:
import('core::logging::writer', 'DatabaseLogWriter');
$logger->addlogWriter('db', new DatabaseLogWriter('my-connection', 'app_logs'));Das erste Argument des Konstruktors referenziert die Datenbank-Verbindung, die mit Hilfe des ConnectionManager erzeugt wird, der zweite Parameter benennt die Log-Tabelle.
Der GraphiteLogWriter - zusammen mit dem GraphiteLogEntry - ist eine Implementierung für das System-Monitoring-Tool Graphite. Mit Hilfe des GraphiteLogEntry können Sie auf einfache Weise Monitoring Metriken an einen statsd-Daemon schicken.
Zur Registrierung des GraphiteLogWriter können Sie folgenden Code nutzen:
import('core::logging::writer', 'GraphiteLogWriter');
$logger->addlogWriter('graphite', new GraphiteLogWriter('my-graphite-host', '1234'));Da die Implementierung des GraphiteLogEntry bereits alle notwendigen Formatierungen vornimmt, kann beim GraphiteLogWriter lediglich das Trennzeichen zwischen mehreren Einträgen konfiguriert werden. Nutzen Sie hierzu bitte die Methode setEntrySeparator() und übergeben dieser das gewünschte Trennzeichen (Standard: \n).
Die Verwendung des GraphiteLogWriter gestaltet sich wie folgt:
$t = Singleton::getInstance('BenchmarkTimer');
$renderingTime = round(floatval($t->getTotalTime() * 1000));
import('core::logging::entry', 'GraphiteLogEntry');
$logger->addEntry(new GraphiteLogEntry(
'graphite',
'rendering-time',
'ms',
$renderingTime,
LogEntry::SEVERITY_INFO
));// Konfiguration über den Konstruktor
import('core::logging::writer', 'GraphiteLogWriter');
$logger->addlogWriter(
'graphite',
new GraphiteLogWriter('my-graphite-host', '1234', false)
);
// Nachträgliche Konfiguration
$writer = $logger->getLogWriter('graphite');
$writer->setBatchWrites(false);Zur Verwendung des Loggers muss dieser zunächst via
import('core::logging','Logger');eingebunden werden. Dies gilt auch für die in Kapitel 3 gezeigten Beispiele. Anschließend kann mit
$log = &Singleton::getInstance('Logger');eine Referenz auf die Instanz des Loggers im aktuellen Gültigkeitsbereich erzeugt werden. Das Hinzufügen eines Eintrags mit dem Inhalt MESSAGE zu einem Logfile mit dem Namen FILENAME ist mit dem Aufruf
$log->logEntry('FILENAME', 'MESSAGE');erledigt. Der dritte Parameter (optional) der Methode logEntry() definiert die SEVERITY der Meldung. Die verfügbaren Werte für den Schweregrad sind im Interface LogEntry definiert.
Ab Version 1.17 kann ebenso die Methode addEntry() genutzt werden. Diese erwartet eine Instanz des Interfaces LogEntry:
$log->addEntry(new SimpleLogEntry('FILENAME', 'MESSAGE'));Das in Kapitel 2 beschriebene Interface umfasst die Methoden writeLogEntries() und setTarget(). Die erste Methode kümmert sich bei der Implementierung eines LogWriter um die Persistenz der übergebenen Einträge, die zweite nimmt den Ziel-Bezeichner entgegen.
Während der Registrierung eines LogWriters mit Hilfe der Methode Logger::addLogWriter() injiziert der Logger den Ziel-Bezeichner über die Methode LogWriter::setTarget() unter dem der LogWriter registriert wurde.
Das Interface LogWriter definiert selbst keinen Konstruktor um die Implementierung hinsichtlich ihrer Konfigurierbarkeit nicht einzuschränken.
Als Implementierungsbeispiel soll im Folgenden ein LogWriter erstellt werden, der die übergebenen Einträge an den Syslog-Daemon übergibt. Hierzu kann die PHP-Funktion syslog(int $priority, string $message) genutzt werden. Die Implementierung gestaltet sich wie folgt:
class SysLogWriter implements LogWriter {
private $target;
public function writeLogEntries(array $entries) {
define_syslog_variables();
openlog($this->target, LOG_PID, LOG_LOCAL0);
foreach ($entries as $entry) {
syslog(
$this->getMappedSeverity($entry->getSeverity()),
$entry
);
}
closelog();
}
public function setTarget($target) {
$this->target = $target;
}
private function getMappedSeverity($severity) {
if ($severity == LogEntry::SEVERITY_DEBUG) {
return LOG_DEBUG;
}
if ($severity == LogEntry::SEVERITY_ERROR) {
return LOG_ERR;
}
if ($severity == LogEntry::SEVERITY_FATAL) {
return LOG_EMERG;
}
if ($severity == LogEntry::SEVERITY_TRACE) {
return LOG_NOTICE;
}
if ($severity == LogEntry::SEVERITY_WARNING) {
return LOG_WARNING;
}
return LOG_INFO;
}
}Um die Implementierung einzusetzen, kann diese wie folgt beim Logger registriert werden:
$logger->addLogWriter('syslog', new SysLogWriter());Das in Kapitel 2 beschriebe Interface umfasst die Methoden getLogTarget(), getSeverity() und __toString(). Die ersten beiden Methode geben Log-Ziel und severity-Definition aus, die letzte erzeugt das Format bzw. den Inhalt des Log-Eintrags.
Beim Hinzufügen eines Log-Eintrags mit den Methoden logEntry() (nutzt intern die LogEntry-Implementierung SimpleLogEntry) bzw. addEntry() (beliebige Implementierungen nutzbar) sortiert der Logger die übergebenen Einträge bereits an Hand des Log-Ziels (Rückgabe der Methode getLogTarget()) für die Verarbeitung an Ende des Requests vor.
Das Interface LogEntry definiert selbst keinen Konstruktor um die Implementierung hinsichtlich der Inhalte nicht einzuschränken.
Als Implementierungsbeispiel soll im Folgenden ein CsvLogEntry erstellt werden, der die übergebenen Inhalte im comma separated values Stil speichert. Die Implementierung gestaltet sich wie folgt:
class CsvLogEntry implements LogEntry {
private $target;
private $severity;
private $message;
private $date;
private $time;
public function __construct($target, $message, $severity) {
$this->date = date('Y-m-d');
$this->time = date('H:i:s');
$this->target = $target;
$this->message = $message;
$this->severity = $severity;
}
public function __toString() {
return $this->date . ';' . $this->time . ';' . $this->severity . ';' . $this->message;
}
public function getLogTarget() {
return $this->target;
}
public function getSeverity() {
return $this->severity;
}
}Möchten Sie statt einem SimpleLogEntry die CsvLogEntry-Implementierung nutzen, so bietet Ihnen die Methode Logger::addEntry() das passende Interface dazu:
$logger->addEntry(new CsvLogEntry(
'csv-file',
'There is something wrong!',
LogEntry::SEVERITY_ERROR
));Um eine CSV-Datei mit der vorliegenden Implementierung zu erstellen, muss für das Log-Ziel csv-file zunächst per
$logger->addLogWriter('csv-file', new FileLogWriter('../logs'));ein entsprechender LogWriter registriert werden.
Die Entwicklung des APF wird von JetBRAINS mit PHPStorm-Lizenzen unterstützt und wir sind überzeugt davon, dass PHPStorm die Qualität nachhaltig steigert. Benutzen auch Sie PHPStorm!
Proud to useIntelligent PHP IDE for coding, testing and debugging with pleasure