From 19d675ab8ee12d09ee1fd7b7c81d007f43e084e5 Mon Sep 17 00:00:00 2001 From: "upfy@qq.com" Date: Thu, 7 Jan 2016 21:43:24 +0800 Subject: [PATCH 01/14] merge --- demo.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demo.php b/demo.php index 2571a0f..dc0c8c2 100644 --- a/demo.php +++ b/demo.php @@ -22,7 +22,9 @@ slog('msg','log','color:red;font-size:20px;');//自定义日志的样式,第三个参数为css样式 //调试sql +/* $link=mysql_connect( 'localhost:3306' , 'root' , '123456' , true ) ; mysql_select_db('kuaijianli',$link); $sql="DELETE * FROM `uxxxser`"; slog($sql,$link); + */ From 540e64ca42f68fd7e65978af8f060af0a2ff7266 Mon Sep 17 00:00:00 2001 From: "upfy@qq.com" Date: Tue, 12 Jan 2016 00:28:11 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E5=85=81=E8=AE=B8=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=E5=BC=BA=E5=88=B6id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/slog.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/php/slog.php b/php/slog.php index 77473da..22fd094 100644 --- a/php/slog.php +++ b/php/slog.php @@ -267,7 +267,7 @@ protected static function check() if(!empty($allow_client_ids)) { //通过数组交集得出授权强制推送的client_id - self::$_allowForceClientIds = array_intersect((array)$allow_client_ids, (array)self::getConfig('force_client_ids')); + self::$_allowForceClientIds = array_intersect($allow_client_ids, self::getConfig('force_client_ids')); if (!$tabid && count(self::$_allowForceClientIds)) { return true; } @@ -278,6 +278,10 @@ protected static function check() return false; } } + else + { + self::$_allowForceClientIds = self::getConfig('force_client_ids'); + } return true; } @@ -316,6 +320,10 @@ protected static function getClientArg($name) public static function config($config) { $config=array_merge(self::$config,$config); + if(isset($config['force_client_id'])){ + //兼容老配置 + $config['force_client_ids']=array_merge($config['force_client_ids'],array($config['force_client_id'])); + } self::$config=$config; if(self::check()) { From 713d94542742bf5764f4a96d6272aba0ab1198ed Mon Sep 17 00:00:00 2001 From: "upfy@qq.com" Date: Tue, 12 Jan 2016 00:43:52 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=95=99=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 026329d..018f5e1 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,11 @@ SocketLog适合Ajax调试和API调试, 举一个常见的场景,用SocketLog - 提示:另一种更简单的方法,因为OneThink每次执行完sql语句都会调用$this->debug, 所以我们可以把slog($this->queryStr,$this->_linkID); 直接写在 Db.class.php文件的debug方法中。 这样不管是mysqli还是mysql驱动都有效。 +## 视频教程 + [http://edu.yuantuan.com/course/198](http://edu.yuantuan.com/course/198) + +感谢猿团的张盛翔(诺墨)提供教程。 + ##About Me * Author: @luofei614 新浪微博:http://weibo.com/luofei614 * 三生社群CTO,ThinkPHP核心开发者之一,待过新浪云计算 From c1f3e2ad94e192982670d636eb954b9c014045ff Mon Sep 17 00:00:00 2001 From: "upfy@qq.com" Date: Mon, 25 Jan 2016 16:34:07 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9F=9F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 018f5e1..95ed38c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ SocketLog适合Ajax调试和API调试, 举一个常见的场景,用SocketLog #使用方法 * 首先,请在chrome浏览器上安装好插件。 - * 安装服务端`npm install -g socketlog-server` , 运行命令 `socketlog-server` 即可启动服务。 将会在本地起一个websocket服务 ,监听端口是1229 。 如果想服务后台运行: `socketlog-server > /dev/null &` 我们提供公用的服务端,需要去申请client_id : http://111.202.76.133/ + * 安装服务端`npm install -g socketlog-server` , 运行命令 `socketlog-server` 即可启动服务。 将会在本地起一个websocket服务 ,监听端口是1229 。 如果想服务后台运行: `socketlog-server > /dev/null &` 我们提供公用的服务端,需要去申请client_id : http://slog.thinkphp.cn/ * 如果你的服务器有防火墙,请开启1229和1116两个端口,这两个端口是socketlog要使用的。 * 在自己的程序中发送日志: From 806b578e17c699bb350199c8244d3269697e68cb Mon Sep 17 00:00:00 2001 From: luofei614 Date: Mon, 29 Aug 2016 23:43:12 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E6=A0=88=E4=B8=8D=E8=BE=93=E5=87=BA=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- php/slog.php | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 95ed38c..47cb5ca 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ SocketLog适合Ajax调试和API调试, 举一个常见的场景,用SocketLog ##About Me * Author: @luofei614 新浪微博:http://weibo.com/luofei614 -* 三生社群CTO,ThinkPHP核心开发者之一,待过新浪云计算 +* 优伯立信创始人,ThinkPHP核心开发者之一,待过新浪云计算 [1]: https://github.com/luofei614/SocketLog/raw/master/screenshots/discuz.png diff --git a/php/slog.php b/php/slog.php index 22fd094..54b13f2 100644 --- a/php/slog.php +++ b/php/slog.php @@ -90,13 +90,14 @@ public static function trace($msg,$trace_level=1,$css='') $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{ + //不输出参数速度会有明显的改善 + //if(!empty($trace['args'])){ + // self::groupCollapsed($trace_msg); + // self::log($trace['args']); + // self::groupEnd(); + //}else{ self::log($trace_msg); - } + //} } self::groupEnd(); } From cf55d5f6f5ce0e07b5fab9cc43c68c26d3867fd1 Mon Sep 17 00:00:00 2001 From: luofei614 Date: Mon, 24 Oct 2016 16:00:40 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9compose=20=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47cb5ca..e5b9adb 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ SocketLog适合Ajax调试和API调试, 举一个常见的场景,用SocketLog ##支持composer - * 使用composer安装命令 `composer require luofei614\socketlog` + * 使用composer安装命令 `composer require luofei614/socketlog` * 直接调用静态方法 From a5615b9c4366a4c242d4482fd5c2db45221c9483 Mon Sep 17 00:00:00 2001 From: luofei614 Date: Wed, 18 Oct 2017 21:04:40 +0800 Subject: [PATCH 07/14] ok --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5b9adb..9f7a10f 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ SocketLog适合Ajax调试和API调试, 举一个常见的场景,用SocketLog - 提示:另一种更简单的方法,因为OneThink每次执行完sql语句都会调用$this->debug, 所以我们可以把slog($this->queryStr,$this->_linkID); 直接写在 Db.class.php文件的debug方法中。 这样不管是mysqli还是mysql驱动都有效。 ## 视频教程 - [http://edu.yuantuan.com/course/198](http://edu.yuantuan.com/course/198) + [http://edu.normalcoder.com/course/941](http://edu.normalcoder.com/course/941) 感谢猿团的张盛翔(诺墨)提供教程。 From 44cf02568a8565deb1c3611b05392bc3b0daf495 Mon Sep 17 00:00:00 2001 From: luofei614 Date: Thu, 8 Feb 2018 15:02:48 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E5=85=BC=E5=AE=B9php7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/slog.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/php/slog.php b/php/slog.php index 54b13f2..0a3b95e 100644 --- a/php/slog.php +++ b/php/slog.php @@ -10,7 +10,7 @@ class Slog 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 $_allowForceClientIds = array(); //配置强制推送且被授权的client_id protected static $_instance; @@ -65,7 +65,7 @@ public static function sql($sql,$link) } throw new Exception('SocketLog can not support this database link'); - } + } @@ -83,6 +83,7 @@ public static function trace($msg,$trace_level=1,$css='') self::groupCollapsed($msg,$css); $traces=debug_backtrace(false); $traces=array_reverse($traces); + $trace_level = $trace_level == '' ? 0 : intval($trace_level); $max=count($traces)-$trace_level; for($i=0;$i<$max;$i++){ $trace=$traces[$i]; @@ -323,7 +324,7 @@ public static function config($config) $config=array_merge(self::$config,$config); if(isset($config['force_client_id'])){ //兼容老配置 - $config['force_client_ids']=array_merge($config['force_client_ids'],array($config['force_client_id'])); + $config['force_client_ids']=array_merge($config['force_client_ids'],array($config['force_client_id'])); } self::$config=$config; if(self::check()) @@ -481,10 +482,10 @@ protected static function sendToClient($tabid, $client_id, $logs, $force_client_ 'client_id'=>$client_id, 'logs'=>$logs, 'force_client_id'=>$force_client_id, - ); + ); $msg=@json_encode($logs); $address='/'.$client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 - self::send(self::getConfig('host'),$msg,$address); + self::send(self::getConfig('host'),$msg,$address); } public function __destruct() From 0bb31d57437c728d0808bd142bdb67c2d1a1bd75 Mon Sep 17 00:00:00 2001 From: luofei614 Date: Sun, 15 Jul 2018 18:02:29 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=92=88=E5=AF=B9Think?= =?UTF-8?q?PHP=E7=94=A8=E6=88=B7=E7=9A=84=E6=9C=8D=E5=8A=A1=E7=AB=AF?= =?UTF-8?q?=EF=BC=8C=E7=94=B3=E8=AF=B7=E5=9C=B0=E5=9D=80slog.thinkphp.cn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- server/index.html | 80 +++++++++++++++++++++++++++++++++++++++++++++++ server/index.js | 14 ++++++++- 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 server/index.html diff --git a/Dockerfile b/Dockerfile index 3807466..9a5d7e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,6 @@ RUN apt-get update && \ nodejs &&\ rm -rf /var/lib/apt/lists/* add . /socketlog -EXPOSE 1229 1116 +EXPOSE 1229 1116 8712 WORKDIR /socketlog ENTRYPOINT ["nodejs", "/socketlog/server/index.js"] diff --git a/server/index.html b/server/index.html new file mode 100644 index 0000000..c14f98d --- /dev/null +++ b/server/index.html @@ -0,0 +1,80 @@ + + + + + + ThinkPHP SocketLog服务 + + + + + + + +Fork me on GitHub +
+
+

ThinkPHP SocketLog服务

+

调试程序、分析源码的好工具

+

具体使用方法,请阅读GitHUb项目上的文档:http://github.com/luofei614

+

点击下方按钮,生成Client_id进行使用

