|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * 日志写入类 |
| 4 | + * @author AizuYan |
| 5 | + |
| 6 | + */ |
| 7 | +class Log { |
| 8 | + // 日志消息等级 |
| 9 | + const EMERGENCY = LOG_EMERG; // 0 |
| 10 | + const ALERT = LOG_ALERT; // 1 |
| 11 | + const CRITICAL = LOG_CRIT; // 2 |
| 12 | + const ERROR = LOG_ERR; // 3 |
| 13 | + const WARNING = LOG_WARNING; // 4 |
| 14 | + const NOTICE = LOG_NOTICE; // 5 |
| 15 | + const INFO = LOG_INFO; // 6 |
| 16 | + const STRACE = 7; |
| 17 | + const DEBUG = 8; |
| 18 | + /** |
| 19 | + * @var string 日志记录时间的格式 |
| 20 | + */ |
| 21 | + public static $timestamp = 'Y-m-d H:i:s'; |
| 22 | + /** |
| 23 | + * @var boolean 是否在运行过程中马上记录的标志 |
| 24 | + */ |
| 25 | + public static $writeOnAdd = false; |
| 26 | + /** |
| 27 | + * @var Log 单例模式的容器 |
| 28 | + */ |
| 29 | + protected static $_instance = null; |
| 30 | + /** |
| 31 | + * 获取单例模式,并将Log::write方法加入脚本停止时执行的函数列表 |
| 32 | + * |
| 33 | + * $log = Log::instance(); |
| 34 | + * |
| 35 | + * @return Log |
| 36 | + */ |
| 37 | + public static function instance() { |
| 38 | + if (Log::$_instance === null) { |
| 39 | + Log::$_instance = new Log; |
| 40 | + register_shutdown_function(array(Log::$_instance, 'write')); |
| 41 | + } |
| 42 | + return Log::$_instance; |
| 43 | + } |
| 44 | + |
| 45 | + /** |
| 46 | + * @var array 日志消息数组 |
| 47 | + */ |
| 48 | + protected $_messages = array(); |
| 49 | + |
| 50 | + /** |
| 51 | + * @var array 保存写日志(Logwriter)对象的数组 |
| 52 | + */ |
| 53 | + protected $_writers = array(); |
| 54 | + |
| 55 | + /** |
| 56 | + * 添加一个写日志对象到日志对象(Log)中,并设置该写日志对象记录哪些错误等级 |
| 57 | + * |
| 58 | + * $log->attach($writer); |
| 59 | + * |
| 60 | + * @param object 写日志对象 |
| 61 | + * @param mixed 写日志对象要记录的错误的等级数组,或者要记录等最大等级 |
| 62 | + * @param integer 如果前面的$levels不是数组,这个参数有效,表示最小的记录等级 |
| 63 | + * @return Log |
| 64 | + */ |
| 65 | + public function attach(Logwriter $writer, $levels = array(), $min_level = 0) { |
| 66 | + if ( ! is_array($levels)) { |
| 67 | + $levels = range($min_level, $levels); |
| 68 | + } |
| 69 | + //将写日志对象和该对象要记录的等级存入日志对象中 |
| 70 | + $this->_writers["{$writer}"] = array ( |
| 71 | + 'object' => $writer, |
| 72 | + 'levels' => $levels |
| 73 | + ); |
| 74 | + return $this; |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * 从日志对象中去除一个写日志对象. The same writer object must be used. |
| 79 | + * |
| 80 | + * $log->detach($writer); |
| 81 | + * |
| 82 | + * @param object 写日志(Log_Writer)实例 |
| 83 | + * @return Log |
| 84 | + */ |
| 85 | + public function detach(Logger $writer) { |
| 86 | + // 移除一个“写日志”对象 |
| 87 | + unset($this->_writers["{$writer}"]); |
| 88 | + return $this; |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * 添加一组日志信息到日志对象中 |
| 93 | + * |
| 94 | + * $log->add(Log::ERROR, 'Could not locate user: :user', array( |
| 95 | + * ':user' => $username, |
| 96 | + * )); |
| 97 | + * |
| 98 | + * @param string 这组日志对象的错误等级 |
| 99 | + * @param string 日志消息 |
| 100 | + * @param array 记录错误位置信息 |
| 101 | + * |
| 102 | + * array('file'=>__FILE__,'line'=>'__LINE__'); |
| 103 | + * |
| 104 | + * @return Log |
| 105 | + */ |
| 106 | + public function add($level, $message, array $additional=null) { |
| 107 | + // Create a new message and timestamp it |
| 108 | + $this->_messages[] = array ( |
| 109 | + 'time' => date(Log::$timestamp, time()), |
| 110 | + 'level' => $level, |
| 111 | + 'body' => $message, |
| 112 | + 'file' => isset($additional['file']) ? $additional['file'] : NULL, |
| 113 | + 'line' => isset($additional['line']) ? $additional['line'] : NULL, |
| 114 | + ); |
| 115 | + if (Log::$writeOnAdd) { |
| 116 | + $this->write(); |
| 117 | + } |
| 118 | + return $this; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * 记录并清理所有的日志信息 |
| 123 | + * |
| 124 | + * $log->write(); |
| 125 | + * |
| 126 | + * @return 无 |
| 127 | + */ |
| 128 | + public function write() { |
| 129 | + if (empty($this->_messages)) { |
| 130 | + // 无日志消息返回 |
| 131 | + return; |
| 132 | + } |
| 133 | + // 将消息保存至私有变量中 |
| 134 | + $messages = $this->_messages; |
| 135 | + // 清空消息数组 |
| 136 | + $this->_messages = array(); |
| 137 | + foreach ($this->_writers as $writer) { |
| 138 | + if (empty($writer['levels'])) { |
| 139 | + // 如果该“写日志”对象的levels数组为空,该“写日志”对象记录所有级别的日志 |
| 140 | + $writer['object']->write($messages); |
| 141 | + } else { |
| 142 | + // 对消息进行过滤,记录需要改Logwriter记录的日志信息到数组$filtered |
| 143 | + $filtered = array(); |
| 144 | + foreach ($messages as $message) { |
| 145 | + if (in_array($message['level'], $writer['levels'])) { |
| 146 | + $filtered[] = $message; |
| 147 | + } |
| 148 | + } |
| 149 | + // 写入过滤后的日志到该Logwriter对象的目录 |
| 150 | + $writer['object']->write($filtered); |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +/** |
| 157 | + * 系统日志记录执行记录操作的类库,并把他们存储为/YYYY/MM/DD.php格式 |
| 158 | + */ |
| 159 | +class Logwriter { |
| 160 | + const FILE_EXT = '.php'; |
| 161 | + //安全信息,用于获取日志时的验证(我这里是“欢迎来到日志的世界”) |
| 162 | + const FILE_SECURITY = '<?php ($_GET[\'p\'] && md5($_GET[\'p\']) == \'8b812956650da4ed2199a4463f55194b\') or die(\'No direct script access.\');'; |
| 163 | + /** |
| 164 | + * @var string 保存日志的目录 |
| 165 | + */ |
| 166 | + protected $_directory; |
| 167 | + |
| 168 | + /** |
| 169 | + * 创建一个新的日志写操作实例 |
| 170 | + * |
| 171 | + * $writer = new Logwriter($directory); |
| 172 | + * |
| 173 | + * @param string 当前实例存储日志的目录 |
| 174 | + * @return 无 |
| 175 | + */ |
| 176 | + public function __construct($directory) { |
| 177 | + if (!is_dir($directory) || !is_writable($directory)) { |
| 178 | + try{ |
| 179 | + mkdir($directory,true); |
| 180 | + chmod($directory, 0777); |
| 181 | + }catch(Exception $e){ |
| 182 | + |
| 183 | + } |
| 184 | + } |
| 185 | + // 将保存日志的目录路径放入对象环境中 |
| 186 | + $this->_directory = realpath($directory).DIRECTORY_SEPARATOR; |
| 187 | + } |
| 188 | + |
| 189 | + /** |
| 190 | + * 将messages数组中的每一组日志信息存储到文件中,格式为/YYYY/MM/DD.php |
| 191 | + * example:2014/11/18.php 表示2014年11月18日的日志文件 |
| 192 | + * |
| 193 | + * $writer->write($messages); |
| 194 | + * |
| 195 | + * @param array 要保存的日志信息 |
| 196 | + * @return void |
| 197 | + */ |
| 198 | + public function write(array $messages) { |
| 199 | + // “年”这一级目录 |
| 200 | + $directory = $this->_directory.date('Y'); |
| 201 | + if ( ! is_dir($directory)) { |
| 202 | + // 如果“年”级目录不存在,创建 |
| 203 | + mkdir($directory, 02777); |
| 204 | + // 设置目录权限(must be manually set to fix umask issues) |
| 205 | + chmod($directory, 02777); |
| 206 | + } |
| 207 | + |
| 208 | + // “月”这一级目录 |
| 209 | + $directory .= DIRECTORY_SEPARATOR.date('m'); |
| 210 | + if ( ! is_dir($directory)) { |
| 211 | + // 如果“月”级目录不存在,创建 |
| 212 | + mkdir($directory, 02777); |
| 213 | + // 设置权限 (must be manually set to fix umask issues) |
| 214 | + chmod($directory, 02777); |
| 215 | + } |
| 216 | + |
| 217 | + // 要写入的文件 |
| 218 | + $filename = $directory.DIRECTORY_SEPARATOR.date('d').self::FILE_EXT; |
| 219 | + if ( ! file_exists($filename)) { |
| 220 | + // 如果不存在日志文件,创建,并在记录日志开始写入安全验证程序 |
| 221 | + file_put_contents($filename, self::FILE_SECURITY.' ?>'.PHP_EOL); |
| 222 | + // 设置文件权限为所有用户可读可写 |
| 223 | + chmod($filename, 0666); |
| 224 | + } |
| 225 | + |
| 226 | + foreach ($messages as $message) { |
| 227 | + // 循环日志写信数组,写入每一条日志 |
| 228 | + file_put_contents($filename, PHP_EOL.$message['time'].' --- '.$message['level'].': '.$message['body'].' [at file]:'.$message['file'].' [at line]:'.$message['line'], FILE_APPEND); |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + /** |
| 233 | + * 魔术方法,生成对象的唯一标识 |
| 234 | + * |
| 235 | + * @return void |
| 236 | + */ |
| 237 | + public function __toString() { |
| 238 | + return spl_object_hash($this); |
| 239 | + } |
| 240 | +} |
0 commit comments