自己实现的一个PHP错误异常日志捕获类
特性:
输出到控制台/文件/自定义方法
可以通过错误等级进行过滤,有总开关可以关闭一切错误输出
易用
废话不多说,上代码:
<?php
/**
* Created by PhpStorm.
* User: tim
* Date: 2019/1/30
* Time: 15:41
* 功能:日志函数,用与捕获错误或者输出信息到指定的输出流。
* 用法:程序的入口入口函数调用,index.php/init.php等,生命周期内全局有效。
* 原理:
* 1. set_error_handler() 函数设置用户自定义的错误处理函数。
* 该函数用于创建运行时期间的用户自己的错误处理方法。
* 该函数会返回旧的错误处理程序,若失败,则返回 null。
* 2. set_exception_handler() 函数设置用户自定义的异常处理函数。
* 该函数用于创建运行时期间的用户自己的异常处理方法。
* 该函数会返回旧的异常处理程序,若失败,则返回 null。
* 3. debug_backtrace() 函数用于获取函数调用轨迹[发现不好用]
* 4. error_get_last() 获取错误信息 [发现不好用]
* 5. register_shutdown_function() 程序运行结束回调函数 [发现没嘛用]
* 6. libxml_get_last_error() 获取错误信息 [发现不好用]
*
* 函数组织形式,依据日志记录流程自顶部向下分层:
* 接口层: 设置是否输出、输出的错误级别、输出到哪里、手动输出。
* 错误捕捉层:对于错误的捕捉、对于异常的捕捉
* 时间格式化层:为错误信息附上时间戳
* 输出过滤层:再此处实现错误级别过滤
* 输出抽象层:在此层实现总开关和输出函数实例调用
* 输出层:输出方式的具体实现
*
* 其他说明:
* 1. 为了注册错误异常处理函数,因此有一个构造函数
* 2. 为了Log::log()函数可以被随处调用,因此大多数函数和变量为static
* 3. 为了配置可以链式调用,因此不使用静态方法(强迫症)
* 4. 错误类型用中文阅读性不好,建议换成英文
*/
class Log
{
private static $logfile = __DIR__.'/log_password.txt';
private static $debug = true;
private static $out_level = 30719;
private static $out_method_function='Log::out_console';
private static $errmap = array(
'0' => '错误要了命了 ',
'1' => '致命运行时错误',
'2' => '运行时警告 ',
'4' => '编译语法错误 ',
'8' => '运行时通知 ',
'16' => '初始化致命错误',
'32' => '初始化警告 ',
'64' => '致命编译错误 ',
'128'=> '编译时警告 ',
'256'=> '用户自定义错误',
'512'=> '用户自定义警告',
'1024'=> '用户自定义通知',
'2048'=> 'PHP兼容性建议 ',
'4096'=> '可捕捉致命错误',
'8192'=> '运行时通知 ',
'16384'=>'用户产生的警告',
'30719'=>'其他警告WARNING'
);
public function __construct(){
@set_error_handler(array(&$this, 'error_handler'));
@set_exception_handler(array(&$this, 'exception_handler'));
// @register_shutdown_function(array(&$this,'shutdown_handler')); // 似乎用不到,有错没错,程序运行结束就调用
$this->init();
}
// 还原配置更改
public function init(){
$this->is_debug(true)->out_level(30719)->out_method('Log::out_console');
return $this;
}
// 通过任意实例更改是否输出配置
public function is_debug($is_debug=true){
self::$debug=$is_debug;
return $this;
}
// 通过
public function out_level($level=30719){
self::$out_level=$level;
return $this;
}
public function out_method($out_method=null){
$out_method = is_null($out_method)?self::$out_method_function:$out_method;
self::$out_method_function = $out_method;
return $this;
}
// 手动输出信息 类似与 console.log()
public static function log(...$strs){
$str = '';
foreach($strs as $s){
if (is_array($s) || is_object($s)) $s = json_encode($s,256);
$str .= $s;
}
self::out(self::format($str.' '.self::trace()));
return self::$debug;
}
//E_STRICT类型
public function error_handler($type, $message, $file, $line)
{
// echo "出错";
$msg = self::format($message.' '.$file.' '.$line,self::$errmap[$type]);
self::whether_out($msg,$type);
}
public function exception_handler($error){
// echo "发生异常";
$msg = self::format($error->getMessage().' '.$error->getFile().' '.$error->getLine().' ',
self::$errmap[$error->getCode()]);
self::whether_out($msg,$error->getCode());
}
public function shutdown_handler(...$args){
// echo "运行结束";
print_r(error_get_last());
print_r(debug_backtrace());
print_r($args);
}
// 是否输出,总开关开 && 错误级别高于阈值
private static function whether_out($str,$type=30719){
if(intval($type)<=self::$out_level)
self::out($str);
}
// 重写此函数可以写到Socket/Db/http/file
private static function out($str){
if(self::$debug) call_user_func(self::$out_method_function,$str);
}
// 输出到文件
private static function out_file($str){
file_put_contents(self::$logfile,$str, FILE_APPEND);
}
private static function out_socket($str){
// TODO
}
private static function out_db($str){
// TODO
}
private static function out_browser($str){
echo "<script>console.log('$str')</script>";
}
private static function out_console($str){
echo $str.PHP_EOL;
}
// 调用轨迹
public static function trace(){
$separator = " ";
$trace = debug_backtrace();
$tra='';
$count=0;
foreach ($trace as $index => $step) {
$call = $step['class'].'::'.$step['function'];
if($call=='Log::trace'
|| $call=='Log::format' || ++$count>2)continue;
$tra .= $step['file'];
$tra .= ':'.$step['line'].'行'.$separator;
// $tra .= isset($step['class'])?(' '.$step['class'].'::'):'';
// $tra .= isset($step['function'])?(''.$step['function'].'()'):'';
// $tra .= isset($step['args'][0])?json_encode($step['args']):'';
// $tra .= ') : ';
}
return $tra;
}
// 附上时间和错误类型
private static function format($log='msg null',$type='INFO '){
$separator = " => ";
$info = date('Y-m-d H-i-s ',time())
.@sprintf('%06d',1000000*(floatval(microtime())-floor(floatval(microtime()))))
.$separator.$log.PHP_EOL;
return $type.' '.$info;
}
}
代码细节没耐心看?直接来个Demo
-
默认直接输入到控制台
<?php require 'Log.php'; $log = new Log(); 1/0; //$log->is_debug(false)->out_method('Log::out_file'); $t=array(); print($t[0]); Log::log("hahahahahah"); function zzzzh($a,$b){ Log::log("PHP是什么意思?"); echo "in zzzzh".$a.$b; } $log->is_debug(true); try{ zzzzh(2345); }catch(Exception $e){ print_r($e); }
-
演示总开关,更改输出方式
<?php require 'Log.php'; $log = new Log(); 1/0; $log->is_debug(false)->out_method('Log::out_file'); $t=array(); print($t[0]); Log::log("hahahahahah"); function zzzzh($a,$b){ Log::log("PHP是什么意思?"); echo "in zzzzh".$a.$b; } $log->is_debug(true); try{ zzzzh(2345); }catch(Exception $e){ print_r($e); }