+ +
+
+
+ + + diff --git a/server/index.js b/server/index.js index 056bf10..426f408 100644 --- a/server/index.js +++ b/server/index.js @@ -42,7 +42,7 @@ var httpServer = http.createServer(function(request, response){ { var requestBody = ''; request.on('data', function(data) { - requestBody+=data; + requestBody+=data; }); request.on('end',function(){ console.log(requestBody); @@ -58,4 +58,16 @@ var httpServer = http.createServer(function(request, response){ }); httpServer.listen(1116); + + +//测试服务的申请页面 +var httpServer = http.createServer(function(request, response){ + var html = fs.readFileSync(__dirname+'/index.html').toString(); + response.end(html); +}); +httpServer.listen(8712); + + + + console.log('SocketLog started success'); From 0f77886b63ba23a407204cb7db00dfb5bf0c6318 Mon Sep 17 00:00:00 2001 From: luofei614 Date: Thu, 4 Oct 2018 13:28:58 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E6=94=AF=E6=8C=81php7=EF=BC=8Ccreate=5Ff?= =?UTF-8?q?unction=E6=94=B9=E4=B8=BA=E5=8C=BF=E5=90=8D=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/slog.function.php | 4 +++- php/slog.php | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/php/slog.function.php b/php/slog.function.php index 22cd145..873ca8b 100644 --- a/php/slog.function.php +++ b/php/slog.function.php @@ -5,7 +5,9 @@ function slog($log,$type='log',$css='') { if(is_string($type)) { - $type=preg_replace_callback('/_([a-zA-Z])/',create_function('$matches', 'return strtoupper($matches[1]);'),$type); + $type=preg_replace_callback('/_([a-zA-Z])/',function($matches){ + return strtoupper($matches[1]); + } ,$type); if(method_exists('\think\org\Slog',$type) || in_array($type,Slog::$log_types)) { return call_user_func(array('\think\org\Slog',$type),$log,$css); diff --git a/php/slog.php b/php/slog.php index 0a3b95e..e9a7363 100644 --- a/php/slog.php +++ b/php/slog.php @@ -398,6 +398,7 @@ public static function sendLog() return ; } + $time_str=''; $memory_str=''; if(self::$start_time) From 73964d9871829a53c9a55dd9cd085cb53bb27735 Mon Sep 17 00:00:00 2001 From: luofei614 Date: Wed, 9 Oct 2019 11:56:10 +0800 Subject: [PATCH 11/14] update --- chrome/background.js | 25 +++++-------------------- composer.json | 4 ++-- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/chrome/background.js b/chrome/background.js index d07afa8..73c9420 100644 --- a/chrome/background.js +++ b/chrome/background.js @@ -182,29 +182,14 @@ chrome.webRequest.onBeforeSendHeaders.addListener( header+="&client_id="+client_id; - var special_domain=localStorage.getItem('special_domain'); - if(!special_domain) + //将Header隐藏在User-Agent中, 不能使用自定义Header了, 不让HTTPS情况下会报不安全 + for (var i = 0; i < details.requestHeaders.length; ++i) { - special_domain='*.sinaapp.com'; - localStorage.setItem('special_domain',special_domain); - } - var exp=url_exp(special_domain); - if (exp.test(details.url)) - { - //如果是特殊环境域名 - for (var i = 0; i < details.requestHeaders.length; ++i) + if (details.requestHeaders[i].name === 'User-Agent') { - if (details.requestHeaders[i].name === 'User-Agent') - { - //将参数放在User-agent中,兼容SAE的情况 - details.requestHeaders[i].value+=" SocketLog("+header+")"; - break; - } + details.requestHeaders[i].value+=" SocketLog("+header+")"; + break; } - } - else - { - details.requestHeaders.push({name:'SocketLog',value:" SocketLog("+header+")"}); } return {requestHeaders: details.requestHeaders}; diff --git a/composer.json b/composer.json index aee4a07..74b954b 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "luofei614/socketlog", - "description": "debug tool for api , ajax,wechat ,v2.2", + "description": "debug tool for api , ajax,wechat ,v2.3", "keywords": ["SocketLog", "slog", "api"], "license": "MIT", "authors": [ @@ -21,4 +21,4 @@ "exclude":["!/php"] }, "minimum-stability": "dev" -} +} \ No newline at end of file From 77b2946abb293c8b38a4919117963b65d6af3145 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 23 Jan 2021 17:17:16 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E9=87=8D=E6=96=B0=E7=BC=96=E8=BE=91=20RE?= =?UTF-8?q?ADME.md=20=E6=96=87=E6=A1=A3=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 556 +++++++++++++------- screenshots/SocketLog-Options-env.png | Bin 0 -> 25626 bytes screenshots/SocketLog-detail-msg.png | Bin 0 -> 19136 bytes screenshots/SocketLog-extension-options.png | Bin 0 -> 25446 bytes 4 files changed, 370 insertions(+), 186 deletions(-) create mode 100644 screenshots/SocketLog-Options-env.png create mode 100644 screenshots/SocketLog-detail-msg.png create mode 100644 screenshots/SocketLog-extension-options.png diff --git a/README.md b/README.md index 9f7a10f..2b8b983 100644 --- a/README.md +++ b/README.md @@ -1,210 +1,394 @@ -#说明 - -SocketLog适合Ajax调试和API调试, 举一个常见的场景,用SocketLog来做微信调试, 我们在做微信API开发的时候,如果API有bug,微信只提示“改公众账号暂时无法提供服务,请稍候再试” ,我们根本不知道API出来什么问题。 有了SocketLog就不一样了, 我们可以知道微信给API传递了哪些参数, 程序有错误我们也能看见错误信息(下方有张图片,可能加载慢,请耐心等待一下) - -![微信调试](https://raw.githubusercontent.com/luofei614/SocketLog/master/screenshots/weixin.png) - - - * 正在运行的API有bug,不能var_dump进行调试,因为会影响client的调用。 将日志写到文件,查看也不方便,特别是带调用栈或大数据结构的文件日志,查看日志十分困难。 这时候用SocketLog最好,SocketLog通过websocket将调试日志打印到浏览器的console中。你还可以用它来分析开源程序,分析SQL性能,结合taint分析程序漏洞。 - * Chrome插件安装: https://chrome.google.com/webstore/detail/socketlog/apkmbfpihjhongonfcgdagliaglghcod (如果不能正常访问这个页面,你可以用下面手动安装的方法进行安装) - * 目录结构: - * chrome 目录是 chrome插件的源代码 - * chrome.crx 文件是chrome插件的安装包, 如果你无法从chrome应用商店安装,可进行手动安装, 浏览器地址栏输入并打开: chrome://extensions/ ,然后将chrome.crx拖入即可安装。 - * php 目录下的SocketLog.class.php是发送日志的类库,我们在发送日志的时候,需要载入这个类库然后调用函数slog即可。 - * 效果展示: 我们在浏览网站的时候在浏览器console中就知道程序做了什么,这对于二次开发产品十分有用。 下面效果图在console中打印出浏览discuz程序时,执行了哪些sql语句, 以及执行sql语句的调用栈。程序的warning,notice等错误信息也可以打到console中。 -![enter image description here][1] - -#使用方法 - * 首先,请在chrome浏览器上安装好插件。 - * 安装服务端`npm install -g socketlog-server` , 运行命令 `socketlog-server` 即可启动服务。 将会在本地起一个websocket服务 ,监听端口是1229 。 如果想服务后台运行: `socketlog-server > /dev/null &` 我们提供公用的服务端,需要去申请client_id : http://slog.thinkphp.cn/ - * 如果你的服务器有防火墙,请开启1229和1116两个端口,这两个端口是socketlog要使用的。 - * 在自己的程序中发送日志: - - - - - * 用slog函数发送日志, 支持多种日志类型: - - slog('msg','log'); //一般日志 - slog('msg','error'); //错误日志 - slog('msg','info'); //信息日志 - slog('msg','warn'); //警告日志 - slog('msg','trace');// 输入日志同时会打出调用栈 - slog('msg','alert');//将日志以alert方式弹出 - slog('msg','log','color:red;font-size:20px;');//自定义日志的样式,第三个参数为css样式 - - * 通过上面例子可以看出, slog函数支持三个参数: - * 第一个参数是日志内容,日志内容不光能支持字符串哟,大家如果传递数组,对象等一样可以打印到console中。 - * 第二个参数是日志类型,可选,如果没有指定日志类型默认类型为log, 第三个参数是自定样式,在这里写上你自定义css样式即可。 - -##配置 -* 在载入slog.function.php文件后,还可以对SocketLog进行一些配置。 -* 例如:我们如果想将程序的报错信息页输出到console,可以配置 - - true - ),'config'); - echo notice;//制造一个notice报错 - slog('这里是输出的一般日志'); - ?> -* 配置SocketLog也是用slog函数, 第一个参数传递配置项的数组,第二个参数设置为config -* 还支持其他配置项 - - true,//是否打印日志的开关 - 'host'=>'localhost',//websocket服务器地址,默认localhost - 'optimize'=>false,//是否显示利于优化的参数,如果运行时间,消耗内存等,默认为false - 'show_included_files'=>false,//是否显示本次程序运行加载了哪些文件,默认为false - 'error_handler'=>false,//是否接管程序错误,将程序错误显示在console中,默认为false - 'force_client_id'=>'',//日志强制记录到配置的client_id,默认为空 - 'allow_client_ids'=>array()////限制允许读取日志的client_id,默认为空,表示所有人都可以获得日志。 +## 一,简介 + +SocketLog 是一款高效的、探针式的服务器端程序调试和分析工具。 + +将“探针”引入到目标项目后,SocketLog 会通过在服务端启动的一个 WebSocket 服务,将程序执行过程中收集到的调试信息推送到客户端。 +客户端会通过一个 Chrome 插件将调试信息打印到浏览器的 Console 中,这些信息包括程序的运行时间、吞吐率、内存消耗;PHP 的 Error、warning、notice 信息;程序执行的 SQL 语句以及对 SQL 语句的 explain 等等。 + +SocketLog 使用 PHP 和 NodeJs 开发,是 FirePHP 和 ChromePHP 的替代者,特别适合用于调试 Ajax 方式发起的请求和 API 项目。 + +### 1、使用场景举例: +**用 SocketLog 来做微信开发调试。** + +举一个常见的场景:你在做微信 API 开发的时候,是否遇到了 API 有 bug,微信只提示“该公众账号暂时无法提供服务,请稍候再试” ? + +我们根本不知道 API 出了什么问题,由于不能打印信息,只能通过日志来排查,这种方式的调试效率实在是太低了。 + +现在有了 SocketLog 就不一样了,我们可以知道微信给 API 传递了哪些参数,程序有错误我们也能看见错误信息。 + +![微信调试](./screenshots/weixin.png) + +### 2、SocketLog 的用途: +- **API bug 调试** + - 正在运行的 API 有 bug,不能使用 var_dump() 进行调试,因为会影响 client 的调用。 + 将日志写到文件,查看也不方便,特别是带调用栈或大数据结构的文件日志,查看起来十分困难。 + - 是时候让 SocketLog 大显身手了,它通过 WebSocket 将调试日志打印到浏览器的 Console 中,查看起来非常方便。 + +- **其他用途** + - 你还可以用它来分析开源程序、分析 SQL 性能、结合 PHP Taint 分析程序漏洞。 + +### 3、文件和目录简要说明: +``` +├── chrome :Chrome 插件的源代码目录。 +├── chrome.crx :Chrome 插件的安装包文件,手动安装方法见下文。 +├── demo.php : Demo 示例文件。 +├── Dockerfile +├── composer.json +├── README.md +├── php +│ ├── slog.function.php :探针文件,在需要发送日志的时候,载入这个文件,然后调用其中的函数 slog() 即可。 +│ └── slog.php :收集调试日志的函数类库文件,需要在探针文件中载入这个类库。 +├── screenshots :README.md 本文档中需要的示例图片。 +└── server + ├── bin + │ └── socketlog-server:当前目录下启动 WebSocket 服务的文件。 + ├── config.json + ├── index.html : 申请 client_id 的页面。 + ├── index.js :WebSocket 服务的入口文件。 + ├── node_modules :Node 所需的组件目录。 + └── package.json +``` + +### 4、效果展示: +针对一个开源的 Web 项目,我们想基于它做二次开发。如果能在浏览网站的时候,可以从浏览器的 Console 中知道程序都做了些什么,这对二次开发将十分有帮助。 + +下面的效果图显示,在浏览 discuz 程序时,Console 中打印出了: +- 程序的运行时间、吞吐率和内存消耗信息。 +- 程序的 warning,notice 等错误信息。 +- 当前页面执行了哪些 SQL 语句,以及执行 SQL 语句的调用栈信息。 + +![打印浏览 discuz 时执行的 SQL 语句](./screenshots/discuz.png) + +## 二,安装与使用 +### 1、安装: +- 1,客户端,安装 Chrome 插件。 + - 方法一,在线安装: + + 打开 [Chrome 网上应用店](https://chrome.google.com/webstore/category/extensions?hl=zh-CN),搜索 SocketLog ,然后点击 “添加至 Chrome” 完成安装。如果不能正常访问 Chrome 网上应用店,可以尝试如下的手动安装方法。 + + - 方法二,手动安装: + + 在 GitHub 上下载 [chrome.crx](https://github.com/luofei614/SocketLog),打开浏览器地址栏并输入:chrome://extensions,将chrome.crx 拖入打开的页面即可。 +- 2,服务端,安装 Socket 服务并启动。 + > (请确保你的环境已经安装了 NodeJs) + - 方法一,npm 方式安装: + - 全局安装 + ```shell + $ npm install -g socketlog-server + ``` + - 局部安装 (切换到目标项目所在目录) + ``` + $ npm install socketlog-server + ``` + - 启动服务 + ```shell + ## 普通方式运行: + $ socketlog-server + + ## 如果想让服务在后台运行: + $ socketlog-server > /dev/null & + ``` + - 方法二,源码方式安装: + - 根据实际情况选定目录(例如,在目标项目所在目录下) + ```shell + ## 下载项目代码或者 clone + $ git clone https://github.com/luofei614/SocketLog.git + ``` + - 启动服务 + ```shell + ## 请注意实际项目的 index.js 文件的路径 + $ node server/index.js + ``` +- 3,php 探针文件: + + ```php + true), // error_handler 需要设置为 true + 'config' + ); + echo notice; // 制造一个 notice 报错 + + // 输出一般日志 + slog('这里是输出的一般日志'); + ``` + - 配置解析: + - 第一个 slog() 函数有两个参数,第一个参数 **传递配置项的数组**,第二个参数 **设置为 config**。 + - 第二个 slog() 函数,将字符串消息直接输出。 + +- 2,支持的其他配置项: + + ```php + true, // 是否打印日志, [true | false] + 'host' => 'localhost', // WebSocket 服务器地址,默认为 localhost + 'optimize' => false, // 是否显示有利于程序优化的信息,如运行时间、吞吐率、消耗内存等,默认为 false + 'show_included_files' => false, // 是否显示本次程序运行加载了哪些文件,默认为 false + 'error_handler' => false, // 是否接管程序错误,将程序错误显示在 Console 中,默认为 false + 'force_client_id' => '', // 日志强制输出到配置的 client_id,默认为空 + 'allow_client_ids' => array() // 限制允许读取日志的 client_id,默认为空,表示所有人都可以获得日志。 + ), + 'config' + ); + ``` + - 参数解析: + - **optimize** + - 若设置为 true,则日志中显示有利于程序优化的信息。 + - 如:`[运行时间:0.081346035003662s][吞吐率:12.29req/s][内存消耗:346,910.45kb]` + - **show_included_files** + - 若设置为 true,则显示出程序运行时加载了哪些文件。 + - 比如,我们在分析开源项目时,如果不知道模板文件在那里,往往需要看一下加载的文件列表。 + - **error_handler** + - 若设置为 true,则接管报错,将错误信息打印到浏览器 Console。 + - 在开发时 notice 信息能让我们快速发现 bug,但是有些 notice 是不可避免的,如果让他们显示在页面中会影响网页的正常布局,那么设置 error_handler 为 true,可让它显示在浏览器 Console 中,问题成功解决。 + - 另外,此功能结合 [鸟哥](https://www.laruence.com/) 开发的 PHP Taint 也是极佳的。 + - Taint 能自动检测出 XSS、SQL 注入、Shell注入等漏洞。 + - 如果只用 PHP Taint,它的 warning 报错只告诉了变量输出的地方,并不知道变量在哪里赋值、怎么传递? + - 通过 SocketLog,能看到调用栈,可以轻松地对有问题的变量进行跟踪。更多 Taint 的信息请参考 [这里](https://www.laruence.com/2012/02/14/2544.html)。 + +- 3,设置 client_id + + 在 Chrome 浏览器中,可以设置插件的 client_id,可以是你任意指定的字符串。 + + ![设置插件的 client_id](./screenshots/socketlog.png) + + **设置了 client_id 后能实现以下功能:** + - 1,设置 allow_client_ids 配置项,只允许指定的浏览器获取日志,这样就可以把调试代码带上线。 + - 普通用户访问不会触发调试,也不会收到调试日志,而开发人员访问就能看到调试日志,这样有利于找线上 bug。 + - client_id 建议设置为:姓名拼音+随机字符串形式,原因有二。 + - 其一,如果有员工离职可以将其对应的 client_id 从配置项 allow_client_ids 中移除。 + - 其二,加上随机字符串,目的是防备别人根据你公司员工姓名猜测出 client_id,获取线上的调试日志。 + + - 设置 allow_client_ids 的示例代码: + ```php + slog( + array( + 'allow_client_ids' => array('luofei_zfH5NbLn', 'easy_DJq0z80H') // 可设置多个 cliet_id + ), + 'set_config' + ); + ``` + + - 2,设置 force_client_id 配置项,让后台脚本也能输出日志到 Console 中。 + + - 网站有可能使用了队列,一些业务逻辑通过后台脚本处理,由于后台脚本不和浏览器接触,并不知道当前触发程序的是哪个浏览器。所以如果需要调试,我们就需要强制将日志打印到指定 client_id 的浏览器上。 + - 因此在后台脚本中使用 SocketLog 时,设置 force_client_id 配置项,指定要强制输出浏览器的 client_id 即可(只可设置一个 client_i)。 + - 设置 force_client_id 的示例代码: + ```php + 'luofei_zfH5NbLn' // 只可设置一个 cliet_id + ), + 'config' + ); + slog('test'); + ``` + +### 4,支持 composer(暂不推荐): + +- 1,使用 composer 的安装命令如下: + + > 注意:通过 composer 方式安装的文件版本较旧,有些问题,不能直接使用,故不推荐。 + ```shell + composer require luofei614/socketlog + ``` +- 2,直接调用静态方法 + ```php + true, // 是否打印日志, [true | false] + 'host' => 'localhost', // WebSocket 服务器地址,默认为 localhost + 'optimize' => false, // 是否显示有利于程序优化的信息,如运行时间、吞吐率、消耗内存等,默认为 false + 'show_included_files' => false, // 是否显示本次程序运行加载了哪些文件,默认为 false + 'error_handler' => false, // 是否接管程序错误,将程序错误显示在 Console 中,默认为 false + 'force_client_id' => '', // 日志强制输出到配置的 client_id,默认为空 + 'allow_client_ids' => array() // 限制允许读取日志的 client_id,默认为空,表示所有人都可以获得日志。 ) - ,'config'); - ?> -* optimize 参数如果设置为true, 可以在日志中看见利于优化参数,如:`[运行时间:0.081346035003662s][吞吐率:12.29req/s][内存消耗:346,910.45kb]` -* show_included_files 设置为true,能显示出程序运行时加载了哪些文件,比如我们在分析开源程序时,如果不知道模板文件在那里, 往往看一下加载文件列表就知道模板文件在哪里了。 -* error_handler 设置为true,能接管报错,将错误信息显示到浏览器console, 在开发程序时notice报错能让我们快速发现bug,但是有些notice报错是不可避免的,如果让他们显示在页面中会影响网页的正常布局,那么就设置error_handler,让它显示在浏览器console中吧。 另外此功能结合php taint也是极佳的。 taint能自动检测出xss,sql注入, 如果只用php taint, 它warning报错只告诉了变量输出的地方,并不知道变量在那里赋值、怎么传递。通过SocketLog, 能看到调用栈,轻松对有问题变量进行跟踪。 更多taint的信息:http://www.laruence.com/2012/02/14/2544.html -* 设置client_id: 在chrome浏览器中,可以设置插件的Client_ID ,Client_ID是你任意指定的字符串。 -![enter image description here][2] -* 设置client_id后能实现以下功能: - -* 1,配置allow_client_ids 配置项,让指定的浏览器才能获得日志,这样就可以把调试代码带上线。 普通用户访问不会触发调试,不会发送日志。 开发人员访问就能看的调试日志, 这样利于找线上bug。 Client_ID 建议设置为姓名拼命加上随机字符串,这样如果有员工离职可以将其对应的client_id从配置项allow_client_ids中移除。 client_id除了姓名拼音,加上随机字符串的目的,以防别人根据你公司员工姓名猜测出client_id,获取线上的调试日志。 -* 设置allow_client_ids示例代码: - - slog(array( - 'allow_client_ids'=>array('luofei_zfH5NbLn','easy_DJq0z80H') - ),'set_config') - -* 2, 设置force_client_id配置项,让后台脚本也能输出日志到chrome。 网站有可能用了队列,一些业务逻辑通过后台脚本处理, 如果后台脚本需要调试,你也可以将日志打印到浏览器的console中, 当然后台脚本不和浏览器接触,不知道当前触发程序的是哪个浏览器,所以我们需要强制将日志打印到指定client_id的浏览器上面。 我们在后台脚本中使用SocketLog时设置force_client_id 配置项指定要强制输出浏览器的client_id 即可。 -* 示例代码: - - 'luofei_zfH5NbLn' - ),'config'); - slog('test'); ` - -##支持composer - - * 使用composer安装命令 `composer require luofei614/socketlog` - - * 直接调用静态方法 - - true,//是否打印日志的开关 - 'host'=>'localhost',//websocket服务器地址,默认localhost - 'optimize'=>false,//是否显示利于优化的参数,如果运行时间,消耗内存等,默认为false - 'show_included_files'=>false,//是否显示本次程序运行加载了哪些文件,默认为false - 'error_handler'=>false,//是否接管程序错误,将程序错误显示在console中,默认为false - 'force_client_id'=>'',//日志强制记录到配置的client_id,默认为空 - 'allow_client_ids'=>array()////限制允许读取日志的client_id,默认为空,表示所有人都可以获得日志。 - )); - Slog::log('log'); //一般日志 - Slog::error('msg'); //错误日志 - Slog::info('msg'); //信息日志 - Slog::warn('msg'); //警告日志 - Slog::trace('msg');// 输入日志同时会打出调用栈 - Slog::alert('msg');//将日志以alert方式弹出 - Slog::log('msg','color:red;font-size:20px;');//自定义日志的样式,第三个参数为css样式 - -##支持ThinkPHP - ThinkPHP5后, 在框架层集成了SocketLog ,只需要设置配置即可用 - -##对数据库进行调试 - * SocketLog还能对sql语句进行调试,自动对sql语句进行explain分析,显示出有性能问题的sql语句。 如下图所示。 - ![enter image description here][3] - * 图中显示出了三条sql语句 , 第一条sql语句字体较大,是因为它又性能问题, 在sql语句的后台已经标注Using filesort。 我们还可以点击某个sql语句看到sql执行的调用栈,清楚的知道sql语句是如何被执行的,方便我们分析程序、方便做开源程序的二次开发。图中第三条sql语句为被点开的状态。 - * 用slog函数打印sql语句是,第二个参数传递为mysql或mysqli的对象即可。 示例代码: - - - $link=mysql_connect( 'localhost:3306' , 'root' , '123456' , true ) ; - mysql_select_db('kuaijianli',$link); - $sql="SELECT * FROM `user`"; - slog($sql,$link); -后面会以OneThink为实例再对数据库调试进行演示。 - -通过上面的方法,socketlog还能自动为你检测没有where语句的sql操作,然后自动提示你。 - - * 注意,有时候在数据比较少的情况下,mysql查询不会使用索引,explain也会提示Using filesort等性能问题, 其实这时候并不是真正有性能问题, 你需要自行进行判断,或者增加更多的数据再测试。 - -##对API进行调试 - 网站调用了API ,如何将API程序的调试信息也打印到浏览器的console中? 前面我们讲了一个配置 force_client_id, 能将日志强制记录到指定的浏览器。 用这种方式也可以将API的调试信息打印到console中,但是force_client_id 只能指定一个client_id, 如果我们的开发环境是多人共用,这种方式就不方便了。 - 其实只要将浏览器传递给网站的User-Agent 再传递给API, API程序中不用配置force_client_id, 也能识别当前访问程序的浏览器, 将日志打印到当前访问程序的浏览器, 我们需要将SDK代码稍微做一下修改。 调用API的SDK,一般是用curl写的,增加下面代码可以将浏览器的User-Agent传递到API 。 - - - $headers=array(); - if(isset($_SERVER['HTTP_USER_AGENT'])) - { - $headers[]='User-Agent: '.$_SERVER['HTTP_USER_AGENT']; + ); + + Slog::log('log'); // 一般日志 + Slog::error('msg'); // 错误日志 + Slog::info('msg'); // 信息日志 + Slog::warn('msg'); // 警告日志 + Slog::trace('msg'); // 输出日志,同时会打出调用栈 + Slog::alert('msg'); // 将日志以 alert 方式弹出 + Slog::log('msg', 'log', 'color:red;font-size:20px;'); // 自定义日志的样式,第三个参数为 css 样式 + ``` + +### 5,支持 ThinkPHP: +ThinkPHP5 之后,在框架层集成了 SocketLog,只需要设置配置即可用。参考:[远程调试](http://www.kancloud.cn/manual/thinkphp5/156120) + +### 6,对数据库进行调试: +SocketLog 还能对 SQL 语句进行调试,自动对 SQL 语句进行 explain 分析,打印出有性能问题的 SQL 语句。 如下图所示。 + +![对 SQL 语句进行 explain 分析](./screenshots/sql.png "对 SQL 语句进行 explain 分析") + +- 图片解析: + - 图中显示出了三条 SQL 语句,第一条 SQL 语句字体较大,是因为它有性能问题,在 SQL 语句的后台已经标注 Using filesort。 + - 我们还可以点击某个 SQL 语句查看它执行的调用栈,清楚地知道 SQL 语句是如何被执行的,方便我们分析程序、方便做开源程序的二次开发。 + - 图中第三条 SQL 语句为被点开的状态。 + +- 实际用例: + + - 用 slog() 函数打印 SQL 语句时,第二个参数传递 mysql 或 mysqli 的对象即可。 + +- 示例代码: + ```php + $link = mysql_connect('localhost:3306', 'root', '123456', true); + mysql_select_db('kuaijianli', $link); + + $sql = "SELECT * FROM `user`"; + + slog($sql, $link); + ``` + 后面会以 OneThink 为例再对数据库调试进行演示 + + 借助上面的方法,SocketLog 还能自动为你检测出没有 where 条件的 SQL 操作,然后自动提示你。 + +> 注意:有时候在数据比较少的情况下,MySQL 查询不会使用索引,explain 也会提示 Using filesort 等性能问题,其实这时候并不是真正有性能问题,你需要自行判断,或者增加更多的数据再测试。 + +### 7,对 API 进行调试: +网站调用了 API ,如何将 API 程序的调试信息也打印到浏览器的 Console 中? + +- 方法一:配置 force_client_id + - 前面我们讲了一个 force_client_id 配置,能将日志强制记录到指定的浏览器。这种方法同样适用于将 API 的调试信息打印到 Console 中。 + - 缺点: force_client_id 只能指定一个 client_id,如果我们的开发环境是多人共用,这种方式就不方便了。 + +- 方法二:识别 User-Agent + - 其实只要将浏览器发送的 User-Agent 再传递给 API,API 程序中不用配置 force_client_id,也能识别当前访问程序的浏览器,将日志回传回来。 + - 我们需要将 SDK 代码稍微做一下修改。调用 API 的 SDK,一般是用 curl 写的,添加下面的代码可以将浏览器的 User-Agent 传递到 API 。 + ```php + $headers = array(); + if (isset($_SERVER['HTTP_USER_AGENT'])) { + $headers[] = 'User-Agent: ' . $_SERVER['HTTP_USER_AGENT']; } - if(isset($_SERVER['HTTP_SOCKETLOG'])) - { - $headers[]='Socketlog: '.$_SERVER['HTTP_SOCKETLOG']; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $headers[] = 'Socketlog: ' . $_SERVER['HTTP_SOCKETLOG']; } - curl_setopt($ch,CURLOPT_HTTPHEADER,$headers); -##区分正式和开发环境 + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + ``` + +### 8,区分正式和开发环境: +- 1,进入 Chrome 浏览器的 “更多工具” ----> “扩展程序(E)” 或者直接在地址栏输入 `chrome://extensions/`,打开扩展程序列表页。 + +- 2,找到 “SocketLog” 然后点击 “详细信息”,打开 SocketLog 的详细页。 - 进入chrome浏览器的“工具”-->“扩展程序” , 点击SocketLog的“选项”进行设置。 +![SocketLog 详细信息](./screenshots/SocketLog-detail-msg.png "SocketLog 详细信息") +- 3,找到“扩展程序选项”,点击右侧的按钮。 -##分析开源程序 +![扩展程序选项](./screenshots/SocketLog-extension-options.png "扩展程序选项") - 有了SocketLog,我们能很方便的分析开源程序,下面以OneThink为例, 大家可以在 http://www.topthink.com/topic/2228.html 下载最新的OneThink程序。 安装好OneThink后,按下面步骤增加SocketLog程序。 +- 4,此时在打开的 SocketLog Options 页,可以设置 “特殊环境域名” 和 “正式环境域名”。 - * 将SocketLog.class.php复制到OneThink的程序目录中,你如果没有想好将文件放到哪个子文件夹,暂且放到根目录吧。 - * 编辑入口文件index.php, 再代码的最前面加载slog.function.php ,并设置SocketLog +![设置开发环境的域名](./screenshots/SocketLog-Options-env.png "设置开发环境的域名") - +### 9,分析开源程序: - true, - 'optimize'=>true, - 'show_included_files'=>true - ),'config'); +有了 SocketLog,我们能很方便的分析开源程序,下面以 OneThink 为例,大家可以在 http://www.topthink.com/topic/2228.html 下载最新的 OneThink 程序。 安装好 OneThink 后,按下面步骤增加 SocketLog 程序。 +- 1,将 slog.php 复制到 OneThink 的程序目录中,如果没有想好将文件放到哪个子文件夹,可以暂且放到根目录。 +- 2,编辑入口文件 index.php,在代码的最前面加载 slog.function.php,并设置 SocketLog。 + ```php + true, + 'optimize' => true, + 'show_included_files' => true + ), + 'config' + ); + ``` - - 编辑ThinkPHP/Library/Think/Db/Driver.class.php 文件,在这个类中的execute 方法为一个执行sql语句的方法,增加代码: - +- 3,编辑 ThinkPHP/Library/Think/Db/Driver.class.php 文件,在这个类中的 execute() 方法为一个执行 SQL 语句的方法,增加代码: + ```php + slog($this->queryStr, $this->_linkID); + ``` +- 4,类中的 query() 方法也是一个执行 SQL 语句的地方,同样需要增加上面的代码。 - slog($this->queryStr,$this->_linkID); +- 5,然后浏览网站看看效果: - - 类中的query方法也是一个执行sql语句的地方, 同样需要增加上面的代码 - - - 然后浏览网站看看效果: - - ![enter image description here][4] - -通过console的日志,访问每一页我们都知道程序干了什么,是一件很爽的事情。 +![enter image description here](./screenshots/onethink.png) -- 提示:另一种更简单的方法,因为OneThink每次执行完sql语句都会调用$this->debug, 所以我们可以把slog($this->queryStr,$this->_linkID); 直接写在 Db.class.php文件的debug方法中。 这样不管是mysqli还是mysql驱动都有效。 +通过 Console 的日志,访问每一页我们都知道程序干了些什么,这是一件很爽的事情。 -## 视频教程 - [http://edu.normalcoder.com/course/941](http://edu.normalcoder.com/course/941) +> 提示: +另一种更简单的方法,因为 OneThink 每次执行完 SQL 语句都会调用 `$this->debug`, 所以我们可以把 `slog($this->queryStr, $this->_linkID);` 直接写在 `Db.class.php` 文件的 debug() 方法中。 这样不管是 mysqli 还是 mysql 驱动都有效。 + +## 三,视频教程 +[http://edu.normalcoder.com/course/941](http://edu.normalcoder.com/course/941) 感谢猿团的张盛翔(诺墨)提供教程。 -##About Me +## 四,About Me * Author: @luofei614 新浪微博:http://weibo.com/luofei614 * 优伯立信创始人,ThinkPHP核心开发者之一,待过新浪云计算 - - [1]: https://github.com/luofei614/SocketLog/raw/master/screenshots/discuz.png - [2]: https://github.com/luofei614/SocketLog/raw/master/screenshots/socketlog.png - [3]: https://github.com/luofei614/SocketLog/raw/master/screenshots/sql.png - [4]: https://github.com/luofei614/SocketLog/raw/master/screenshots/onethink.png +[1]: https://github.com/luofei614/SocketLog/raw/master/screenshots/discuz.png +[2]: https://github.com/luofei614/SocketLog/raw/master/screenshots/socketlog.png +[3]: https://github.com/luofei614/SocketLog/raw/master/screenshots/sql.png +[4]: https://github.com/luofei614/SocketLog/raw/master/screenshots/onethink.png diff --git a/screenshots/SocketLog-Options-env.png b/screenshots/SocketLog-Options-env.png new file mode 100644 index 0000000000000000000000000000000000000000..01d5ca00aff18f04a2a3a9aa08058ed122809811 GIT binary patch literal 25626 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�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 TDG$8!15QCkMY=-LH0XZ;sKuF= literal 0 HcmV?d00001 diff --git a/screenshots/SocketLog-detail-msg.png b/screenshots/SocketLog-detail-msg.png new file mode 100644 index 0000000000000000000000000000000000000000..fd22338d8224f4a1fc2084b1ba889cb89d4d459c GIT binary patch literal 19136 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 literal 0 HcmV?d00001 diff --git a/screenshots/SocketLog-extension-options.png b/screenshots/SocketLog-extension-options.png new file mode 100644 index 0000000000000000000000000000000000000000..c3fd6bc5a6f7501509bb2f60793c145ec9169237 GIT binary patch literal 25446 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 Date: Wed, 27 Jan 2021 23:00:26 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 4 + chrome/background.js | 209 ------------ chrome/evn.js | 38 --- chrome/images/logo_128.png | Bin 0 -> 9320 bytes chrome/images/logo_16.png | Bin 0 -> 1605 bytes chrome/images/logo_64.png | Bin 0 -> 4732 bytes chrome/images/logo_disabled_128.png | Bin 0 -> 7404 bytes chrome/images/logo_disabled_16.png | Bin 0 -> 1499 bytes chrome/images/logo_disabled_64.png | Bin 0 -> 3752 bytes chrome/js/background.js | 207 ++++++++++++ chrome/js/evn.js | 29 ++ chrome/js/log.js | 27 ++ chrome/js/options.js | 19 ++ chrome/js/popup.js | 176 ++++++++++ chrome/log.js | 38 --- chrome/manifest.json | 59 ++-- chrome/options.html | 212 ++++++------ chrome/options.js | 25 -- chrome/popup.html | 248 ++++++++++---- chrome/popup.js | 49 --- composer.json | 42 ++- demo.php | 4 +- logo.png | Bin 0 -> 7448 bytes php/slog.function.php | 35 +- php/slog.php | 484 ++++++++++++++-------------- server/config.json | 10 +- server/index.html | 157 ++++----- server/index.js | 61 ++-- 28 files changed, 1179 insertions(+), 954 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 chrome/background.js delete mode 100644 chrome/evn.js create mode 100644 chrome/images/logo_128.png create mode 100644 chrome/images/logo_16.png create mode 100644 chrome/images/logo_64.png create mode 100644 chrome/images/logo_disabled_128.png create mode 100644 chrome/images/logo_disabled_16.png create mode 100644 chrome/images/logo_disabled_64.png create mode 100644 chrome/js/background.js create mode 100644 chrome/js/evn.js create mode 100644 chrome/js/log.js create mode 100644 chrome/js/options.js create mode 100644 chrome/js/popup.js delete mode 100644 chrome/log.js delete mode 100644 chrome/options.js delete mode 100644 chrome/popup.js create mode 100644 logo.png diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5f3c755 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "php.validate.executablePath": "D:\\Program Files\\php-7.4.14\\php.exe", + "php.executablePath": "D:\\Program Files\\php-7.4.14\\php.exe" +} \ No newline at end of file diff --git a/chrome/background.js b/chrome/background.js deleted file mode 100644 index 73c9420..0000000 --- a/chrome/background.js +++ /dev/null @@ -1,209 +0,0 @@ -/** - * github: https://github.com/luofei614/SocketLog - * @author luofei614 - */ -var websocket=null; -var websocket_timeout=0; -function ws_init() -{ - if(websocket) - { - //避免重复监听 - websocket.onclose=function(){};//onclose 函数置空,防止重复链接 - websocket.close(); - } - var address=localStorage.getItem('address'); - var client_id=localStorage.getItem('client_id'); - if(!address) - { - address="ws://localhost:1229"; - } - if(client_id) - { - //client_id作为地址 - address+='/'+client_id; - } - websocket=new WebSocket(address); - - websocket.onerror=function(msg) - { - clearTimeout(websocket_timeout); - websocket_timeout=setTimeout(ws_init,2000); - localStorage.setItem('status','error'); - disable_icon(); - }; - - websocket.onclose=function() - { - setTimeout(function(){ - clearTimeout(websocket_timeout); - websocket_timeout=setTimeout(ws_init,2000); - },1000); - localStorage.setItem('status','close'); - disable_icon(); - } - - websocket.onopen=function() - { - localStorage.setItem('status','open'); - enable_icon(); - } - - - websocket.onmessage=function(event){ - - var check_error=function() - { - if(event.data.indexOf('SocketLog error handler')!='-1') - { - var opt = { - type: "basic", - title: "注意", - message: "有异常报错,请注意查看console 控制台中的日志", - iconUrl: "logo.png" - }; - chrome.notifications.create('',opt,function(id){ - setTimeout(function(){ - chrome.notifications.clear(id,function(){}); - },3000); - }); - - } - - - if(event.data.indexOf('[NO WHERE]')!='-1') - { - var opt = { - type: "basic", - title: "注意", - message: "存在没有WHERE语句的操作sql语句", - iconUrl: "logo.png" - }; - chrome.notifications.create('',opt,function(id){ - setTimeout(function(){ - chrome.notifications.clear(id,function(){}); - },3000); - }); - } - - }; - - try - { - var data=JSON.parse(event.data); - } - catch(e) - { - if(0==event.data.indexOf('close:')){ - websocket.onclose=function(){};//onclose 函数置空,防止重复链接 - alert('此client_id不允许连接服务'); - }else{ - alert('日志格式错误,'+event.data); - } - return ; - } - var client_id=localStorage.getItem('client_id'); - //判断是否有强制日志 - if(client_id && data.force_client_id==client_id) - { - //将强制日志输出到当前的tab页 - chrome.tabs.query( - {currentWindow: true, active: true}, - function(tabArray) { - if (tabArray && tabArray[0]) - { - //延迟保证日志每次都能记录 - setTimeout(function(){ - check_error(); - chrome.tabs.sendMessage(tabArray[0].id,data.logs); - },100); - } - } - ); - return ; - } - - if((client_id && data.client_id!=client_id) || !data.tabid) - { - //不是当前用户的日志不显示。 - return ; - } - //延迟保证日志每次都能记录 - setTimeout(function(){ - check_error(); - chrome.tabs.sendMessage(parseInt(data.tabid),data.logs); - },100); - }; -} - -function ws_restart() -{ - ws_init(); -} - - -function enable_icon() { - chrome.browserAction.setIcon({ - path: "logo.png" - }); -} - -function disable_icon() { - chrome.browserAction.setIcon({ - path: "logo_disabled.png" - }); -} - - - -ws_init(); - - -function url_exp(url) -{ - var splatParam = /\*/g; - var escapeRegExp = /[-[\]{}()+?.,\\^$#\s]/g; - url = url.replace(escapeRegExp, '\\$&') - .replace(splatParam, '(.*?)'); - return new RegExp(url, 'i'); -} - -chrome.webRequest.onBeforeSendHeaders.addListener( - function(details) { - - var header="tabid="+details.tabId; - - var client_id=localStorage.getItem('client_id'); - if(!client_id) - { - client_id=''; - } - - - header+="&client_id="+client_id; - - //将Header隐藏在User-Agent中, 不能使用自定义Header了, 不让HTTPS情况下会报不安全 - for (var i = 0; i < details.requestHeaders.length; ++i) - { - if (details.requestHeaders[i].name === 'User-Agent') - { - details.requestHeaders[i].value+=" SocketLog("+header+")"; - break; - } - } - - return {requestHeaders: details.requestHeaders}; - }, - {urls: [""]}, - ["blocking", "requestHeaders"]); - -chrome.webRequest.onCompleted.addListener(function(details){ - var online_domain=localStorage.getItem('online_domain'); - if(online_domain){ - var exp=url_exp(online_domain); - if(exp.test(details.url)) - { - chrome.tabs.sendMessage(details.tabId,'online_evn'); - } - } -},{urls: [""],types:['main_frame','sub_frame']}); diff --git a/chrome/evn.js b/chrome/evn.js deleted file mode 100644 index 13c0587..0000000 --- a/chrome/evn.js +++ /dev/null @@ -1,38 +0,0 @@ -//环境标示,识别正式环境 -chrome.extension.onMessage.addListener( -function(logs) -{ - if('online_evn'==logs) - { - if('complete'!=document.readyState){ - document.addEventListener('DOMContentLoaded', show_online_evn, true); - } - else - { - show_online_evn(); - } - } -}); - -function show_online_evn() -{ - if(window.top==window || window.top.document.getElementsByTagName('frameset').length>0) - { - if(!document.getElementById('_socketlog_online_evn_span')) - { - //标记正式环境 - var mySpan = document.createElement("span"); - mySpan.innerHTML = "正式环境,请谨慎操作!"; - mySpan.style.color = "#fff"; - mySpan.id="_socketlog_online_evn_span"; - mySpan.style.backgroundColor = "red"; - mySpan.style.position="absolute"; - mySpan.style.top="10px"; - mySpan.style.left="10px"; - mySpan.style.padding="5px"; - mySpan.style.zIndex="10000"; - document.body.appendChild(mySpan); - } - } -} - diff --git a/chrome/images/logo_128.png b/chrome/images/logo_128.png new file mode 100644 index 0000000000000000000000000000000000000000..fcf2d7097b186b1325d373260762750ffacc81ec GIT binary patch literal 9320 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 literal 0 HcmV?d00001 diff --git a/chrome/images/logo_64.png b/chrome/images/logo_64.png new file mode 100644 index 0000000000000000000000000000000000000000..c9d63ecbe07fc8e3fd227cb9599d700150a481c3 GIT binary patch literal 4732 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 literal 0 HcmV?d00001 diff --git a/chrome/images/logo_disabled_64.png b/chrome/images/logo_disabled_64.png new file mode 100644 index 0000000000000000000000000000000000000000..7c87b708c20ea414fb504fbc62bf807586410ad4 GIT binary patch literal 3752 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 KYmM`<^Zx^aikn;j literal 0 HcmV?d00001 diff --git a/chrome/js/background.js b/chrome/js/background.js new file mode 100644 index 0000000..18e05a4 --- /dev/null +++ b/chrome/js/background.js @@ -0,0 +1,207 @@ +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +var websocket = null; +var websocket_timeout = 0; +var limit_connect = 1; // 断线重连次数 +var count = 0; // 重连计数 + +function ws_init() { + if (websocket) { + //避免重复监听 + websocket.onclose = function() {}; //onclose 函数置空,防止重复链接 + websocket.close(); + } + var protocol = localStorage.getItem("protocol"); + var address = localStorage.getItem("address"); + var port = localStorage.getItem("port"); + var full_address = ""; + var client_id = localStorage.getItem("client_id"); + var open = localStorage.getItem("open"); + + if (!protocol) { + protocol = "ws"; + } + if (!address) { + address = "localhost"; + } + if (!port) { + port = "1229"; + } + + full_address = protocol + "://" + address + ":" + port; + + if (client_id) { + //client_id作为地址 + full_address += "/" + client_id; + } + + if (open == "false" || open == null) { + disable_icon(); + localStorage.setItem("status", "close"); + return false; + } + + websocket = new WebSocket(full_address); + + websocket.onerror = function(msg) { + localStorage.setItem("status", "error"); + disable_icon(); + }; + + websocket.onclose = function() { + localStorage.setItem("status", "close"); + disable_icon(); + }; + + websocket.onopen = function() { + localStorage.setItem("status", "open"); + enable_icon(); + }; + + websocket.onmessage = function(event) { + var check_error = function() { + if (event.data.indexOf("SocketLog error handler") != "-1") { + var opt = { + type: "basic", + title: "注意", + message: "有异常报错,请注意查看 Console 控制台中的日志", + iconUrl: "logo.png", + }; + chrome.notifications.create("", opt, function(id) { + setTimeout(function() { + chrome.notifications.clear(id, function() {}); + }, 3000); + }); + } + + if (event.data.indexOf("[NO WHERE]") != "-1") { + var opt = { + type: "basic", + title: "注意", + message: "存在没有 WHERE 条件的操作 SQL 语句", + iconUrl: "logo.png", + }; + chrome.notifications.create("", opt, function(id) { + setTimeout(function() { + chrome.notifications.clear(id, function() {}); + }, 3000); + }); + } + }; + + try { + var data = JSON.parse(event.data); + } catch (e) { + if (0 == event.data.indexOf("close:")) { + websocket.onclose = function() {}; //onclose 函数置空,防止重复链接 + alert("此 client_id 不允许连接服务"); + } else { + alert("日志格式错误," + event.data); + } + return; + } + var client_id = localStorage.getItem("client_id"); + //判断是否有强制日志 + if (client_id && data.force_client_id == client_id) { + //将强制日志输出到当前的tab页 + chrome.tabs.query({ currentWindow: true, active: true }, + function(tabArray) { + if (tabArray && tabArray[0]) { + //延迟保证日志每次都能记录 + setTimeout(function() { + check_error(); + chrome.tabs.sendMessage(tabArray[0].id, data.logs); + }, 100); + } + } + ); + return; + } + + if ((client_id && data.client_id != client_id) || !data.tabid) { + //不是当前用户的日志不显示。 + return; + } + //延迟保证日志每次都能记录 + setTimeout(function() { + check_error(); + chrome.tabs.sendMessage(parseInt(data.tabid), data.logs); + }, 100); + }; +} + +function ws_restart() { + ws_init(); +} + +function enable_icon() { + chrome.browserAction.setIcon({ + path: "images/logo_16.png", + }); +} + +function disable_icon() { + chrome.browserAction.setIcon({ + path: "images/logo_disabled_16.png", + }); +} + +ws_init(); + +function url_exp(url) { + var splatParam = /\*/g; + var escapeRegExp = /[-[\]{}()+?.,\\^$#\s]/g; + url = url.replace(escapeRegExp, "\\$&").replace(splatParam, "(.*?)"); + return new RegExp(url, "i"); +} + +chrome.webRequest.onBeforeSendHeaders.addListener( + function(details) { + var header = "tabid=" + details.tabId; + var client_id = localStorage.getItem("client_id"); + + if (!client_id) { + client_id = ""; + } + + header += "&client_id=" + client_id; + + var special_domain = localStorage.getItem("special_domain"); + if (!special_domain) { + special_domain = "*.sinaapp.com"; + localStorage.setItem("special_domain", special_domain); + } + var exp = url_exp(special_domain); + if (exp.test(details.url)) { + //如果是特殊环境域名 + for (var i = 0; i < details.requestHeaders.length; ++i) { + if (details.requestHeaders[i].name === "User-Agent") { + //将参数放在User-agent中,兼容SAE的情况 + details.requestHeaders[i].value += " SocketLog(" + header + ")"; + break; + } + } + } else { + details.requestHeaders.push({ + name: "SocketLog", + value: " SocketLog(" + header + ")", + }); + } + + return { requestHeaders: details.requestHeaders }; + }, { urls: [""] }, ["blocking", "requestHeaders"] +); + +chrome.webRequest.onCompleted.addListener( + function(details) { + var online_domain = localStorage.getItem("online_domain"); + if (online_domain) { + var exp = url_exp(online_domain); + if (exp.test(details.url)) { + chrome.tabs.sendMessage(details.tabId, "online_evn"); + } + } + }, { urls: [""], types: ["main_frame", "sub_frame"] } +); diff --git a/chrome/js/evn.js b/chrome/js/evn.js new file mode 100644 index 0000000..120de2b --- /dev/null +++ b/chrome/js/evn.js @@ -0,0 +1,29 @@ +//环境标示,识别正式环境 +chrome.extension.onMessage.addListener(function(logs) { + if ("online_evn" == logs) { + if ("complete" != document.readyState) { + document.addEventListener("DOMContentLoaded", show_online_evn, true); + } else { + show_online_evn(); + } + } +}); + +function show_online_evn() { + if (window.top == window || window.top.document.getElementsByTagName("frameset").length > 0) { + if (!document.getElementById("_socketlog_online_evn_span")) { + //标记正式环境 + var mySpan = document.createElement("span"); + mySpan.innerHTML = "正式环境,请谨慎操作!"; + mySpan.style.color = "#fff"; + mySpan.id = "_socketlog_online_evn_span"; + mySpan.style.backgroundColor = "red"; + mySpan.style.position = "absolute"; + mySpan.style.top = "10px"; + mySpan.style.left = "10px"; + mySpan.style.padding = "5px"; + mySpan.style.zIndex = "10000"; + document.body.appendChild(mySpan); + } + } +} \ No newline at end of file diff --git a/chrome/js/log.js b/chrome/js/log.js new file mode 100644 index 0000000..c10e558 --- /dev/null +++ b/chrome/js/log.js @@ -0,0 +1,27 @@ +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +chrome.extension.onMessage.addListener( + function(logs) { + if ("object" != typeof logs) { + return; + } + logs.forEach(function(log) { + if (console[log.type]) { + if (log.css) { + console[log.type]("%c" + log.msg, log.css); + } else { + console[log.type](log.msg); + } + return; + } + + if ("alert" == log.type) { + alert(log.msg); + } else { + alert("SocketLog type error, " + log.type); + } + }); + } +); \ No newline at end of file diff --git a/chrome/js/options.js b/chrome/js/options.js new file mode 100644 index 0000000..d966d34 --- /dev/null +++ b/chrome/js/options.js @@ -0,0 +1,19 @@ +document.addEventListener('DOMContentLoaded', init, false); + +function init() { + if (localStorage.getItem('online_domain')) { + document.getElementById('online_domain').value = localStorage.getItem('online_domain'); + } + + if (localStorage.getItem('special_domain')) { + document.getElementById('special_domain').value = localStorage.getItem('special_domain'); + } + + document.getElementById('save').addEventListener('click', save, false); +} + +function save() { + localStorage.setItem('online_domain', document.getElementById('online_domain').value); + localStorage.setItem('special_domain', document.getElementById('special_domain').value); + alert('保存成功'); +} \ No newline at end of file diff --git a/chrome/js/popup.js b/chrome/js/popup.js new file mode 100644 index 0000000..d492578 --- /dev/null +++ b/chrome/js/popup.js @@ -0,0 +1,176 @@ +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +document.addEventListener('DOMContentLoaded', init, false); +/** + * 初始化函数 + * @return {[type]} [description] + */ +function init() { + var protocol = document.getElementById('protocol'); + var address = document.getElementById('address'); + var port = document.getElementById('port'); + var full_address = document.getElementById('full_address'); + var client_id = document.getElementById('client_id'); + var open = document.getElementById('open'); + + if (localStorage.getItem('protocol')) { + protocol.value = localStorage.getItem('protocol'); + } + if (localStorage.getItem('address')) { + address.value = localStorage.getItem('address'); + } + if (localStorage.getItem('port')) { + port.value = localStorage.getItem('port'); + } + + full_address.value = protocol.value + '://' + address.value + ':' + port.value; + + if (localStorage.getItem('client_id')) { + client_id.value = localStorage.getItem('client_id'); + } + if (localStorage.getItem('open')) { + open.checked = localStorage.getItem('open') == 'false' ? false : true; + } + // 显示连接状态 + var status = localStorage.getItem('status'); + viewStatusNotic(status); + + document.getElementById('save').addEventListener('click', save, false); + + protocol.addEventListener('input', addressChange, false); + address.addEventListener('input', addressChange, false); + port.addEventListener('input', addressChange, false); + + protocol.addEventListener('porpertychange', addressChange, false); + address.addEventListener('porpertychange', addressChange, false); + port.addEventListener('porpertychange', addressChange, false); + +} +/** + * 检测主机地址变化,实时各项配置值 + * @param {[type]} e [description] + * @return {[type]} [description] + */ +function addressChange(e) { + var protocol = document.getElementById('protocol').value; + var address = document.getElementById('address').value; + var port = document.getElementById('port').value; + document.getElementById('full_address').value = protocol + '://' + address + ':' + port; +} +/** + * 辅助函数,检测一个值 IP 是否合法 + * @param {[type]} ip [description] + * @return {Boolean} [description] + */ +function isValidIP(ip) { + var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; + return reg.test(ip); +} +/** + * 辅助函数,检测一个值域名是否合法 + * @param {[type]} domain [description] + * @return {Boolean} [description] + */ +function isValidDomain(domain) { + var reg = /^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+.?$/; + return reg.test(domain); +} +/** + * 辅助函数,检测一个值是否在数组中 + * @param {[type]} search [description] + * @param {[type]} array [description] + * @return {[type]} [description] + */ +function inArray(search, array) { + for (var i in array) { + if (array[i] == search) { + return true; + } + } + return false; +} + +/** + * 显示弹窗 + * @param {[type]} status [description] + * @return {[type]} [description] + */ +function viewStatusNotic(status) { + if (status) { + var text = '', color = ''; + + switch (status) { + case "connecting": + text = '正在连接...'; + color = '#64bd63'; + break; + case "open": + text = '链接成功'; + color = '#64bd63'; + break; + case "close": + text = '链接断开'; + color = 'gray'; + break; + case "error": + text = '链接失败'; + color = 'red'; + break; + default: + alert('运行状态异常'); + break; + } + document.getElementById('status-div').style.color = color; + document.getElementById('status').innerHTML = text; + } +} + +/** + * 保存配置项 + * @return {[type]} [description] + */ +function save() { + // 获取配置项的值 + var protocol = document.getElementById('protocol').value; + var address = document.getElementById('address').value; + var port = document.getElementById('port').value; + var client_id = document.getElementById('client_id').value; + var open = document.getElementById('open').checked; + + // 校验主机地址和协议 + var allow_protocols = ['ws', 'wss']; + var ipOk = isValidIP(address); + var domainOk = isValidDomain(address); + + if (!inArray(protocol, allow_protocols)) { + alert('不支持的协议类型'); + var status = 'error'; + viewStatusNotic(status); + return false; + } + if (!ipOk || !domainOk) { + alert('不是合法的 IP 地址或域名'); + var status = 'error'; + viewStatusNotic(status); + return false; + } + + // 保存配置项的值 + localStorage.setItem('protocol', protocol); + localStorage.setItem('address', address); + localStorage.setItem('port', port); + localStorage.setItem('client_id', client_id); + localStorage.setItem('open', open); + // 触发重新连接 + chrome.extension.getBackgroundPage().ws_restart(); + // 显示连接状态 + var status = localStorage.getItem('status'); + console.log(status); + viewStatusNotic(status); + // 如果连接成功,关闭当前设置弹出 + if (status == 'open') { + window.close(); + } +} diff --git a/chrome/log.js b/chrome/log.js deleted file mode 100644 index fe7390d..0000000 --- a/chrome/log.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * github: https://github.com/luofei614/SocketLog - * @author luofei614 - */ -chrome.extension.onMessage.addListener( -function(logs) -{ - if('object'!=typeof(logs)) - { - return ; - } - logs.forEach(function(log) - { - if(console[log.type]) - { - if(log.css) - { - console[log.type]('%c'+log.msg,log.css); - } - else - { - console[log.type](log.msg); - } - return ; - } - - if('alert'==log.type) - { - alert(log.msg); - } - else - { - alert('SocketLog type error, '+log.type); - } - }); -}); - - diff --git a/chrome/manifest.json b/chrome/manifest.json index 0657033..96b3be2 100644 --- a/chrome/manifest.json +++ b/chrome/manifest.json @@ -1,30 +1,37 @@ { - "manifest_version": 2, + "manifest_version": 2, - "name": "SocketLog", - "description": "SocketLog是FirePHP和ChromePHP的替代者,不仅能做ajax调试,还能做API调试。", - "version": "2.2.0", - "permissions": [ "webRequest","webRequestBlocking", "tabs","notifications", "http://*/*", "https://*/*" ], - "content_scripts": [ - { - "js": [ "log.js" ], - "matches": [ "http://*/*", "https://*/*" ], - "run_at": "document_start" + "name": "SocketLog", + "description": "SocketLog 是 FirePHP 和 ChromePHP 的替代者,不仅能做 ajax 调试,还能做 API 调试。", + "version": "2.2.0", + "permissions": ["webRequest", "webRequestBlocking", "tabs", "notifications", "http://*/*", "https://*/*"], + "content_scripts": [{ + "js": ["js/log.js"], + "matches": ["http://*/*", "https://*/*"], + "run_at": "document_start" + }, + { + "js": ["js/evn.js"], + "matches": ["http://*/*", "https://*/*"], + "run_at": "document_start", + "all_frames": true + } + ], + "background": { + "scripts": ["js/background.js"] }, - { - "js": [ "evn.js" ], - "matches": [ "http://*/*", "https://*/*" ], - "run_at": "document_start", - "all_frames":true + "options_page": "options.html", + "icons": { + "16": "images/logo_16.png", + "64": "images/logo_64.png", + "128": "images/logo_128.png" + }, + "browser_action": { + "default_icon": { + "16": "images/logo_16.png", + "64": "images/logo_64.png", + "128": "images/logo_128.png" + }, + "default_popup": "popup.html" } - ], - "background":{ - "scripts":["background.js"] - }, - "options_page": "options.html", - "browser_action": { - "default_icon": "logo.png", - "default_popup": "popup.html" - } - -} +} \ No newline at end of file diff --git a/chrome/options.html b/chrome/options.html index 97dd8e2..60807ae 100644 --- a/chrome/options.html +++ b/chrome/options.html @@ -1,119 +1,123 @@ + - - Chrome Logger options - - - - - -
-

SocketLog Options

-
-
-
-
- - + \ No newline at end of file diff --git a/chrome/options.js b/chrome/options.js deleted file mode 100644 index 0924d24..0000000 --- a/chrome/options.js +++ /dev/null @@ -1,25 +0,0 @@ -document.addEventListener('DOMContentLoaded', init, false); -function init() -{ - - if(localStorage.getItem('online_domain')) - { - document.getElementById('online_domain').value=localStorage.getItem('online_domain'); - } - - - if(localStorage.getItem('special_domain')) - { - document.getElementById('special_domain').value=localStorage.getItem('special_domain'); - } - - document.getElementById('save').addEventListener('click',save,false); -} - - -function save() -{ - localStorage.setItem('online_domain',document.getElementById('online_domain').value); - localStorage.setItem('special_domain',document.getElementById('special_domain').value); - alert('保存成功'); -} diff --git a/chrome/popup.html b/chrome/popup.html index 822a533..e29b395 100644 --- a/chrome/popup.html +++ b/chrome/popup.html @@ -1,71 +1,193 @@ - + + - - + + - + + -
-
SocketLog 设置
-
- 监听地址:
- Client_ID:
- -
- 状态:正在连接... -
- -
-
-
-
+
+
SocketLog 设置
+
+ 监听主机:
监听协议:
监听端口:
监听地址: + Client_ID:
+
+ 开启: +
+ +
+ 状态:正在连接... +
+ +
+ +
+ +
+
- + + \ No newline at end of file diff --git a/chrome/popup.js b/chrome/popup.js deleted file mode 100644 index e4c7780..0000000 --- a/chrome/popup.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * github: https://github.com/luofei614/SocketLog - * @author luofei614 - */ -document.addEventListener('DOMContentLoaded', init, false); -function init() -{ - if(localStorage.getItem('address')) - { - document.getElementById('address').value=localStorage.getItem('address'); - } - - if(localStorage.getItem('client_id')) - { - document.getElementById('client_id').value=localStorage.getItem('client_id'); - } - var status=localStorage.getItem('status'); - if(status) - { - var text=''; - - switch(status) - { - case "open": - text='链接成功'; - break; - case "close": - text='链接断开'; - break; - case "error": - text='链接失败'; - break; - default: - alert('运行状态异常'); - break; - } - document.getElementById('status').innerHTML=text; - } - - document.getElementById('save').addEventListener('click',save,false); -} - -function save() -{ - localStorage.setItem('address',document.getElementById('address').value); - localStorage.setItem('client_id',document.getElementById('client_id').value); - chrome.extension.getBackgroundPage().ws_restart(); - window.close(); -} diff --git a/composer.json b/composer.json index 74b954b..c0ce5c7 100644 --- a/composer.json +++ b/composer.json @@ -1,24 +1,22 @@ { - "name": "luofei614/socketlog", - "description": "debug tool for api , ajax,wechat ,v2.3", - "keywords": ["SocketLog", "slog", "api"], - "license": "MIT", - "authors": [ - { - "name": "luofei", - "email": "upfy@qq.com" - } - ], - "require":{ - "php": ">=5.4.0" - }, - "autoload": { - "psr-4": { - "think\\org\\": "php/" - } - }, - "archive" : { - "exclude":["!/php"] - }, - "minimum-stability": "dev" + "name": "luofei614/socketlog", + "description": "debug tool for api , ajax,wechat ,v2.3", + "keywords": ["SocketLog", "slog", "api"], + "license": "MIT", + "authors": [{ + "name": "luofei", + "email": "upfy@qq.com" + }], + "require": { + "php": ">=5.4.0" + }, + "autoload": { + "psr-4": { + "think\\org\\": "php/" + } + }, + "archive": { + "exclude": ["!/php"] + }, + "minimum-stability": "dev" } \ No newline at end of file diff --git a/demo.php b/demo.php index 56b9675..7b5097a 100644 --- a/demo.php +++ b/demo.php @@ -10,12 +10,12 @@ 'force_client_ids' => array(//日志强制记录到配置的client_id,默认为空,client_id必须在allow_client_ids中 //'client_01', //'client_02', - ), + ), 'allow_client_ids' => array(//限制允许读取日志的client_id,默认为空,表示所有人都可以获得日志。 //'client_01', //'client_02', //'client_03', - ), + ), ),'config'); //输出日志 diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b7fbac0c5454a60b811a3c8908f08de927dde1cc GIT binary patch literal 7448 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 */ + namespace think\org; + 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'); + 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 $_allowForceClientIds = array(); //配置强制推送且被授权的client_id protected static $_instance; - protected static $config=array( - 'enable'=>true, //是否记录日志的开关 - 'host'=>'localhost', + protected static $config = array( + 'enable' => true, //是否记录日志的开关 + 'host' => 'localhost', //是否显示利于优化的参数,如果允许时间,消耗内存等 - 'optimize'=>false, - 'show_included_files'=>false, - 'error_handler'=>false, + 'optimize' => false, + 'show_included_files' => false, + 'error_handler' => false, //日志强制记录到配置的client_id - 'force_client_ids'=>array(), + 'force_client_ids' => array(), //限制允许读取日志的client_id - 'allow_client_ids'=>array() + 'allow_client_ids' => array() ); - protected static $logs=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;' + 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) + 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); + 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) + public static function sql($sql, $link) { - if(is_object($link) && 'mysqli'==get_class($link)) - { - return self::mysqlilog($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_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); + if (is_object($link) && 'PDO' == get_class($link)) { + return self::pdolog($sql, $link); } throw new Exception('SocketLog can not support this database link'); @@ -71,125 +70,113 @@ public static function sql($sql,$link) public static function big($log) { - self::log($log,'font-size:20px;color:red;'); + self::log($log, 'font-size:20px;color:red;'); } - public static function trace($msg,$trace_level=1,$css='') + public static function trace($msg, $trace_level = 1, $css = '') { - if(!self::check()) - { - return ; + if (!self::check()) { + return; } - self::groupCollapsed($msg,$css); - $traces=debug_backtrace(false); - $traces=array_reverse($traces); + self::groupCollapsed($msg, $css); + $traces = debug_backtrace(false); + $traces = array_reverse($traces); $trace_level = $trace_level == '' ? 0 : intval($trace_level); - $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.']'; + $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::log($trace_msg); //} } self::groupEnd(); } - public static function mysqlilog($sql,$db) + public static function mysqlilog($sql, $db) { - if(!self::check()) - { - return ; + if (!self::check()) { + return; } - $css=self::$css['sql']; - if(preg_match('/^SELECT /i', $sql)) - { + $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); + $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); + self::sqlwhere($sql, $css); + self::trace($sql, 2, $css); } - public static function mysqllog($sql,$db) + public static function mysqllog($sql, $db) { - if(!self::check()) - { - return ; + if (!self::check()) { + return; } - $css=self::$css['sql']; - if(preg_match('/^SELECT /i', $sql)) - { + $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); + $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); + self::sqlwhere($sql, $css); + self::trace($sql, 2, $css); } - public static function pdolog($sql,$pdo) + public static function pdolog($sql, $pdo) { - if(!self::check()) - { - return ; + if (!self::check()) { + return; } - $css=self::$css['sql']; - if(preg_match('/^SELECT /i', $sql)) - { + $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); + $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); + self::sqlwhere($sql, $css); + self::trace($sql, 2, $css); } - private static function sqlexplain($arr,&$sql,&$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 filesort')) { + $sql .= ' <---################[Using filesort]'; + $css = self::$css['sql_warn']; } - if(false!==strpos($arr['extra'],'Using temporary')) - { - $sql.=' <---################[Using temporary]'; - $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) + 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']; + if (preg_match('/^UPDATE |DELETE /i', $sql) && !preg_match('/WHERE.*(=|>|<|LIKE|IN)/i', $sql)) { + $sql .= '<---###########[NO WHERE]'; + $css = self::$css['sql_warn']; } } @@ -199,45 +186,73 @@ private static function sqlwhere(&$sql,&$css) */ public static function registerErrorHandler() { - if(!self::check()) - { - return ; + if (!self::check()) { + return; } - set_error_handler(array(__CLASS__,'error_handler')); - register_shutdown_function(array(__CLASS__,'fatalError')); + 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']); + 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 + if ($e = error_get_last()) { + self::error_handler($e['type'], $e['message'], $e['file'], $e['line']); + self::sendLog(); //此类终止不会调用类的 __destruct 方法,所以此处手动sendLog } } @@ -254,34 +269,28 @@ public static function getInstance() protected static function check() { - if(!self::getConfig('enable')) - { + if (!self::getConfig('enable')) { return false; } - $tabid=self::getClientArg('tabid'); - //是否记录日志的检查 - if(!$tabid && !self::getConfig('force_client_ids')) - { + $tabid = self::getClientArg('tabid'); + //是否记录日志的检查 + if (!$tabid && !self::getConfig('force_client_ids')) { return false; } //用户认证 - $allow_client_ids=self::getConfig('allow_client_ids'); - if(!empty($allow_client_ids)) - { + $allow_client_ids = self::getConfig('allow_client_ids'); + if (!empty($allow_client_ids)) { //通过数组交集得出授权强制推送的client_id self::$_allowForceClientIds = array_intersect($allow_client_ids, self::getConfig('force_client_ids')); if (!$tabid && count(self::$_allowForceClientIds)) { return true; } - $client_id=self::getClientArg('client_id'); - if(!in_array($client_id,$allow_client_ids)) - { + $client_id = self::getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { return false; } - } - else - { + } else { self::$_allowForceClientIds = self::getConfig('force_client_ids'); } return true; @@ -289,7 +298,7 @@ protected static function check() protected static function getClientArg($name) { - static $args=array(); + static $args = array(); $key = 'HTTP_USER_AGENT'; @@ -297,21 +306,17 @@ protected static function getClientArg($name) $key = 'HTTP_SOCKETLOG'; } - if(!isset($_SERVER[$key])) - { + if (!isset($_SERVER[$key])) { return null; } - if(empty($args)) - { - if(!preg_match('/SocketLog\((.*?)\)/',$_SERVER[$key],$match)) - { - $args=array('tabid'=>null); + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = array('tabid' => null); return null; } - parse_str($match[1],$args); + parse_str($match[1], $args); } - if(isset($args[$name])) - { + if (isset($args[$name])) { return $args[$name]; } return null; @@ -319,25 +324,22 @@ protected static function getClientArg($name) //设置配置 - public static function config($config) + public static function config($config) { - $config=array_merge(self::$config,$config); - if(isset($config['force_client_id'])){ + $config = array_merge(self::$config, $config); + if (isset($config['force_client_id'])) { //兼容老配置 - $config['force_client_ids']=array_merge($config['force_client_ids'],array($config['force_client_id'])); + $config['force_client_ids'] = array_merge($config['force_client_ids'], array($config['force_client_id'])); } - self::$config=$config; - if(self::check()) - { + 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['optimize']) { + self::$start_time = microtime(true); + self::$start_memory = memory_get_usage(); } - if($config['error_handler']) - { + if ($config['error_handler']) { self::registerErrorHandler(); } } @@ -345,25 +347,25 @@ public static function config($config) //获得配置 - public static function getConfig($name) + public static function getConfig($name) { - if(isset(self::$config[$name])) + if (isset(self::$config[$name])) { return self::$config[$name]; + } return null; } //记录日志 - public function record($type,$msg='',$css='') + public function record($type, $msg = '', $css = '') { - if(!self::check()) - { - return ; + if (!self::check()) { + return; } - self::$logs[]=array( - 'type'=>$type, - 'msg'=>$msg, - 'css'=>$css + self::$logs[] = array( + 'type' => $type, + 'msg' => $msg, + 'css' => $css ); } @@ -373,9 +375,9 @@ public function record($type,$msg='',$css='') * @param string $address - 地址 * @return bool */ - public static function send($host,$message='',$address='/') + public static function send($host, $message = '', $address = '/') { - $url='http://'.$host.':'.self::$port.$address; + $url = 'http://' . $host . ':' . self::$port . $address; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); @@ -383,85 +385,76 @@ public static function send($host,$message='',$address='/') 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" + $headers = array( + "Content-Type: application/json;charset=UTF-8" ); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);//设置header + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header $txt = curl_exec($ch); return true; } public static function sendLog() { - if(!self::check()) - { - return ; + 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]"; + $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 (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']); + 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'] + 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'=>'' + 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' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '' ); - self::$logs[]=array( - 'type'=>'groupEnd', - 'msg'=>'', - 'css'=>'', + self::$logs[] = array( + 'type' => 'groupEnd', + 'msg' => '', + '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=''; + $tabid = self::getClientArg('tabid'); + if (!$client_id = self::getClientArg('client_id')) { + $client_id = ''; } - if(!empty(self::$_allowForceClientIds)) - { + if (!empty(self::$_allowForceClientIds)) { //强制推送到多个client_id - foreach(self::$_allowForceClientIds as $force_client_id) { - $client_id=$force_client_id; + foreach (self::$_allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; self::sendToClient($tabid, $client_id, self::$logs, $force_client_id); } } else { @@ -477,22 +470,21 @@ public static function sendLog() * @param $logs * @param $force_client_id */ - protected static function sendToClient($tabid, $client_id, $logs, $force_client_id) { - $logs=array( - 'tabid'=>$tabid, - 'client_id'=>$client_id, - 'logs'=>$logs, - 'force_client_id'=>$force_client_id, + protected static function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = array( + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, ); - $msg=@json_encode($logs); - $address='/'.$client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 - self::send(self::getConfig('host'),$msg,$address); + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + self::send(self::getConfig('host'), $msg, $address); } public function __destruct() { self::sendLog(); } - } - diff --git a/server/config.json b/server/config.json index f992ccf..abe2106 100644 --- a/server/config.json +++ b/server/config.json @@ -1,6 +1,6 @@ { - //是否开启对client_id的验证 - "client_verify":false, - //能连接服务器的client_id - "client_ids":["luofei_zfH5NbLn"] -} + // 是否开启对 client_id 的验证 + "client_verify": false, + // 能连接服务器的 client_id + "client_ids": ["luofei_zfH5NbLn"] +} \ No newline at end of file diff --git a/server/index.html b/server/index.html index c14f98d..5bf2d7b 100644 --- a/server/index.html +++ b/server/index.html @@ -1,80 +1,89 @@ - - - - ThinkPHP SocketLog服务 - - - - - - + .box { + width: 500px; + margin: 0 auto; + } + + .ps { + margin: 20px auto; + width: 500px; + } + + -Fork me on GitHub -
-
-

ThinkPHP SocketLog服务

-

调试程序、分析源码的好工具

-

具体使用方法,请阅读GitHUb项目上的文档:http://github.com/luofei614

-

点击下方按钮,生成Client_id进行使用

- -
-
-
-