Files
framework/library/think/slog.php
2015-12-08 09:57:32 +08:00

473 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* github: https://github.com/luofei614/SocketLog
* @author luofei614<weibo.com/luofei614>
*/
namespace think;
class slog
{
public static $start_time=0;
public static $start_memory=0;
public static $port=1116;//SocketLog 服务的http的端口号
public static $log_types=array('log','info','error','warn','table','group','groupCollapsed','groupEnd','alert');
protected static $_instance;
protected static $config=array(
'enable'=>true, //是否记录日志的开关
'host'=>'localhost',
//是否显示利于优化的参数,如果允许时间,消耗内存等
'optimize'=>false,
'show_included_files'=>false,
'error_handler'=>false,
//日志强制记录到配置的client_id
'force_client_id'=>'',
//限制允许读取日志的client_id
'allow_client_ids'=>array()
);
protected static $logs=array();
protected static $css=array(
'sql'=>'color:#009bb4;',
'sql_warn'=>'color:#009bb4;font-size:14px;',
'error_handler'=>'color:#f4006b;font-size:14px;',
'page'=>'color:#40e2ff;background:#171717;'
);
public static function __callStatic($method,$args)
{
if(in_array($method,self::$log_types))
{
array_unshift($args,$method);
return call_user_func_array(array(self::getInstance(),'record'),$args);
}
}
public static function sql($sql,$link)
{
if(is_object($link) && 'mysqli'==get_class($link))
{
return self::mysqlilog($sql,$link);
}
if(is_resource($link) && ('mysql link'==get_resource_type($link) || 'mysql link persistent'==get_resource_type($link)))
{
return self::mysqllog($sql,$link);
}
if(is_object($link) && 'PDO'==get_class($link))
{
return self::pdolog($sql,$link);
}
throw new Exception('SocketLog can not support this database link');
}
public static function big($log)
{
self::log($log,'font-size:20px;color:red;');
}
public static function trace($msg,$trace_level=2,$css='')
{
if(!self::check())
{
return ;
}
self::groupCollapsed($msg,$css);
$traces=debug_backtrace(false);
$traces=array_reverse($traces);
$max=count($traces)-$trace_level;
for($i=0;$i<$max;$i++){
$trace=$traces[$i];
$fun=isset($trace['class'])?$trace['class'].'::'.$trace['function']:$trace['function'];
$file=isset($trace['file'])?$trace['file']:'unknown file';
$line=isset($trace['line'])?$trace['line']:'unknown line';
$trace_msg='#'.$i.' '.$fun.' called at ['.$file.':'.$line.']';
if(!empty($trace['args'])){
self::groupCollapsed($trace_msg);
self::log($trace['args']);
self::groupEnd();
}else{
self::log($trace_msg);
}
}
self::groupEnd();
}
public static function mysqlilog($sql,$db)
{
if(!self::check())
{
return ;
}
$css=self::$css['sql'];
if(preg_match('/^SELECT /i', $sql))
{
//explain
$query = @mysqli_query($db,"EXPLAIN ".$sql);
$arr=mysqli_fetch_array($query);
self::sqlexplain($arr,$sql,$css);
}
self::sqlwhere($sql,$css);
self::trace($sql,2,$css);
}
public static function mysqllog($sql,$db)
{
if(!self::check())
{
return ;
}
$css=self::$css['sql'];
if(preg_match('/^SELECT /i', $sql))
{
//explain
$query = @mysql_query("EXPLAIN ".$sql,$db);
$arr=mysql_fetch_array($query);
self::sqlexplain($arr,$sql,$css);
}
//判断sql语句是否有where
self::sqlwhere($sql,$css);
self::trace($sql,2,$css);
}
public static function pdolog($sql,$pdo)
{
if(!self::check())
{
return ;
}
$css=self::$css['sql'];
if(preg_match('/^SELECT /i', $sql))
{
//explain
try {
$obj=$pdo->query( "EXPLAIN ".$sql);
if(is_object($obj) && method_exists($obj,'fetch'))
{
$arr=$obj->fetch(\PDO::FETCH_ASSOC);
self::sqlexplain($arr,$sql,$css);
}
} catch (Exception $e) {
}
}
self::sqlwhere($sql,$css);
self::trace($sql,2,$css);
}
private static function sqlexplain($arr,&$sql,&$css)
{
$arr = array_change_key_case($arr, CASE_LOWER);
if(false!==strpos($arr['extra'],'Using filesort'))
{
$sql.=' <---################[Using filesort]';
$css=self::$css['sql_warn'];
}
if(false!==strpos($arr['extra'],'Using temporary'))
{
$sql.=' <---################[Using temporary]';
$css=self::$css['sql_warn'];
}
}
private static function sqlwhere(&$sql,&$css)
{
//判断sql语句是否有where
if(preg_match('/^UPDATE |DELETE /i',$sql) && !preg_match('/WHERE.*(=|>|<|LIKE|IN)/i',$sql))
{
$sql.='<---###########[NO WHERE]';
$css=self::$css['sql_warn'];
}
}
/**
* 接管报错
*/
public static function registerErrorHandler()
{
if(!self::check())
{
return ;
}
set_error_handler(array(__CLASS__,'error_handler'));
register_shutdown_function(array(__CLASS__,'fatalError'));
}
public static function error_handler($errno, $errstr, $errfile, $errline)
{
switch($errno){
case E_WARNING: $severity = 'E_WARNING'; break;
case E_NOTICE: $severity = 'E_NOTICE'; break;
case E_USER_ERROR: $severity = 'E_USER_ERROR'; break;
case E_USER_WARNING: $severity = 'E_USER_WARNING'; break;
case E_USER_NOTICE: $severity = 'E_USER_NOTICE'; break;
case E_STRICT: $severity = 'E_STRICT'; break;
case E_RECOVERABLE_ERROR: $severity = 'E_RECOVERABLE_ERROR'; break;
case E_DEPRECATED: $severity = 'E_DEPRECATED'; break;
case E_USER_DEPRECATED: $severity = 'E_USER_DEPRECATED'; break;
case E_ERROR: $severity = 'E_ERR'; break;
case E_PARSE: $severity = 'E_PARSE'; break;
case E_CORE_ERROR: $severity = 'E_CORE_ERROR'; break;
case E_COMPILE_ERROR: $severity = 'E_COMPILE_ERROR'; break;
case E_USER_ERROR: $severity = 'E_USER_ERROR'; break;
default: $severity= 'E_UNKNOWN_ERROR_'.$errno; break;
}
$msg="{$severity}: {$errstr} in {$errfile} on line {$errline} -- SocketLog error handler";
self::trace($msg,2,self::$css['error_handler']);
}
public static function fatalError()
{
// 保存日志记录
if ($e = error_get_last())
{
self::error_handler($e['type'],$e['message'],$e['file'],$e['line']);
self::sendLog();//此类终止不会调用类的 __destruct 方法所以此处手动sendLog
}
}
public static function getInstance()
{
if (self::$_instance === null) {
self::$_instance = new self();
}
return self::$_instance;
}
protected static function _log($type,$logs,$css='')
{
self::getInstance()->record($type,$logs,$css);
}
protected static function check()
{
if(!self::getConfig('enable'))
{
return false;
}
$tabid=self::getClientArg('tabid');
//是否记录日志的检查
if(!$tabid && !self::getConfig('force_client_id'))
{
return false;
}
//用户认证
$allow_client_ids=self::getConfig('allow_client_ids');
if(!empty($allow_client_ids))
{
if (!$tabid && in_array(self::getConfig('force_client_id'), $allow_client_ids)) {
return true;
}
$client_id=self::getClientArg('client_id');
if(!in_array($client_id,$allow_client_ids))
{
return false;
}
}
return true;
}
protected static function getClientArg($name)
{
static $args=array();
$key = 'HTTP_USER_AGENT';
if (isset($_SERVER['HTTP_SOCKETLOG'])) {
$key = 'HTTP_SOCKETLOG';
}
if(!isset($_SERVER[$key]))
{
return null;
}
if(empty($args))
{
if(!preg_match('/SocketLog\((.*?)\)/',$_SERVER[$key],$match))
{
$args=array('tabid'=>null);
return null;
}
parse_str($match[1],$args);
}
if(isset($args[$name]))
{
return $args[$name];
}
return null;
}
//设置配置
public static function config($config)
{
$config=array_merge(self::$config,$config);
self::$config=$config;
if(self::check())
{
self::getInstance(); //强制初始化SocketLog实例
if($config['optimize'])
{
self::$start_time=microtime(true);
self::$start_memory=memory_get_usage();
}
if($config['error_handler'])
{
self::registerErrorHandler();
}
}
}
//获得配置
public static function getConfig($name)
{
if(isset(self::$config[$name]))
return self::$config[$name];
return null;
}
//记录日志
public function record($type,$msg='',$css='')
{
if(!self::check())
{
return ;
}
self::$logs[]=array(
'type'=>$type,
'msg'=>$msg,
'css'=>$css
);
}
/**
* @param null $host - $host of socket server
* @param string $message - 发送的消息
* @param string $address - 地址
* @return bool
*/
public static function send($host,$message='',$address='/')
{
$url='http://'.$host.':'.self::$port.$address;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $message);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$headers=array(
"Content-Type: application/json;charset=UTF-8"
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);//设置header
$txt = curl_exec($ch);
return true;
}
public static function sendLog()
{
if(!self::check())
{
return ;
}
$time_str='';
$memory_str='';
if(self::$start_time)
{
$runtime=microtime(true)-self::$start_time;
$reqs=number_format(1/$runtime,2);
$time_str="[运行时间:{$runtime}s][吞吐率:{$reqs}req/s]";
}
if(self::$start_memory)
{
$memory_use=number_format(memory_get_usage()-self::$start_memory/1024,2);
$memory_str="[内存消耗:{$memory_use}kb]";
}
if(isset($_SERVER['HTTP_HOST']))
{
$current_uri=$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
}
else
{
$current_uri="cmd:".implode(' ',$_SERVER['argv']);
}
array_unshift(self::$logs,array(
'type'=>'group',
'msg'=>$current_uri.$time_str.$memory_str,
'css'=>self::$css['page']
));
if(self::getConfig('show_included_files'))
{
self::$logs[]=array(
'type'=>'groupCollapsed',
'msg'=>'included_files',
'css'=>''
);
self::$logs[]=array(
'type'=>'log',
'msg'=>implode("\n",get_included_files()),
'css'=>''
);
self::$logs[]=array(
'type'=>'groupEnd',
'msg'=>'',
'css'=>'',
);
}
self::$logs[]=array(
'type'=>'groupEnd',
'msg'=>'',
'css'=>'',
);
$tabid=self::getClientArg('tabid');
if(!$client_id=self::getClientArg('client_id'))
{
$client_id='';
}
if($force_client_id=self::getConfig('force_client_id'))
{
$client_id=$force_client_id;
}
$logs=array(
'tabid'=>$tabid,
'client_id'=>$client_id,
'logs'=>self::$logs,
'force_client_id'=>$force_client_id,
);
$msg=@json_encode($logs);
$address='/'.$client_id; //将client_id作为地址 server端通过地址判断将日志发布给谁
self::send(self::getConfig('host'),$msg,$address);
}
public function __destruct()
{
self::sendLog();
}
}