From 0c576ea19d6d42950615f94feb33df305107a9a6 Mon Sep 17 00:00:00 2001 From: mistic100 Date: Fri, 24 Apr 2015 14:00:50 +0000 Subject: feature 3221 Add Logger class git-svn-id: http://piwigo.org/svn/trunk@31102 68402e56-0260-453c-a942-63ccdbb3a9ee --- include/Logger.class.php | 453 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 453 insertions(+) create mode 100644 include/Logger.class.php (limited to 'include/Logger.class.php') diff --git a/include/Logger.class.php b/include/Logger.class.php new file mode 100644 index 000000000..da90e7152 --- /dev/null +++ b/include/Logger.class.php @@ -0,0 +1,453 @@ + + * + * @package logger + */ + +class Logger +{ + /** + * Error severity, from low to high. From BSD syslog RFC, section 4.1.1 + * @link http://www.faqs.org/rfcs/rfc3164.html + */ + const EMERGENCY = 0; // Emergency: system is unusable + const ALERT = 1; // Alert: action must be taken immediately + const CRITICAL = 2; // Critical: critical conditions + const ERROR = 3; // Error: error conditions + const WARNING = 4; // Warning: warning conditions + const NOTICE = 5; // Notice: normal but significant condition + const INFO = 6; // Informational: informational messages + const DEBUG = 7; // Debug: debug messages + + /** + * Custom "disable" level. + */ + const OFF = -1; // Log nothing at all + + /** + * Internal status codes. + */ + const STATUS_LOG_OPEN = 1; + const STATUS_OPEN_FAILED = 2; + const STATUS_LOG_CLOSED = 3; + + /** + * Disable archive purge. + */ + const ARCHIVE_NO_PURGE = -1; + + /** + * Standard messages produced by the class. + * @var array + */ + private static $_messages = array( + 'writefail' => 'The file could not be written to. Check that appropriate permissions have been set.', + 'opensuccess' => 'The log file was opened successfully.', + 'openfail' => 'The file could not be opened. Check permissions.', + ); + + /** + * Instance options. + * @var array + */ + private $options = array( + 'directory' => null, // Log files directory + 'filename' => null, // Path to the log file + 'globPattern' => 'log_*.txt', // Pattern to select all log files with glob() + 'severity' => self::DEBUG, // Current minimum logging threshold + 'dateFormat' => 'Y-m-d G:i:s', // Date format + 'archiveDays' => self::ARCHIVE_NO_PURGE, // Number of files to keep + ); + + /** + * Current status of the logger. + * @var integer + */ + private $_logStatus = self::STATUS_LOG_CLOSED; + /** + * File handle for this instance's log file. + * @var resource + */ + private $_fileHandle = null; + + /** + * Class constructor. + * + * @param array $options + * @return void + */ + public function __construct($options) + { + $this->options = array_merge($this->options, $options); + + if (is_string($this->options['severity'])) { + $this->options['severity'] = self::codeToLevel($this->options['severity']); + } + + if ($this->options['severity'] === self::OFF) { + return; + } + + $this->options['directory'] = rtrim($this->options['directory'], '\\/') . DIRECTORY_SEPARATOR; + + if ($this->options['filename'] == null) { + $this->options['filename'] = 'log_' . date('Y-m-d') . '.txt'; + } + + $this->options['filePath'] = $this->options['directory'] . $this->options['filename']; + + if (!file_exists($this->options['directory'])) { + mkgetdir($this->options['directory'], MKGETDIR_DEFAULT|MKGETDIR_PROTECT_HTACCESS); + } + + if (file_exists($this->options['filePath']) && !is_writable($this->options['filePath'])) { + $this->_logStatus = self::STATUS_OPEN_FAILED; + throw new RuntimeException(self::$_messages['writefail']); + return; + } + + if (($this->_fileHandle = fopen($this->options['filePath'], 'a'))) { + $this->_logStatus = self::STATUS_LOG_OPEN; + } + else { + $this->_logStatus = self::STATUS_OPEN_FAILED; + throw new RuntimeException(self::$_messages['openfail']); + } + + if ($this->options['archiveDays'] != self::ARCHIVE_NO_PURGE && rand() % 97 == 0) { + $this->purge(); + } + } + + /** + * Class destructor. + */ + public function __destruct() + { + if ($this->_fileHandle) { + fclose($this->_fileHandle); + } + } + + /** + * Returns logger status. + * + * @return int + */ + public function status() + { + return $this->_logStatus; + } + + /** + * Returns logger severity threshold. + * + * @return int + */ + public function severity() + { + return $this->options['severity']; + } + + /** + * Writes a $line to the log with a severity level of DEBUG. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function debug($line, $cat = null, $args = array()) + { + $this->log(self::DEBUG, $line, $cat, $args); + } + + /** + * Writes a $line to the log with a severity level of INFO. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function info($line, $cat = null, $args = array()) + { + $this->log(self::INFO, $line, $cat, $args); + } + + /** + * Writes a $line to the log with a severity level of NOTICE. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function notice($line, $cat = null, $args = array()) + { + $this->log(self::NOTICE, $line, $cat, $args); + } + + /** + * Writes a $line to the log with a severity level of WARNING. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function warn($line, $cat = null, $args = array()) + { + $this->log(self::WARNING, $line, $cat, $args); + } + + /** + * Writes a $line to the log with a severity level of ERROR. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function error($line, $cat = null, $args = array()) + { + $this->log(self::ERROR, $line, $cat, $args); + } + + /** + * Writes a $line to the log with a severity level of ALERT. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function alert($line, $cat = null, $args = array()) + { + $this->log(self::ALERT, $line, $cat, $args); + } + + /** + * Writes a $line to the log with a severity level of CRITICAL. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function critical($line, $cat = null, $args = array()) + { + $this->log(self::CRITICAL, $line, $cat, $args); + } + + /** + * Writes a $line to the log with a severity level of EMERGENCY. + * + * @param string $line + * @param string $cat + * @param array $args + */ + public function emergency($line, $cat = null, $args = array()) + { + $this->log(self::EMERGENCY, $line, $cat, $args); + } + + /** + * Writes a $line to the log with the given severity. + * + * @param integer $severity + * @param string $line + * @param string $cat + * @param array $args + */ + public function log($severity, $message, $cat = null, $args = array()) + { + if ($this->severity() >= $severity) { + if (is_array($cat)) { + $args = $cat; + $cat = null; + } + $line = $this->formatMessage($severity, $message, $cat, $args); + $this->write($line); + } + } + + /** + * Directly writes a line to the log without adding level and time. + * + * @param string $line + */ + public function write($line) + { + if ($this->_logStatus == self::STATUS_LOG_OPEN) { + if (fwrite($this->_fileHandle, $line) === false) { + throw new RuntimeException(self::$_messages['writefail']); + } + } + } + + /** + * Purges files matching 'globPattern' older than 'archiveDays'. + */ + public function purge() { + $files = glob($this->options['directory'] . $this->options['globPattern']); + $limit = time() - $this->options['archiveDays'] * 86400; + + foreach ($files as $file) { + if (@filemtime($file) < $limit) { + @unlink($file); + } + } + } + + /** + * Formats the message for logging. + * + * @param string $level + * @param string $message + * @param array $context + * @return string + */ + private function formatMessage($level, $message, $cat, $context) + { + if (!empty($context)) { + $message .= "\n" . $this->indent($this->contextToString($context)); + } + $line = "[" . $this->getTimestamp() . "]\t[" . self::levelToCode($level) . "]\t"; + if ($cat != null) { + $line .= "[" . $cat . "]\t"; + } + return $line . $message . "\n"; + } + + /** + * Gets the formatted Date/Time for the log entry. + * + * PHP DateTime is dumb, and you have to resort to trickery to get microseconds + * to work correctly, so here it is. + * + * @return string + */ + private function getTimestamp() + { + $originalTime = microtime(true); + $micro = sprintf("%06d", ($originalTime - floor($originalTime)) * 1000000); + $date = new DateTime(date('Y-m-d H:i:s.'.$micro, $originalTime)); + return $date->format($this->options['dateFormat']); + } + + /** + * Takes the given context and converts it to a string. + * + * @param array $context + * @return string + */ + private function contextToString($context) + { + $export = ''; + foreach ($context as $key => $value) { + $export .= "{$key}: "; + $export .= preg_replace(array( + '/=>\s+([a-zA-Z])/im', + '/array\(\s+\)/im', + '/^ |\G /m' + ), array( + '=> $1', + 'array()', + ' ' + ), str_replace('array (', 'array(', var_export($value, true))); + $export .= PHP_EOL; + } + return str_replace(array('\\\\', '\\\''), array('\\', '\''), rtrim($export)); + } + + /** + * Indents the given string with the given indent. + * + * @param string $string The string to indent + * @param string $indent What to use as the indent. + * @return string + */ + private function indent($string, $indent = ' ') + { + return $indent.str_replace("\n", "\n".$indent, $string); + } + + /** + * Converts level constants to string name. + * + * @param int $level + * @return string + */ + static function levelToCode($level) + { + switch ($level) { + case self::EMERGENCY: + return 'EMERGENCY'; + case self::ALERT: + return 'ALERT'; + case self::CRITICAL: + return 'CRITICAL'; + case self::NOTICE: + return 'NOTICE'; + case self::INFO: + return 'INFO'; + case self::WARNING: + return 'WARNING'; + case self::DEBUG: + return 'DEBUG'; + case self::ERROR: + return 'ERROR'; + default: + throw new RuntimeException('Unknown severity level ' . $level); + } + } + + /** + * Converts level names to constant. + * + * @param string $code + * @return int + */ + static function codeToLevel($code) + { + switch (strtoupper($code)) { + case 'EMERGENCY': + return self::EMERGENCY; + case 'ALERT': + return self::ALERT; + case 'CRITICAL': + return self::CRITICAL; + case 'NOTICE': + return self::NOTICE; + case 'INFO': + return self::INFO; + case 'WARNING': + return self::WARNING; + case 'DEBUG': + return self::DEBUG; + case 'ERROR': + return self::ERROR; + default: + throw new RuntimeException('Unknown severity code ' . $code); + } + } +} -- cgit v1.2.3