Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /**
- * Doplněk - používané výjimky (které nejsou součástí SPL).
- */
- if (!class_exists('NotImplementedException')) {
- class NotImplementedException extends LogicException { }
- }
- if (!class_exists('IOException')) {
- class IOException extends RuntimeException { }
- }
- if (!class_exists('FileNotFoundException')) {
- class FileNotFoundException extends IOException { }
- }
- /**
- * <b>Log: Logování do souboru</b>
- *
- * <i>Použití:</i>
- * $log = Log::getInstance();
- * $log->add('Zpráva do logu', 'Volitelný ladící výpis,který se k ní připojí');
- * $log->add(. ....
- * $log->write();
- *
- * <i>Nastavení:</i>
- * $log->setLogDir('../log'); //cesta k adresáři s logy. Výchozí je aktuální adresář, musí se zadat *absolutní* cesta.
- * $log->setMaxLogSize('2MB'); // max.velikost logu. Buď v bytech, nebo 'k/kb', 'M/MB' (case-insens.). Výchozí je 0 = bez limitu
- * $log->setLogName('myLog.<date>.log'); // maska pro názvy souboru. Musí obs. "<date>" - u aktuálního se nahradí "current", u
- * archivovaných logů datumem archivace. Výchozí je "<date>.log". Není možné zadávat i cestu k souboru.
- * - metody setLogDir, setMaxLogSize, setLogName a add vracejí opět instanci příslušného logu, je možné je řetězit.
- *
- * <i>Několik různých logů:</i>
- * Log::getInstance('app')->setLogDir('../log')->setLogName('appLog.<date>');
- * Log::getInstance('db')->setLogDir('../log')->setLogName('db.<date>');
- * Log::getInstance()->setLogDir('../log')->setLogName('blizeNeurcenyLog.<date>.log');
- *
- * Log::getInstance('db')->add('Chyba databáze.');
- * Log::getInstance('app')->add('Chyba aplikace')->add('Další chyba aplikace');
- * Log::getInstance()->add('Toto jde do nepojmenovaného logu.');
- *
- * Log::getInstance('db')->write(); // zapíše jen databázový log
- * Log::writeAll(); // zapíše všechny existující logy
- * - Metody write() / writeAll() je praktické volat z funkce registrované jako shutdown handler.
- *
- * @author Viktorie Halasů <[email protected]>
- * @version 1.1 (lastmod 2012-05-03)
- * @license MIT <http://www.opensource.org/licenses/mit-license.php>
- */
- final class Log {
- /** @var string Název souboru zámku. */
- private $lockName = '.lock';
- /** @var string Cesta k adresáři s logy. Výchozí = adresář s touto třídou. */
- private $path = '';
- /** @var string Maska pro název souboru logu (musí obsahovat řetězec '<date>'). */
- private $logNameMask = '<date>.log';
- /** @var int Limit velikosti logu (v bytech). Pokud 0, tak se nepoužije. */
- private $limit = 0;
- /** @var array Záznamy, které se mají zapsat do logu.*/
- private $entries = array();
- private static $instances = array();
- /**
- * Soukromý konstruktor.
- */
- private function __construct() {
- $this->path = dirname(__FILE__);
- }
- /**
- * Vrací instanci Logu, v případě potřeby ji vytvoří.
- * @param string $logName Volitelný identifikátor logu (pokud se jich používá víc).
- * @return Log
- */
- public static function getInstance($logName = 0) {
- if (!isset(self::$instances[$logName]))
- self::$instances[$logName] = new self;
- return self::$instances[$logName];
- }
- /**
- * Přidá zprávu do fronty logu.
- * @param string $zprava Text zprávy k zapsání.
- * @param string $debug='' Ladící výpis
- * @return Log fluent interface
- */
- public function add($zprava, $debug='') {
- $this->entries[] = $this->format($zprava, $debug);
- return $this;
- }
- /**
- * Zapíše do souboru logu všechny zprávy ve frontě.
- * @throws IOException Pokud se nepodařilo zapsat data.
- */
- public function write() {
- if (empty($this->entries))
- return;
- $this->tryBackup();
- if (!file_put_contents($this->getFilename(), implode("\n", $this->entries)."\n", FILE_APPEND | LOCK_EX))
- throw new IOException('Log: Cannot write to log file.');
- $this->entries = array();
- }
- /**
- * Pokusí se zapsat všechny existující logy.
- */
- static public function writeAll() {
- foreach (self::$instances as $log)
- $log->write();
- }
- /**
- * Nastaví cestu k adresáři s logy. Ověří, jestli je adresářem a zapisovatelný.
- * @param string $path *Absolutní cesta* k adresáři.
- * @throws FileNotFoundException Pokud název nezadán, nebo není adresář.
- * @throws IOException Pokud adresář není zapisovatelný.
- * @return Log fluent interface
- */
- public function setLogDir($path) {
- if (empty($path) || !is_dir($path))
- throw new FileNotFoundException('Bad filename given to Log::setLogDir (not a directory or empty string).');
- if (!is_writable($path))
- throw new IOException('Log::setLogDir: given directory is not writable.');
- $this->path = rtrim($path,'/');
- return $this;
- }
- /**
- * Nastaví limit pro velikost logu.
- * @param string $size Buď přesná velikost textově (1k, 5M, 3MB,...), nebo 0 (=bez limitu).
- * @throws InvalidArgumentException Pokud byla zadaná neplatná hodnota.
- * @return Log fluent interface.
- */
- public function setMaxLogSize($size) {
- if ($size == 0)
- return $this;
- if (!preg_match('~^(?P<digit>\d+)\s*(?P<unit>\w*)$~', trim($size), $matches))
- throw new InvalidArgumentException('Bad max filesize given to Log::setMaxLogSize. Proper format is a decimal number optionally followed by "k","kb","m" or "mb" (case insensitive).');
- switch (strtolower($matches['unit'])) {
- case 'k':
- case 'kb':
- $this->limit = $matches['digit'] * pow(2,10);
- break;
- case 'm':
- case 'mb':
- $this->limit = $matches['digit'] * pow(2,20);
- break;
- default:
- $this->limit = $matches['digit'];
- }
- return $this;
- }
- /**
- * Nastavuje masku pro jméno souboru logu, kde "<date>" bude nahrazeno datumem archivace.
- * Výchozí je <date>.log. Pro aktivní (současný) soubor logu se <date> nahradí "current".
- * @param string $mask Smí obsahovat pouze alfanum.znaky, pomlčku, podtržítko, tečku a vlnovku.
- * @throws InvalidArgumentException Pokud maska pro název souboru obsahuje nepovolené znaky.
- * @return Log fluent interface
- */
- public function setLogName($mask) {
- if (empty($mask))
- return $this;
- if (!preg_match('/^[a-z0-9_.~-]*<date>[a-z0-9_.~-]*$/i', $mask))
- throw new InvalidArgumentException('Bad filename given to Log::setLogName. Filename mask must contain "<date>" and only "a-z0-9_.~-" are allowed.');
- $this->logNameMask = $mask;
- return $this;
- }
- /**
- * Zformátuje zprávu do podoby používané v logu.
- * @param string $msg Zpráva
- * @param string $debug='' Volitelný ladící výpis
- * @return string
- *
- * TBD Vyhazovat CR/LF z ladícího výpisu nebo ne?
- */
- private function format($msg, $debug='') {
- /* $debug = empty($debug) ? '' : preg_replace("~[\r\n]+~", ';', $debug); */
- $line = date('Y-m-d H:i:s')."\t$msg\t$debug";
- return $line;
- }
- /**
- * Zjistí aktuální velikost souboru logu (v bytech).
- * @return int
- */
- private function getLogSize() {
- clearstatcache();
- if (file_exists($this->getFilename()))
- return filesize($this->getFilename());
- else
- return 0;
- }
- /**
- * Zjistí, jestli jiné vláknu neprovádí archivaci logu (= existuje soubor zámku).
- * Automaticky maže zámky starší než 1 hodina.
- * @return bool
- */
- private function isLocked() {
- clearstatcache();
- $this->removeOldLocks();
- return file_exists($this->path . '/' . $this->lockName);
- }
- /**
- * Nastaví zámek
- */
- private function lock() {
- file_put_contents($this->path . '/' . $this->lockName, "\n");
- }
- /**
- * Zruší zámek
- * @throws IOException Pokud nebylo možné smazat zámek.
- */
- private function unlock() {
- $filename = $this->path . '/' . $this->lockName;
- if (file_exists($filename)) {
- if (!unlink($filename)) {
- $error = error_get_last();
- throw new IOException('Log: deleting current lock failed. Error: '.$error['message']);
- }
- }
- }
- /**
- * Zruší staré soubory zámku (starší než 1hod)
- * @throws IOException Pokud nebylo možné smazat starý zámek.
- */
- private function removeOldLocks() {
- $filename = $this->path . '/' . $this->lockName;
- if (file_exists($filename)) {
- if (filemtime($filename) < time()-3600) {
- if (!unlink($filename)) {
- $error = error_get_last();
- throw new IOException('Log: deleting old lock failed. Error: '.$error['message']);
- }
- }
- }
- }
- /**
- * Zjistí, jestli není potřeba archivovat log.
- * @return bool
- */
- private function logLimitReached() {
- if ($this->limit == 0)
- return false;
- return ($this->getLogSize() > $this->limit);
- }
- /**
- * Pokud je to potřeba, zálohuje aktuální log pod aktuálním datumem, a current.log zkrátí na "\n".
- * Pokud právě probíhá záloha na jiném vlákně, ostatní vlákna čekají na uvolnění.
- * @throws IOException Pokud se nedá vytvořit záloha logu.
- */
- private function tryBackup() {
- if ($this->logLimitReached()) {
- if ($this->isLocked()) {
- $maxWaitTime = 5; /* jak dlouho nejdele smi cekat na odblokování (sec) */
- for ($stopTime = time() + $maxWaitTime; !$this->isLocked() || time() < $stopTime; usleep(300)) { }
- }
- else {
- $this->lock();
- if (!copy($this->getFilename(), $this->makeBackupFilename())) {
- $this->unlock();
- throw new IOException('Log: Backup of current log failed.');
- }
- file_put_contents($this->getFilename(), "\n");
- $this->unlock();
- }
- }
- }
- /**
- * Vytvoří cestu k záložnímu souboru logu podle masky.
- * Pokud už existuje, vytvoří nový s náhodným sufixem.
- * @return string
- */
- private function makeBackupFilename() {
- $filename = $this->path . '/' . str_replace('<date>', date('Y-m-d_H-i-s'), $this->logNameMask);
- clearstatcache();
- if (file_exists($filename))
- $filename = $this->path . '/' . str_replace('<date>', date('Y-m-d_H-i-s').'_'.rand(1000,9999), $this->logNameMask);
- return $filename;
- }
- /**
- * Vytvoří cestu k aktuálnímu souboru logu podle masky.
- * @return string
- */
- private function getFilename() {
- return $this->path . '/' . str_replace('<date>', 'current', $this->logNameMask);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment