self::ip(), 'date' => date('d.m.Y H:i:s'), 'func' => '(none)', 'version' => '(none)', 'error' => '' ); } /** * Clear the static properties, needed for testging */ public static function clear() { self::$start = null; self::$data = array(); self::$log = ''; } /** * Flush previous static properties and start again */ public static function reset($data = array()) { self::clear(); self::start($data); } /** * If $key is passed, creates/overwrites existing self::$data[$key] with $info * Otherwise, uses array_merge: * if $info has same string keys as self::$data, $info overwrites * if $info has same integer keys as self::$data, $info keys are renumbered * and appended to self::$data * * @param any $info * @param string $key optional, store the whole info under a $key in self::$data * @return void * @see self::start($data) */ public static function info($info, $key = null) { if(is_null($key)) { self::$data = array_merge(self::$data, $info); } else { self::$data[$key] = $info; } } /** * Log a message that will be displayed in the logs if the configuration * says so. * * @param string $message * @param int $verbosity */ public static function log($message, $verbosity = Logger::VERBOSE) { if(Configuration::get('log.verbosity') < $verbosity) { return; } self::$log .= $message."\n"; } /** * Allow stopping by overriding some self::$data values with $data param * - store the time lapse between start() and stop() calls in self::$data * * @param $data allow storing some info on stop * @return string saved log message filepath */ public static function stop($data = null) { if (!is_null($data)) { self::info($data); } $time = (microtime(true) - self::$start) * 1000; self::$data['time'] = round($time, 2).'ms'; $savedLogMessageFilePath = self::NO_FILE_LOGGED; if (Configuration::get('log.verbosity') > Logger::QUIET) { $savedLogMessageFilePath = self::saveLogMessageToFile(self::generateLogMessage()); } return $savedLogMessageFilePath; } /** * @return string */ private static function generateLogMessage() { $format = Configuration::get('log.format'); $patterns = array_map(function($p) { return "%$p%"; }, array_keys(self::$data)); $msg = str_replace($patterns, array_values(self::$data), $format)."\n"; return $msg . self::$log; } /** * * @param string $msg * @return string saved log message file path */ private static function saveLogMessageToFile($msg) { $mostRecentLogFileName = Configuration::get('log.file'); FileSystem::createFileOrDirIfNotExists($mostRecentLogFileName, true); if(self::isMostRecentLogFileFromYesterday()) { self::makeRoomForNewLogFile($mostRecentLogFileName); touch($mostRecentLogFileName); } file_put_contents($mostRecentLogFileName, $msg, FILE_APPEND | LOCK_EX); return $mostRecentLogFileName; } /** * @return bool */ private static function isMostRecentLogFileFromYesterday() { $mostRecentLogFileName = Configuration::get('log.file'); return filemtime($mostRecentLogFileName) < strtotime("midnight"); } /** * Use config log filename as pattern. We want to save the latest log * in a file named after config. But if there is another file with * same name (for example the one for yesterday's log), then we want * to append a number to the end of the file, which basically is the * number of days the log dates from today. * We only keep 10 day logs from (0) to 9. * * glob: think of the * as the pcre equivalent of .* and ? as the pcre equivalent of the dot (.) */ private static function makeRoomForNewLogFile() { $files = self::getExistingNumberedLogFilesArrayDesc(); $files[] = Configuration::get('log.file'); self::renameExsitingLogFilesToHigherCounts($files); self::deleteTenDaysOlderLogFiles(); } /** * @param array $files */ private static function renameExsitingLogFilesToHigherCounts($files) { $oldLogFilesCount = count($files); $newOldestLogFileName = self::getOldestLogFileNameIncrementedByOne($files[0]); array_unshift($files, $newOldestLogFileName); for($i = 0; $i < $oldLogFilesCount; ++$i) { rename($files[$i + 1], $files[$i]); } } /** * Delete all files with a number bigger than 9 */ private static function deleteTenDaysOlderLogFiles() { $mostRecentLogFileName = Configuration::get('log.file'); $files = glob($mostRecentLogFileName.'.??'); array_map('unlink', $files); } /** * Get the list of files matching Configuration::get('log.file') * with some number appended to the file name. Order them by that * number in desc order. * * @return array */ public static function getExistingNumberedLogFilesArrayDesc() { $logFileBaseName = Configuration::get('log.file'); $files = glob("$logFileBaseName.?"); natsort($files); $files = array_reverse($files); return $files; } /** * * @param string $highestFileName */ private static function getOldestLogFileNameIncrementedByOne($highestFileName) { $logFileBaseName = Configuration::get('log.file'); $next = intval(substr($highestFileName, -1)) + 1; return "$logFileBaseName.$next"; } public static function getData() { return self::$data; } public static function getLastLogs($offset = null) { $file = Configuration::get('log.file'); if(! file_exists($file)) { return self::NO_LOG_YET_MSG; } $f = fopen($file, 'r'); $len = 8192; fseek($f, 0, SEEK_END); $size = ftell($f); if(is_null($offset) || $offset > $size) { $offset = $size - $len; } $offset = max(0, $offset); fseek($f, $offset); // remove the first line that may be incomplete $buffer = fread($f, $len); $buffer = explode("\n", $buffer); array_shift($buffer); $buffer = implode("\n", $buffer); // continue reading until the end of the file while(! feof($f)) { $buffer .= fread($f, $len); } fclose($f); return $buffer; } }