Socket::save()   F
last analyzed

Complexity

Conditions 14
Paths 393

Size

Total Lines 101
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 14
eloc 60
c 3
b 0
f 0
nc 393
nop 1
dl 0
loc 101
ccs 0
cts 47
cp 0
crap 210
rs 3.0208

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: luofei614 <weibo.com/luofei614>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\log\driver;
14
15
use Psr\Container\NotFoundExceptionInterface;
16
use think\App;
17
use think\contract\LogHandlerInterface;
18
19
/**
20
 * github: https://github.com/luofei614/SocketLog
21
 * @author luofei614<weibo.com/luofei614>
22
 */
23
class Socket implements LogHandlerInterface
24
{
25
    protected $app;
26
27
    protected $config = [
28
        // socket服务器地址
29
        'host'                => 'localhost',
30
        // socket服务器端口
31
        'port'                => 1116,
32
        // 是否显示加载的文件列表
33
        'show_included_files' => false,
34
        // 日志强制记录到配置的client_id
35
        'force_client_ids'    => [],
36
        // 限制允许读取日志的client_id
37
        'allow_client_ids'    => [],
38
        // 调试开关
39
        'debug'               => false,
40
        // 输出到浏览器时默认展开的日志级别
41
        'expand_level'        => ['debug'],
42
        // 日志头渲染回调
43
        'format_head'         => null,
44
        // curl opt
45
        'curl_opt'            => [
46
            CURLOPT_CONNECTTIMEOUT => 1,
47
            CURLOPT_TIMEOUT        => 10,
48
        ],
49
    ];
50
51
    protected $css = [
52
        'sql'      => 'color:#009bb4;',
53
        'sql_warn' => 'color:#009bb4;font-size:14px;',
54
        'error'    => 'color:#f4006b;font-size:14px;',
55
        'page'     => 'color:#40e2ff;background:#171717;',
56
        'big'      => 'font-size:20px;color:red;',
57
    ];
58
59
    protected $allowForceClientIds = []; //配置强制推送且被授权的client_id
60
61
    protected $clientArg = [];
62
63
    /**
64
     * 架构函数
65
     * @access public
66
     * @param App   $app
67
     * @param array $config 缓存参数
68
     */
69
    public function __construct(App $app, array $config = [])
70
    {
71
        $this->app = $app;
72
73
        if (!empty($config)) {
74
            $this->config = array_merge($this->config, $config);
75
        }
76
77
        if (!isset($config['debug'])) {
78
            $this->config['debug'] = $app->isDebug();
79
        }
80
    }
81
82
    /**
83
     * 调试输出接口
84
     * @access public
85
     * @param array $log 日志信息
86
     * @return bool
87
     */
88
    public function save(array $log = []): bool
89
    {
90
        if (!$this->check()) {
91
            return false;
92
        }
93
94
        $trace = [];
95
96
        if ($this->config['debug']) {
97
            if ($this->app->exists('request')) {
98
                $currentUri = $this->app->request->url(true);
99
            } else {
100
                $currentUri = 'cmd:' . implode(' ', $_SERVER['argv'] ?? []);
101
            }
102
103
            if (!empty($this->config['format_head'])) {
104
                try {
105
                    $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]);
106
                } catch (NotFoundExceptionInterface $notFoundException) {
107
                    // Ignore exception
108
                }
109
            }
110
111
            // 基本信息
112
            $trace[] = [
113
                'type' => 'group',
114
                'msg'  => $currentUri,
115
                'css'  => $this->css['page'],
116
            ];
117
        }
118
119
        $expandLevel = array_flip($this->config['expand_level']);
120
121
        foreach ($log as $type => $val) {
122
            $trace[] = [
123
                'type' => isset($expandLevel[$type]) ? 'group' : 'groupCollapsed',
124
                'msg'  => '[ ' . $type . ' ]',
125
                'css'  => $this->css[$type] ?? '',
126
            ];
127
128
            foreach ($val as $msg) {
129
                if (!is_string($msg)) {
130
                    $msg = var_export($msg, true);
131
                }
132
                $trace[] = [
133
                    'type' => 'log',
134
                    'msg'  => $msg,
135
                    'css'  => '',
136
                ];
137
            }
138
139
            $trace[] = [
140
                'type' => 'groupEnd',
141
                'msg'  => '',
142
                'css'  => '',
143
            ];
144
        }
145
146
        if ($this->config['show_included_files']) {
147
            $trace[] = [
148
                'type' => 'groupCollapsed',
149
                'msg'  => '[ file ]',
150
                'css'  => '',
151
            ];
152
153
            $trace[] = [
154
                'type' => 'log',
155
                'msg'  => implode("\n", get_included_files()),
156
                'css'  => '',
157
            ];
158
159
            $trace[] = [
160
                'type' => 'groupEnd',
161
                'msg'  => '',
162
                'css'  => '',
163
            ];
164
        }
