Location: PHPKode > scripts > Logger_ciacob > logger_ciacob/assets/Logger.php
<?php
	/**
	* "Dumps input data to a file on disk."
    * @author: Claudius Tiberiu Iacob <hide@address.com>
    * @link: http://goodys-in-code.ciacob.org
    * @version: 1.0
	*/
	class Logger extends ParamsProxy {

		const NOT_INITIALIZED = "NOT_INITIALIZED";
		const ILLEGAL_MAX_FILE_SIZE = "ILLEGAL_MAX_FILE_SIZE";
		const ILLEGAL_OPERATING_MODE = "ILLEGAL_OPERATING_MODE";
		const ILLEGAL_WRAP_LIMIT = "ILLEGAL_WRAP_LIMIT";
		const ILLEGAL_BANNER_TEMPLATE = "ILLEGAL_BANNER_TEMPLATE";
		const ILLEGAL_ENTRY_TEMPLATE = "ILLEGAL_ENTRY_TEMPLATE";
		const ILLEGAL_CONTEXT_NAME = "ILLEGAL_CONTEXT_NAME";
		const CANNOT_REDEFINE_CONTEXT = "CANNOT_REDEFINE_CONTEXT";
		const ILLEGAL_CONTEXT_DEFINITION = "ILLEGAL_CONTEXT_DEFINITION";
		const ILLEGAL_CLASS_NAME = "ILLEGAL_CLASS_NAME";
		const CANNOT_REASIGN_CLASS_NAME = "CANNOT_REASIGN_CLASS_NAME";
		const CLASS_IS_NOT_ASIGNED = "CLASS_IS_NOT_ASIGNED";
		const LOGS_DIR_NOT_WRITABLE = "LOGS_DIR_NOT_WRITABLE";
		const LOGS_DIR_ROOT_NOT_WRITABLE = "LOGS_DIR_ROOT_NOT_WRITABLE";

        private static $instance = null;

        private $isInitialized = false;
        private $logsPath = null;
        private $logsDirName = null;
        private $logFileName = "log.txt";
        private $contexts = null;
        private $mainCtxName = "Main";
        private $maxFileSize = null;
        private $operatingMode = null;
        private $trimToken = "trim";
        private $splitToken = "split";
        private $wrapLimit = null;
        private $bannerTemplate = null;
        private $entryTemplate = null;
        private $bannerLinesNo = 5;
        private $maxWrapLimit = 240;
        private $tabSize = 4;

		private $filePathPH = "FILE PATH";
		private $fileSizePH = "FILE SIZE";
		private $fileStatusPH = "FILE STATUS";
		private $lastModifiedDate = "LAST MODIFIED DATE";
		private $relatedContextName = "RELATED CONTEXT NAME";
        private $yPH = "Y";
		private $mPH = "M";
		private $dPH = "D";
		private $hPH = "H";
		private $minPH = "MIN";
		private $sPH = "S";
		private $callingClassNamePH = "CALLING CLASS NAME";
        private $contentPH = "CONTENT";
        private $fullStatus = "full";
        private $trimmedStatus = "trimmed";
        private $volumeStatus = "vol. no. #";

        private $currLogFilePath = null;
        private $currLogFileSize = null;
        private $currLogFileStatus = null;
        private $currLogFileDate = null;
        private $currCallingCtx = null;
        private $currCallingCls = null;



        public static function getInstance () {
            if (!self::$instance instanceof self) {
                self::$instance = new self();
            }
            return self::$instance;
        }

        protected function __construct () {
			parent::__construct();
			$this->logsDirName = "logs";
        	$this->contexts = array ();
			$this->maxFileSize = 125;
			$this->operatingMode = $this->trimToken;
			$this->wrapLimit = -1;
			$this->bannerTemplate = "";
			$this->entryTemplate = "%" . $this->contentPH . "%\n";
            parent::configure($this);
			$this->logsPath = $this->getLogsPath ();
			$this->contexts [$this->mainCtxName] = array ();
            $this->isInitialized = true;
		}

        private function __clone () {
            // disabling cloning
        }

        private function __wakeup () {
            // disabling serialization
        }

        public function configure ($instance) {
            // disabling the ability to configure an arbitrary class instance
        }

        public function setMaxFileSize ($value) {
        	if (!is_int($value)) {
				trigger_error(Logger::ILLEGAL_MAX_FILE_SIZE, E_USER_ERROR);
        	}
        	if ($value < 125 || $value > 1250) {
				trigger_error(Logger::ILLEGAL_MAX_FILE_SIZE, E_USER_ERROR);
        	}
			$this->maxFileSize = $value * 1024;
        }

        public function setOperatingMode ($value) {
        	if (!is_string($value)) {
				trigger_error(Logger::ILLEGAL_OPERATING_MODE, E_USER_ERROR);
        	}
        	if ($value !== $this->trimToken && $value !== $this->splitToken) {
				trigger_error(Logger::ILLEGAL_OPERATING_MODE, E_USER_ERROR);
        	}
        	$this->operatingMode = $value;
        }

        public function setWrapLimit ($value) {
        	if (!is_int($value)) {
				trigger_error(Logger::ILLEGAL_WRAP_LIMIT, E_USER_ERROR);
        	}
        	if ($value < 80 && $value > $this->maxWrapLimit && $value !== -1) {
				trigger_error(Logger::ILLEGAL_WRAP_LIMIT, E_USER_ERROR);
        	}
        	$this->wrapLimit = $value;
        }

        public function setBannerTemplate ($value) {
        	if (!is_string($value)) {
        		trigger_error(Logger::ILLEGAL_BANNER_TEMPLATE, E_USER_ERROR);
			}
			$value = $this->sanitizeString($value);
			$this->bannerTemplate = $value;
        }

        public function setEntryTemplate ($value) {
        	if (!is_string($value)) {
        		trigger_error(Logger::ILLEGAL_ENTRY_TEMPLATE, E_USER_ERROR);
			}
			$value = trim ($value);
			if ($value === "") {
				trigger_error(Logger::ILLEGAL_ENTRY_TEMPLATE, E_USER_ERROR);
			}
			if (strpos($value, "%" .$this->contentPH . "%") === false) {
				trigger_error(Logger::ILLEGAL_ENTRY_TEMPLATE, E_USER_ERROR);
			}
			$value = $this->sanitizeString($value);
			$value = chr (1) . $value;
			$this->entryTemplate = $value;
        }

		private function __call ($funcName, $arguments) {
            $ctxName = preg_replace('/^set/', "", $funcName);
			$ctxName = trim ($ctxName);
            if ($ctxName === "") {
            	trigger_error(Logger::ILLEGAL_CONTEXT_NAME, E_USER_ERROR);
			}
			if (strpos($ctxName, " ") !== false) {
            	trigger_error(Logger::ILLEGAL_CONTEXT_NAME, E_USER_ERROR);
			}
			if (strpos($ctxName, "__") === 0) {
            	trigger_error(Logger::ILLEGAL_CONTEXT_NAME, E_USER_ERROR);
			}
			if (preg_match('/^[_a-zA-Z]?[_a-zA-Z0-9]*$/', $ctxName) === 0) {
            	trigger_error(Logger::ILLEGAL_CONTEXT_NAME, E_USER_ERROR);
			}
			if (array_key_exists($ctxName, $this->contexts)) {
				if ($ctxName !== $this->$mainCtxName) {
					trigger_error(Logger::CANNOT_REDEFINE_CONTEXT, E_USER_ERROR);
				}
			}
			$clsNames = $arguments[0];
			if (!is_array($clsNames)) {
				trigger_error(Logger::ILLEGAL_CONTEXT_DEFINITION, E_USER_ERROR);
			}
			if (count ($clsNames) === 0) {
				trigger_error(Logger::ILLEGAL_CONTEXT_DEFINITION, E_USER_ERROR);
			}
			foreach ($clsNames as $clsName) {
				if (!is_string($clsName)) {
					trigger_error(Logger::ILLEGAL_CLASS_NAME, E_USER_ERROR);
				}
				$clsName = trim ($clsName);
				if ($clsName === "") {
					trigger_error(Logger::ILLEGAL_CLASS_NAME, E_USER_ERROR);
				}
				if (strpos($clsName, " ") !== false) {
					trigger_error(Logger::ILLEGAL_CLASS_NAME, E_USER_ERROR);
				}
				if (strpos($clsName, "__") === 0) {
					trigger_error(Logger::ILLEGAL_CLASS_NAME, E_USER_ERROR);
				}
				if (preg_match('/^[_a-zA-Z]?[_a-zA-Z0-9]*$/', $clsName) === 0) {
					trigger_error(Logger::ILLEGAL_CLASS_NAME, E_USER_ERROR);
				}
				$isClsNameAssigned = ($this->array_searchRecursive ($clsName, $this->contexts, true) !==
					false);
				if ($isClsNameAssigned) {
					trigger_error (Logger::CANNOT_REASIGN_CLASS_NAME, E_USER_ERROR);
				}
			}
			$this->contexts [$ctxName] = $clsNames;
		}

		public function log ($data) {
			if (!$this->isInitialized) {
				trigger_error (Logger::NOT_INITIALIZED, E_USER_ERROR);
			}
			$callingCtxName = null;
			if (count ($this->contexts) === 1) {
				if (count ($this->contexts[$this->mainCtxName]) === 0) {
					$callingCtxName = $this->mainCtxName;
				}
			}
			if ($callingCtxName === null) {
	            $trace = debug_backtrace();
	            $callingClsName = $trace[1]["class"];
	            $res = $this->array_searchRecursive($callingClsName, $this->contexts, true);
	            $callingCtxName = $res [0];
			}
			if ($callingCtxName === null) {
				trigger_error (Logger::CLASS_IS_NOT_ASIGNED, E_USER_ERROR);
			}
			$this->currCallingCls = $callingClsName;
			$this->currCallingCtx = $callingCtxName;
			clearstatcache();
			if (!is_dir($this->logsPath)) {
				$result = mkdir ($this->logsPath);
				if ($result === false) {
					trigger_error(Logger::LOGS_DIR_ROOT_NOT_WRITABLE, E_USER_ERROR);
				}
				chmod ($this->logsPath, 0777);
			}
			$folderPath = $this->logsPath . "/" . $this->currCallingCtx;
			if (!is_dir($folderPath)) {
				$result = mkdir ($folderPath);
				if ($result === false) {
					trigger_error(Logger::LOGS_DIR_NOT_WRITABLE, E_USER_ERROR);
				}
				chmod ($folderPath, 0777);
			}
			$this->currLogFilePath = $folderPath . "/" . $this->logFileName;
			$this->doPrint ($data);
		}

		private function printBanner ($bannerStr) {
			$fpointer = fopen ($this->currLogFilePath, "r+b");
			$result = fwrite($fpointer, $bannerStr);
			fclose($fpointer);
			if ($result === false) {
				trigger_error(Logger::LOGS_DIR_NOT_WRITABLE, E_USER_ERROR);
			}
		}

		private function prepareBanner ($isArchive = false) {
			$bannerStr = $this->fillBannerTemplate ($isArchive);
			$bannerStr = $this->sanitizeString($bannerStr);
			$lines = explode ("\n", $bannerStr);
			if (count ($lines) > $this->bannerLinesNo) {
				array_splice($lines, $this->bannerLinesNo);
				$lines [$this->bannerLinesNo - 1] .= "...";
			}
			$maxLen = ($this->wrapLimit !== -1)? $this->wrapLimit : $this->maxWrapLimit;
			for ($i=0; $i<count($lines); $i++) {
				$line = $lines[$i];
				$line = trim($line);
				if (strlen ($line) > $maxLen) {
					$line = substr($line, 0, $maxLen);
					$line = trim ($line);
					if (strlen ($line) > $maxLen -3) {
						$line = substr($line, 0, $maxLen-3);
					}
					$line .= "...";
				}
				if (strlen ($line) < $maxLen) {
					$line = str_pad($line, $maxLen, " ", STR_PAD_RIGHT);
				}
				$lines [$i] = $line;
			}
			$bannerStr = implode("\n", $lines);
			return $bannerStr;
		}

		private function fillBannerTemplate ($isArchive = false) {
			$template = $this->bannerTemplate;
			$placeHolders = array ($this->filePathPH, $this->fileSizePH, $this->fileStatusPH,
				$this->lastModifiedDate, $this->relatedContextName);
			foreach ($placeHolders as $placeHolder) {
				$val = "";
				switch ($placeHolder) {
					case $this->filePathPH:
						$val = $this->currLogFilePath;
						break;
					case $this->fileSizePH:
						clearstatcache();
						$val = filesize($this->currLogFilePath);
						break;
					case $this->fileStatusPH:
						$val = $this->fullStatus;
						if ($isArchive === true) {
							$volNo = $this->getLastVolOnSite($this->currLogFilePath) + 1;
							$val = str_replace("#", $volNo, $this->volumeStatus);
						} else {
							$bannerSize = ($this->wrapLimit !== -1)?
								$this->bannerLinesNo * $this->wrapLimit :
								$this->bannerLinesNo * $this->maxWrapLimit;
							$fpointer = fopen ($this->currLogFilePath, "rb");
							$test = fread($fpointer, $bannerSize + $bannerSize / $this->bannerLinesNo);
							fclose ($fpointer);
							if (strpos($test, chr(8)) !== false) {
								$val = $this->trimmedStatus;
							}
						}
						break;
					case $this->lastModifiedDate:
						$val = date ("Y/m/d H:i:s", filemtime($this->currLogFilePath));
						break;
					case $this->relatedContextName:
						$val = $this->currCallingCtx;
						break;
				}
	            $template = str_replace('%' . $placeHolder . '%', $val, $template);
			}
			return $template;
		}

		private function printEntry ($data) {
			$isFirstWriteToFile = !file_exists($this->currLogFilePath);
			$fpointer = fopen ($this->currLogFilePath, "ab");
			if ($isFirstWriteToFile) {
				chmod ($this->currLogFilePath, 0777);
				$padLnLength = ($this->wrapLimit !== -1)? $this->wrapLimit : $this->maxWrapLimit;
				for ($i=0; $i<$this->bannerLinesNo; $i++) {
					$data = str_repeat(" ", $padLnLength) . "\n" . $data;
				}
			}
			$result = fwrite ($fpointer, $data);
			fclose($fpointer);
			if ($result === false) {
				trigger_error(Logger::LOGS_DIR_NOT_WRITABLE, E_USER_ERROR);
			}
		}

		private function prepareEntry ($data) {
			$data = $this->strignify ($data);
			$data = $this->sanitizeString($data);
			$data = $this->fillEntryTemplate ($data);
			if ($this->wrapLimit !== -1) {
				$data = wordwrap($data, $this->wrapLimit, "\n", true);
			}
			$data = $this->fitIntoSize ($data, $this->maxFileSize);
			return $data;
		}

		private function doPrint ($data) {
			$data = $this->prepareEntry ($data);
			$this->provideRoomFor ($data);
			$this->printEntry ($data);
			$bannerStr = $this->prepareBanner ();
			$this->printBanner ($bannerStr);
		}

		private function provideRoomFor ($data) {
			if (file_exists($this->currLogFilePath)) {
				clearstatcache();
				$currFileSize = filesize($this->currLogFilePath);
				$availSize = $this->maxFileSize - $currFileSize;
				$requiredSize = mb_strlen($data, "8bit");
				if ($requiredSize > $availSize) {
					$this->handleOversize($requiredSize);
				}
			}
		}

		private function handleOversize ($oversizeAmt) {
			if ($this->operatingMode === $this->trimToken) {
				$this->trimBy ($oversizeAmt);
			} else {
				$this->makeNewVolume();
			}
		}

		private function trimBy ($trimAmt) {
			$trimmedBytes = 0;
			$deletionsNo = 0;
			$separator = chr (1);
			$trimMark = chr (8);
			$fcontent = file_get_contents($this->currLogFilePath);
			$entries = explode($separator, $fcontent);
			$fcontent = null;
			for ($i=0; $i<count($entries); $i++) {
				$entry = $entries[$i];
				if ($i == 0) {
					continue;
				}
				$trimmedBytes += mb_strlen($entry, "8bit");
				$deletionsNo++;
				if ($trimmedBytes >= $trimAmt) {
					break;
				}
			}
			array_splice($entries, 1, $deletionsNo);
			$entries[1] = $trimMark	. $entries[1];
			$newContent =  implode ($separator, $entries);
			$fpointer = fopen ($this->currLogFilePath, "wb");
			fwrite($fpointer, $newContent);
			fclose($fpointer);
		}

		private function makeNewVolume () {
			$bannerStr = $this->prepareBanner(true);
			$this->printBanner($bannerStr);
			$lastVolOnSite = $this->getLastVolOnSite ($this->currLogFilePath);
			rename($this->currLogFilePath, $this->currLogFilePath . "." . ++$lastVolOnSite);
			$fpointer = fopen ($this->currLogFilePath, "wb");
			fclose ($fpointer);
			$bannerStr = $this->prepareBanner ();
			$this->printBanner ($bannerStr);
		}

		private function getLastVolOnSite ($path) {
			$iterator = 0;
			do {
				$testPath =  $path . "." . ($iterator + 1);
				if (!file_exists ($testPath)) {
					break;
				}
				$iterator++;
			} while (true);
			return $iterator;
		}

		private function fitIntoSize ($str, $size) {
			$strSize = mb_strlen ($str, "8bit");
			$bannerSize = ($this->wrapLimit !== -1)? $this->bannerLinesNo * $this->wrapLimit :
				$this->bannerLinesNo * $this->maxWrapLimit;
			$availSize = $size - $bannerSize;
			if ($strSize > $availSize) {
				$str = substr($str, 0, $availSize);
				$str = trim ($str);
				$str .= "...";
			}
			return $str;
		}

        private function fillEntryTemplate ($paramValue) {
        	$template = $this->entryTemplate;
			$placeHolders = array ($this->yPH, $this->mPH, $this->dPH, $this->hPH, $this->minPH,
				$this->sPH, $this->callingClassNamePH, $this->contentPH);
			foreach ($placeHolders as $placeHolder) {
				$val = "";
				switch ($placeHolder) {
					case $this->yPH:
						$val = date("y");
						break;
					case $this->mPH:
						$val = date("m");
						break;
					case $this->dPH:
						$val = date("d");
						break;
					case $this->hPH:
						$val = date("H");
						break;
					case $this->minPH:
						$val = date("i");
						break;
					case $this->sPH:
						$val = date("s");
						break;
					case $this->callingClassNamePH:
						$val = $this->currCallingCls;
						break;
					case $this->contentPH:
						$val = $paramValue;
						break;
				}
	            $template = str_replace('%' . $placeHolder . '%', $val, $template);
			}
			$template .= "\n";
            return $template;
        }

		private function strignify ($arg) {
			// Log strings literally:
			if (is_string ($arg)) {
                if (strlen ($arg) == 0) {
                    return "<empty string>\n";
                }
                return "$arg\n";
            }
            // Log resources by their type and id:
			if (is_resource ($arg)) {
                return ucfirst (get_resource_type ($arg)) . " resource ($arg)\n";
			}
			// Use var_dump for anything else:
			ob_start();
			var_dump ($arg);
			$val = ob_get_contents();
			ob_end_clean();
			return $val;
		}

        private function getLogsPath () {
        	$segments = explode ("/", $this->configFolderPath);
        	array_pop($segments);
			$path = implode("/", $segments) . "/" . $this->logsDirName;
            $path = preg_replace('/\x5c{1,}/', '/', $path);
            $path = preg_replace('/\x2f$/', '', $path);
            return $path;
        }

        private function sanitizeString ($value) {
			if ($value !== "") {
				$value = preg_replace('/\x00/', "", $value); // remove NULL chars, if any
				$value = preg_replace('/\x08/', "", $value); // remove BACKSPACE chars, if any
				$value = preg_replace('/\x09/', str_repeat(" ", $this->tabSize), $value); // convert TABs
				$value = preg_replace('/\x0d\x0a/', "\n", $value); // convert Windows line ends
				$value = preg_replace('/\x0d/', "\n", $value); // convert Mac line ends
				$lines = explode("\n", $value);
				for ($i=0; $i<count($lines); $i++) {
					$lines[$i] = trim($lines[$i]);
				}
				$value = implode ("\n", $lines);
			}
			return $value;
        }

		private function array_searchRecursive ($needle, $haystack, $strict=false, $path=array()) {
			if (!is_array($haystack)) {
			    return false;
			}
			foreach ($haystack as $key => $val) {
				if (is_array ($val) && $subPath = $this->array_searchRecursive ($needle, $val, $strict,
					$path)) {
			        $path = array_merge ($path, array($key), $subPath);
			        return $path;
			    } elseif ((!$strict && $val == $needle) || ($strict && $val === $needle)) {
			        $path[] = $key;
			        return $path;
			    }
			}
			return false;
		}
	}
?>
Return current item: Logger_ciacob