<?php
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* LoggerAppenderPDO appender logs to a database using the PHP's PDO extension.
*
* ## Configurable parameters: ##
*
* - dsn - The Data Source Name (DSN) used to connect to the database.
* - user - Username used to connect to the database.
* - password - Password used to connect to the database.
* - table - Name of the table to which log entries are be inserted.
* - insertSQL - Sets the insert statement for a logging event. Defaults
* to the correct one - change only if you are sure what you are doing.
* - insertPattern - The conversion pattern to use in conjuction with insert
* SQL. Must contain the same number of comma separated
* conversion patterns as there are question marks in the
* insertSQL.
*
* @version $Revision: 1374546 $
* @package log4php
* @subpackage appenders
* @since 2.0
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @link http://logging.apache.org/log4php/docs/appenders/pdo.html Appender documentation
*/
class LoggerAppenderPDO extends LoggerAppender {
// ******************************************
// *** Configurable parameters ***
// ******************************************
/**
* DSN string used to connect to the database.
* @see http://www.php.net/manual/en/pdo.construct.php
*/
protected $dsn;
/** Database user name. */
protected $user;
/** Database password. */
protected $password;
/**
* The insert query.
*
* The __TABLE__ placeholder will be replaced by the table name from
* {@link $table}.
*
* The questionmarks are part of the prepared statement, and they must
* match the number of conversion specifiers in {@link insertPattern}.
*/
protected $insertSQL = "INSERT INTO __TABLE__ (timestamp, logger, level, message, thread, file, line) VALUES (?, ?, ?, ?, ?, ?, ?)";
/**
* A comma separated list of {@link LoggerPatternLayout} format strings
* which replace the "?" in {@link $insertSQL}.
*
* Must contain the same number of comma separated conversion patterns as
* there are question marks in {@link insertSQL}.
*
* @see LoggerPatternLayout For conversion patterns.
*/
protected $insertPattern = "%date{Y-m-d H:i:s},%logger,%level,%message,%pid,%file,%line";
/** Name of the table to which to append log events. */
protected $table = 'log4php_log';
/** The number of recconect attempts to make on failed append. */
protected $reconnectAttempts = 3;
// ******************************************
// *** Private memebers ***
// ******************************************
/**
* The PDO instance.
* @var PDO
*/
protected $db;
/**
* Prepared statement for the insert query.
* @var PDOStatement
*/
protected $preparedInsert;
/** This appender does not require a layout. */
protected $requiresLayout = false;
// ******************************************
// *** Appender methods ***
// ******************************************
/**
* Acquires a database connection based on parameters.
* Parses the insert pattern to create a chain of converters which will be
* used in forming query parameters from logging events.
*/
public function activateOptions() {
try {
$this->establishConnection();
} catch (PDOException $e) {
$this->warn("Failed connecting to database. Closing appender. Error: " . $e->getMessage());
$this->close();
return;
}
// Parse the insert patterns; pattern parts are comma delimited
$pieces = explode(',', $this->insertPattern);
$converterMap = LoggerLayoutPattern::getDefaultConverterMap();
foreach ($pieces as $pattern) {
$parser = new LoggerPatternParser($pattern, $converterMap);
$this->converters[] = $parser->parse();
}
$this->closed = false;
}
/**
* Connects to the database, and prepares the insert query.
* @throws PDOException If connect or prepare fails.
*/
protected function establishConnection() {
// Acquire database connection
$this->db = new PDO($this->dsn, $this->user, $this->password);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Prepare the insert statement
$insertSQL = str_replace('__TABLE__', $this->table, $this->insertSQL);
$this->preparedInsert = $this->db->prepare($insertSQL);
}
/**
* Appends a new event to the database.
*
* If writing to database fails, it will retry by re-establishing the
* connection up to $reconnectAttempts times. If writing still fails,
* the appender will close.
*/
public function append(LoggerLoggingEvent $event) {
for ($attempt = 1; $attempt <= $this->reconnectAttempts + 1; $attempt++) {
try {
// Attempt to write to database
$this->preparedInsert->execute($this->format($event));
$this->preparedInsert->closeCursor();
break;
} catch (PDOException $e) {
$this->warn("Failed writing to database: " . $e->getMessage());
// Close the appender if it's the last attempt
if ($attempt > $this->reconnectAttempts) {
$this->warn("Failed writing to database after {$this->reconnectAttempts} reconnect attempts. Closing appender.");
$this->close();
// Otherwise reconnect and try to write again
} else {
$this->warn("Attempting a reconnect (attempt $attempt of {$this->reconnectAttempts}).");
$this->establishConnection();
}
}
}
}
/**
* Converts the logging event to a series of database parameters by using
* the converter chain which was set up on activation.
*/
protected function format(LoggerLoggingEvent $event) {
$params = array();
foreach ($this->converters as $converter) {
$buffer = '';
while ($converter !== null) {
$converter->format($buffer, $event);
$converter = $converter->next;
}
$params[] = $buffer;
}
return $params;
}
/**
* Closes the connection to the logging database
*/
public function close() {
// Close the connection (if any)
$this->db = null;
// Close the appender
$this->closed = true;
}
// ******************************************
// *** Accessor methods ***
// ******************************************
/**
* Returns the active database handle or null if not established.
* @return PDO
*/
public function getDatabaseHandle() {
return $this->db;
}
/** Sets the username. */
public function setUser($user) {
$this->setString('user', $user);
}
/** Returns the username. */
public function getUser($user) {
return $this->user;
}
/** Sets the password. */
public function setPassword($password) {
$this->setString('password', $password);
}
/** Returns the password. */
public function getPassword($password) {
return $this->password;
}
/** Sets the insert SQL. */
public function setInsertSQL($sql) {
$this->setString('insertSQL', $sql);
}
/** Returns the insert SQL. */
public function getInsertSQL($sql) {
return $this->insertSQL;
}
/** Sets the insert pattern. */
public function setInsertPattern($pattern) {
$this->setString('insertPattern', $pattern);
}
/** Returns the insert pattern. */
public function getInsertPattern($pattern) {
return $this->insertPattern;
}
/** Sets the table name. */
public function setTable($table) {
$this->setString('table', $table);
}
/** Returns the table name. */
public function getTable($table) {
return $this->table;
}
/** Sets the DSN string. */
public function setDSN($dsn) {
$this->setString('dsn', $dsn);
}
/** Returns the DSN string. */
public function getDSN($dsn) {
return $this->setString('dsn', $dsn);
}
}