165
166
        $trace[] = [
167
            'type' => 'groupEnd',
168
            'msg'  => '',
169
            'css'  => '',
170
        ];
171
172
        $tabid = $this->getClientArg('tabid');
173
174
        if (!$clientId = $this->getClientArg('client_id')) {
175
            $clientId = '';
176
        }
177
178
        if (!empty($this->allowForceClientIds)) {
179
            //强制推送到多个client_id
180
            foreach ($this->allowForceClientIds as $forceClientId) {
181
                $clientId = $forceClientId;
182
                $this->sendToClient($tabid, $clientId, $trace, $forceClientId);
183
            }
184
        } else {
185
            $this->sendToClient($tabid, $clientId, $trace, '');
186
        }
187
188
        return true;
189
    }
190
191
    /**
192
     * 发送给指定客户端
193
     * @access protected
194
     * @author Zjmainstay
195
     * @param  $tabid
196
     * @param  $clientId
197
     * @param  $logs
198
     * @param  $forceClientId
199
     */
200
    protected function sendToClient($tabid, $clientId, $logs, $forceClientId)
201
    {
202
        $logs = [
203
            'tabid'           => $tabid,
204
            'client_id'       => $clientId,
205
            'logs'            => $logs,
206
            'force_client_id' => $forceClientId,
207
        ];
208
209
        $msg     = json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR);
210
        $address = '/' . $clientId; //将client_id作为地址, server端通过地址判断将日志发布给谁
211
212
        $this->send($this->config['host'], $this->config['port'], $msg, $address);
213
    }
214
215
    /**
216
     * 检测客户授权
217
     * @access protected
218
     * @return bool
219
     */
220
    protected function check()
221
    {
222
        $tabid = $this->getClientArg('tabid');
223
224
        //是否记录日志的检查
225
        if (!$tabid && !$this->config['force_client_ids']) {
226
            return false;
227
        }
228
229
        //用户认证
230
        $allowClientIds = $this->config['allow_client_ids'];
231
232
        if (!empty($allowClientIds)) {
233
            //通过数组交集得出授权强制推送的client_id
234
            $this->allowForceClientIds = array_intersect($allowClientIds, $this->config['force_client_ids']);
235
            if (!$tabid && count($this->allowForceClientIds)) {
236
                return true;
237
            }
238
239
            $clientId = $this->getClientArg('client_id');
240
            if (!in_array($clientId, $allowClientIds)) {
241
                return false;
242
            }
243
        } else {
244
            $this->allowForceClientIds = $this->config['force_client_ids'];
245
        }
246
247
        return true;
248
    }
249
250
    /**
251
     * 获取客户参数
252
     * @access protected
253
     * @param string $name
254
     * @return string
255
     */
256
    protected function getClientArg(string $name)
257
    {
258
        if (!$this->app->exists('request')) {
259
            return '';
260
        }
261
262
        if (empty($this->clientArg)) {
263
            if (empty($socketLog = $this->app->request->header('socketlog'))) {
264
                if (empty($socketLog = $this->app->request->header('User-Agent'))) {
265
                    return '';
266
                }
267
            }
268
269
            if (!preg_match('/SocketLog\((.*?)\)/', $socketLog, $match)) {
0 ignored issues
show
Bug introduced by
It seems like $socketLog can also be of type array; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

269
            if (!preg_match('/SocketLog\((.*?)\)/', /** @scrutinizer ignore-type */ $socketLog, $match)) {
Loading history...
270
                $this->clientArg = ['tabid' => null, 'client_id' => null];
271
                return '';
272
            }
273
            parse_str($match[1] ?? '', $this->clientArg);
274
        }
275
276
        if (isset($this->clientArg[$name])) {
277
            return $this->clientArg[$name];
278
        }
279
280
        return '';
281
    }
282
283
    /**
284
     * @access protected
285
     * @param string $host    - $host of socket server
286
     * @param int    $port    - $port of socket server
287
     * @param string $message - 发送的消息
288
     * @param string $address - 地址
289
     * @return bool
290
     */
291
    protected function send($host, $port, $message = '', $address = '/')
292
    {
293
        $url = 'http://' . $host . ':' . $port . $address;
294
        $ch  = curl_init();
295
296
        curl_setopt($ch, CURLOPT_URL, $url);
297
        curl_setopt($ch, CURLOPT_POST, true);
298
        curl_setopt($ch, CURLOPT_POSTFIELDS, $message);
299
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
300
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->config['curl_opt'][CURLOPT_CONNECTTIMEOUT] ?? 1);
301
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['curl_opt'][CURLOPT_TIMEOUT] ?? 10);
302
303
        $headers = [
304
            "Content-Type: application/json;charset=UTF-8",
305
        ];
306
307
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header
308
309
        return curl_exec($ch);
310
    }
311
}
312