自定义PHP系统异常处理类
001 <?php 002 003 // 自定义异常函数 004 set_exception_handler('handle_exception'); 005 006 // 自定义错误函数 007 set_error_handler('handle_error'); 008 009 /** 010 * 异常处理 011 * 012 * @param mixed $exception 异常对象 013 * @author blog.snsgou.com 014 */ 015 function handle_exception($exception) { 016 Error::exceptionError($exception); 017 } 018 019 /** 020 * 错误处理 021 * 022 * @param string $errNo 错误代码 023 * @param string $errStr 错误信息 024 * @param string $errFile 出错文件 025 * @param string $errLine 出错行 026 * @author blog.snsgou.com 027 */ 028 function handle_error($errNo, $errStr, $errFile, $errLine) { 029 if ($errNo) { 030 Error::systemError($errStr, false, true, false); 031 } 032 } 033 034 /** 035 * 系统错误处理 036 * 037 * @author blog.snsgou.com 038 */ 039 class Error { 040 041 public static function systemError($message, $show = true, $save = true, $halt = true) { 042 043 list($showTrace, $logTrace) = self::debugBacktrace(); 044 045 if ($save) { 046 $messageSave = '<b>' . $message . '</b><br /><b>PHP:</b>' . $logTrace; 047 self::writeErrorLog($messageSave); 048 } 049 050 if ($show) { 051 self::showError('system', "<li>$message</li>", $showTrace, 0); 052 } 053 054 if ($halt) { 055 exit(); 056 } else { 057 return $message; 058 } 059 } 060 061 /** 062 * 代码执行过程回溯信息 063 * 064 * @static 065 * @access public 066 */ 067 public static function debugBacktrace() { 068 $skipFunc[] = 'Error->debugBacktrace'; 069 070 $show = $log = ''; 071 $debugBacktrace = debug_backtrace(); 072 ksort($debugBacktrace); 073 foreach ($debugBacktrace as $k => $error) { 074 if (!isset($error['file'])) { 075 // 利用反射API来获取方法/函数所在的文件和行数 076 try { 077 if (isset($error['class'])) { 078 $reflection = new ReflectionMethod($error['class'], $error['function']); 079 } else { 080 $reflection = new ReflectionFunction($error['function']); 081 } 082 $error['file'] = $reflection->getFileName(); 083 $error['line'] = $reflection->getStartLine(); 084 } catch (Exception $e) { 085 continue; 086 } 087 } 088 089 $file = str_replace(SITE_PATH, '', $error['file']); 090 $func = isset($error['class']) ? $error['class'] : ''; 091 $func .= isset($error['type']) ? $error['type'] : ''; 092 $func .= isset($error['function']) ? $error['function'] : ''; 093 if (in_array($func, $skipFunc)) { 094 break; 095 } 096 $error['line'] = sprintf('%04d', $error['line']); 097 098 $show .= '<li>[Line: ' . $error['line'] . ']' . $file . '(' . $func . ')</li>'; 099 $log .= !empty($log) ? ' -> ' : ''; 100 $log .= $file . ':' . $error['line']; 101 } 102 return array($show, $log); 103 } 104 105 /** 106 * 异常处理 107 * 108 * @static 109 * @access public 110 * @param mixed $exception 111 */ 112 public static function exceptionError($exception) { 113 if ($exception instanceof DbException) { 114 $type = 'db'; 115 } else { 116 $type = 'system'; 117 } 118 if ($type == 'db') { 119 $errorMsg = '(' . $exception->getCode() . ') '; 120 $errorMsg .= self::sqlClear($exception->getMessage(), $exception->getDbConfig()); 121 if ($exception->getSql()) { 122 $errorMsg .= '<div class="sql">'; 123 $errorMsg .= self::sqlClear($exception->getSql(), $exception->getDbConfig()); 124 $errorMsg .= '</div>'; 125 } 126 } else { 127 $errorMsg = $exception->getMessage(); 128 } 129 $trace = $exception->getTrace(); 130 krsort($trace); 131 $trace[] = array('file' => $exception->getFile(), 'line' => $exception->getLine(), 'function' => 'break'); 132 $phpMsg = array(); 133 foreach ($trace as $error) { 134 if (!empty($error['function'])) { 135 $fun = ''; 136 if (!empty($error['class'])) { 137 $fun .= $error['class'] . $error['type']; 138 } 139 $fun .= $error['function'] . '('; 140 if (!empty($error['args'])) { 141 $mark = ''; 142 foreach ($error['args'] as $arg) { 143 $fun .= $mark; 144 if (is_array($arg)) { 145 $fun .= 'Array'; 146 } elseif (is_bool($arg)) { 147 $fun .= $arg ? 'true' : 'false'; 148 } elseif (is_int($arg)) { 149 $fun .= (defined('SITE_DEBUG') && SITE_DEBUG) ? $arg : '%d'; 150 } elseif (is_float($arg)) { 151 $fun .= (defined('SITE_DEBUG') && SITE_DEBUG) ? $arg : '%f'; 152 } else { 153 $fun .= (defined('SITE_DEBUG') && SITE_DEBUG) ? '\'' . htmlspecialchars(substr(self::clear($arg), 0, 10)) . (strlen($arg) > 10 ? ' ...' : '') . '\'' : '%s'; 154 } 155 $mark = ', '; 156 } 157 } 158 $fun .= ')'; 159 $error['function'] = $fun; 160 } 161 if (!isset($error['line'])) { 162 continue; 163 } 164 $phpMsg[] = array('file' => str_replace(array(SITE_PATH, '\\'), array('', '/'), $error['file']), 'line' => $error['line'], 'function' => $error['function']); 165 } 166 self::showError($type, $errorMsg, $phpMsg); 167 exit(); 168 } 169 170 /** 171 * 记录错误日志 172 * 173 * @static 174 * @access public 175 * @param string $message 176 */ 177 public static function writeErrorLog($message) { 178 179 return false; // 暂时不写入 180 181 $message = self::clear($message); 182 $time = time(); 183 $file = LOG_PATH . '/' . date('Y.m.d') . '_errorlog.php'; 184 $hash = md5($message); 185 186 $userId = 0; 187 $ip = get_client_ip(); 188 189 $user = '<b>User:</b> userId=' . intval($userId) . '; IP=' . $ip . '; RIP:' . $_SERVER['REMOTE_ADDR']; 190 $uri = 'Request: ' . htmlspecialchars(self::clear($_SERVER['REQUEST_URI'])); 191 $message = "<?php exit;?>\t{$time}\t$message\t$hash\t$user $uri\n"; 192 193 // 判断该$message是否在时间间隔$maxtime内已记录过,有,则不用再记录了 194 if (is_file($file)) { 195 $fp = @fopen($file, 'rb'); 196 $lastlen = 50000; // 读取最后的 $lastlen 长度字节内容 197 $maxtime = 60 * 10; // 时间间隔:10分钟 198 $offset = filesize($file) - $lastlen; 199 if ($offset > 0) { 200 fseek($fp, $offset); 201 } 202 if ($data = fread($fp, $lastlen)) { 203 $array = explode("\n", $data); 204 if (is_array($array)) 205 foreach ($array as $key => $val) { 206 $row = explode("\t", $val); 207 if ($row[0] != '<?php exit;?>') { 208 continue; 209 } 210 if ($row[3] == $hash && ($row[1] > $time - $maxtime)) { 211 return; 212 } 213 } 214 } 215 } 216 217 error_log($message, 3, $file); 218 } 219 220 /** 221 * 清除文本部分字符 222 * 223 * @param string $message 224 */ 225 public static function clear($message) { 226 return str_replace(array("\t", "\r", "\n"), " ", $message); 227 } 228 229 /** 230 * sql语句字符清理 231 * 232 * @static 233 * @access public 234 * @param string $message 235 * @param string $dbConfig 236 */ 237 public static function sqlClear($message, $dbConfig) { 238 $message = self::clear($message); 239 if (!(defined('SITE_DEBUG') && SITE_DEBUG)) { 240 $message = str_replace($dbConfig['database'], '***', $message); 241 //$message = str_replace($dbConfig['prefix'], '***', $message); 242 $message = str_replace(C('DB_PREFIX'), '***', $message); 243 } 244 $message = htmlspecialchars($message); 245 return $message; 246 } 247 248 /** 249 * 显示错误 250 * 251 * @static 252 * @access public 253 * @param string $type 错误类型 db,system 254 * @param string $errorMsg 255 * @param string $phpMsg 256 */ 257 public static function showError($type, $errorMsg, $phpMsg = '') { 258 global $_G; 259 260 $errorMsg = str_replace(SITE_PATH, '', $errorMsg); 261 ob_end_clean(); 262 $host = $_SERVER['HTTP_HOST']; 263 $title = $type == 'db' ? 'Database' : 'System'; 264 echo <<<EOT 265 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 266 <html> 267 <head> 268 <title>$host - $title Error</title> 269 <meta http-equiv="Content-Type" content="text/html; charset={$_G['config']['output']['charset']}" /> 270 <meta name="ROBOTS" content="NOINDEX,NOFOLLOW,NOARCHIVE" /> 271 <style type="text/css"> 272 <!-- 273 body { background-color: white; color: black; font: 9pt/11pt verdana, arial, sans-serif;} 274 #container {margin: 10px;} 275 #message {width: 1024px; color: black;} 276 .red {color: red;} 277 a:link {font: 9pt/11pt verdana, arial, sans-serif; color: red;} 278 a:visited {font: 9pt/11pt verdana, arial, sans-serif; color: #4e4e4e;} 279 h1 {color: #FF0000; font: 18pt "Verdana"; margin-bottom: 0.5em;} 280 .bg1 {background-color: #FFFFCC;} 281 .bg2 {background-color: #EEEEEE;} 282 .table {background: #AAAAAA; font: 11pt Menlo,Consolas,"Lucida Console"} 283 .info { 284 background: none repeat scroll 0 0 #F3F3F3; 285 border: 0px solid #aaaaaa; 286 border-radius: 10px 10px 10px 10px; 287 color: #000000; 288 font-size: 11pt; 289 line-height: 160%; 290 margin-bottom: 1em; 291 padding: 1em; 292 } 293 294 .help { 295 background: #F3F3F3; 296 border-radius: 10px 10px 10px 10px; 297 font: 12px verdana, arial, sans-serif; 298 text-align: center; 299 line-height: 160%; 300 padding: 1em; 301 } 302 303 .sql { 304 background: none repeat scroll 0 0 #FFFFCC; 305 border: 1px solid #aaaaaa; 306 color: #000000; 307 font: arial, sans-serif; 308 font-size: 9pt; 309 line-height: 160%; 310 margin-top: 1em; 311 padding: 4px; 312 } 313 --> 314 </style> 315 </head> 316 <body> 317 <div id="container"> 318 <h1>$title Error</h1> 319 <div class='info'>$errorMsg</div> 320 EOT; 321 if (!empty($phpMsg)) { 322 echo '<div class="info">'; 323 echo '<p><strong>PHP Debug</strong></p>'; 324 echo '<table cellpadding="5" cellspacing="1" width="100%" class="table"><tbody>'; 325 if (is_array($phpMsg)) { 326 echo '<tr class="bg2"><td>No.</td><td>File</td><td>Line</td><td>Code</td></tr>'; 327 foreach ($phpMsg as $k => $msg) { 328 $k++; 329 echo '<tr class="bg1">'; 330 echo '<td>' . $k . '</td>'; 331 echo '<td>' . $msg['file'] . '</td>'; 332 echo '<td>' . $msg['line'] . '</td>'; 333 echo '<td>' . $msg['function'] . '</td>'; 334 echo '</tr>'; 335 } 336 } else { 337 echo '<tr><td><ul>' . $phpMsg . '</ul></td></tr>'; 338 } 339 echo '</tbody></table></div>'; 340 } 341 echo <<<EOT 342 </div> 343 </body> 344 </html> 345 EOT; 346 exit(); 347 } 348 } 349 350 /** 351 * DB异常类 352 * 353 * @author blog.snsgou.com 354 */ 355 class DbException extends Exception { 356 357 protected $sql; 358 protected $dbConfig; // 当前数据库配置信息 359 360 public function __construct($message, $code = 0, $sql = '', $dbConfig = array()) { 361 $this->sql = $sql; 362 $this->dbConfig = $dbConfig; 363 parent::__construct($message, $code); 364 } 365 366 public function getSql() { 367 return $this->sql; 368 } 369 370 public function getDbConfig() { 371 return $this->dbConfig; 372 } 373 }
效果图: