LogViewer::renderTableRow()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 18
nc 4
nop 1
dl 0
loc 29
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: sheldon
5
 * Date: 18-4-18
6
 * Time: 下午4:59.
7
 */
8
9
namespace Yeelight\Models\Tools\LogViewer;
10
11
/**
12
 * Class LogViewer
13
 *
14
 * @category Yeelight
15
 *
16
 * @package Yeelight\Models\Tools\LogViewer
17
 *
18
 * @author Sheldon Lee <[email protected]>
19
 *
20
 * @license https://opensource.org/licenses/MIT MIT
21
 *
22
 * @link https://www.yeelight.com
23
 */
24
class LogViewer
25
{
26
    /**
27
     * The log file name.
28
     *
29
     * @var string
30
     */
31
    public $file;
32
33
    /**
34
     * The path of log file.
35
     *
36
     * @var string
37
     */
38
    protected $filePath;
39
40
    /**
41
     * Start and end offset in current page.
42
     *
43
     * @var array
44
     */
45
    protected $pageOffset = [];
46
47
    /**
48
     * $levelColors
49
     *
50
     * @var array
51
     */
52
    public static $levelColors = [
53
        'EMERGENCY' => 'black',
54
        'ALERT'     => 'navy',
55
        'CRITICAL'  => 'maroon',
56
        'ERROR'     => 'red',
57
        'WARNING'   => 'orange',
58
        'NOTICE'    => 'light-blue',
59
        'INFO'      => 'aqua',
60
        'DEBUG'     => 'green',
61
    ];
62
63
    /**
64
     * LogViewer constructor.
65
     *
66
     * @param null $file
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $file is correct as it would always require null to be passed?
Loading history...
67
     */
68
    public function __construct($file = null)
69
    {
70
        if (is_null($file)) {
71
            $file = $this->getLastModifiedLog();
72
        }
73
74
        $this->file = $file;
75
76
        $this->getFilePath();
77
    }
78
79
    /**
80
     * Get file path by giving log file name.
81
     *
82
     * @throws \Exception
83
     *
84
     * @return string
85
     */
86
    public function getFilePath()
87
    {
88
        if (!$this->filePath) {
89
            $path = sprintf(storage_path('logs/%s'), $this->file);
90
91
            if (!file_exists($path)) {
92
                throw new \Exception('log not exists!');
93
            }
94
95
            $this->filePath = $path;
96
        }
97
98
        return $this->filePath;
99
    }
100
101
    /**
102
     * Get size of log file.
103
     *
104
     * @return int
105
     */
106
    public function getFilesize()
107
    {
108
        return filesize($this->filePath);
109
    }
110
111
    /**
112
     * Get log file list in storage.
113
     *
114
     * @param int $count count
115
     *
116
     * @return array
117
     */
118
    public function getLogFiles($count = 20)
119
    {
120
        $files = glob(storage_path('logs/*'));
121
        $files = array_combine($files, array_map('filemtime', $files));
122
        arsort($files);
123
124
        $files = array_map('basename', array_keys($files));
125
126
        return array_slice($files, 0, $count);
127
    }
128
129
    /**
130
     * Get the last modified log file.
131
     *
132
     * @return string
133
     */
134
    public function getLastModifiedLog()
135
    {
136
        $logs = $this->getLogFiles();
137
138
        return current($logs);
139
    }
140
141
    /**
142
     * Get previous page url.
143
     *
144
     * @return bool|string
145
     */
146
    public function getPrevPageUrl()
147
    {
148
        if ($this->pageOffset['end'] >= $this->getFilesize() - 1) {
149
            return false;
150
        }
151
152
        return route('tools.log-viewer-file', [
153
            'file' => $this->file, 'offset' => $this->pageOffset['end'],
154
        ]);
155
    }
156
157
    /**
158
     * Get Next page url.
159
     *
160
     * @return bool|string
161
     */
162
    public function getNextPageUrl()
163
    {
164
        if ($this->pageOffset['start'] == 0) {
165
            return false;
166
        }
167
168
        return route('tools.log-viewer-file', [
169
            'file' => $this->file, 'offset' => -$this->pageOffset['start'],
170
        ]);
171
    }
172
173
    /**
174
     * Fetch logs by giving offset.
175
     *
176
     * @param int $seek
177
     * @param int $lines
178
     * @param int $buffer
179
     *
180
     * @return array
181
     *
182
     * @see http://www.geekality.net/2011/05/28/php-tail-tackling-large-files/
183
     */
184
    public function fetch($seek = 0, $lines = 20, $buffer = 4096)
185
    {
186
        $f = fopen($this->filePath, 'rb');
187
188
        if ($seek) {
189
            fseek($f, abs($seek));
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fseek() does only seem to accept resource, 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

189
            fseek(/** @scrutinizer ignore-type */ $f, abs($seek));
Loading history...
Bug introduced by
It seems like abs($seek) can also be of type double; however, parameter $offset of fseek() does only seem to accept integer, 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

189
            fseek($f, /** @scrutinizer ignore-type */ abs($seek));
Loading history...
190
        } else {
191
            fseek($f, 0, SEEK_END);
192
        }
193
194
        if (fread($f, 1) != "\n") {
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fread() does only seem to accept resource, 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

194
        if (fread(/** @scrutinizer ignore-type */ $f, 1) != "\n") {
Loading history...
195
            $lines -= 1;
196
        }
197
        fseek($f, -1, SEEK_CUR);
198
199
        // 从前往后读,上一页
200
        // Start reading
201
        if ($seek > 0) {
202
            $output = '';
203
204
            $this->pageOffset['start'] = ftell($f);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of ftell() does only seem to accept resource, 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

204
            $this->pageOffset['start'] = ftell(/** @scrutinizer ignore-type */ $f);
Loading history...
205
206
            while (!feof($f) && $lines >= 0) {
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of feof() does only seem to accept resource, 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

206
            while (!feof(/** @scrutinizer ignore-type */ $f) && $lines >= 0) {
Loading history...
207
                $output = $output.($chunk = fread($f, $buffer));
208
                $lines -= substr_count($chunk, "\n[20");
209
            }
210
211
            $this->pageOffset['end'] = ftell($f);
212
213
            while ($lines++ < 0) {
214
                $strpos = strrpos($output, "\n[20") + 1;
215
                $_ = mb_strlen($output, '8bit') - $strpos;
216
                $output = substr($output, 0, $strpos);
217
                $this->pageOffset['end'] -= $_;
218
            }
219
220
            // 从后往前读,下一页
221
        } else {
222
            $output = '';
223
224
            $this->pageOffset['end'] = ftell($f);
225
226
            while (ftell($f) > 0 && $lines >= 0) {
227
                $offset = min(ftell($f), $buffer);
228
                fseek($f, -$offset, SEEK_CUR);
229
                $output = ($chunk = fread($f, $offset)).$output;
230
                fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
231
                $lines -= substr_count($chunk, "\n[20");
232
            }
233
234
            $this->pageOffset['start'] = ftell($f);
235
236
            while ($lines++ < 0) {
237
                $strpos = strpos($output, "\n[20") + 1;
238
                $output = substr($output, $strpos);
239
                $this->pageOffset['start'] += $strpos;
240
            }
241
        }
242
243
        fclose($f);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

243
        fclose(/** @scrutinizer ignore-type */ $f);
Loading history...
244
245
        return $this->parseLog($output);
246
    }
247
248
    /**
249
     * Get tail logs in log file.
250
     *
251
     * @param int $seek
252
     *
253
     * @return array
254
     */
255
    public function tail($seek)
256
    {
257
        // Open the file
258
        $f = fopen($this->filePath, 'rb');
259
260
        if (!$seek) {
261
            // Jump to last character
262
            fseek($f, -1, SEEK_END);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fseek() does only seem to accept resource, 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

262
            fseek(/** @scrutinizer ignore-type */ $f, -1, SEEK_END);
Loading history...
263
        } else {
264
            fseek($f, abs($seek));
0 ignored issues
show
Bug introduced by
It seems like abs($seek) can also be of type double; however, parameter $offset of fseek() does only seem to accept integer, 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

264
            fseek($f, /** @scrutinizer ignore-type */ abs($seek));
Loading history...
265
        }
266
267
        $output = '';
268
269
        while (!feof($f)) {
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of feof() does only seem to accept resource, 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
        while (!feof(/** @scrutinizer ignore-type */ $f)) {
Loading history...
270
            $output .= fread($f, 4096);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fread() does only seem to accept resource, 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

270
            $output .= fread(/** @scrutinizer ignore-type */ $f, 4096);
Loading history...
271
        }
272
273
        $pos = ftell($f);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of ftell() does only seem to accept resource, 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

273
        $pos = ftell(/** @scrutinizer ignore-type */ $f);
Loading history...
274
275
        fclose($f);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

275
        fclose(/** @scrutinizer ignore-type */ $f);
Loading history...
276
277
        $logs = [];
278
279
        foreach ($this->parseLog(trim($output)) as $log) {
280
            $logs[] = $this->renderTableRow($log);
281
        }
282
283
        return [$pos, $logs];
284
    }
285
286
    /**
287
     * Render table row.
288
     *
289
     * @param $log
290
     *
291
     * @return string
292
     */
293
    protected function renderTableRow($log)
294
    {
295
        $color = self::$levelColors[$log['level']] ?? 'black';
296
297
        $index = uniqid();
298
299
        $button = '';
300
301
        if (!empty($log['trace'])) {
302
            $button = "<a class=\"btn btn-primary btn-xs\" data-toggle=\"collapse\" data-target=\".trace-{$index}\"><i class=\"fa fa-info\"></i>&nbsp;&nbsp;Exception</a>";
303
        }
304
305
        $trace = '';
306
307
        if (!empty($log['trace'])) {
308
            $trace = "<tr class=\"collapse trace-{$index}\">
309
    <td colspan=\"5\"><div style=\"white-space: pre-wrap;background: #333;color: #fff; padding: 10px;\">{$log['trace']}</div></td>
310
</tr>";
311
        }
312
313
        return <<<TPL
314
<tr style="background-color: rgb(255, 255, 213);">
315
    <td><span class="label bg-{$color}">{$log['level']}</span></td>
316
    <td><strong>{$log['env']}</strong></td>
317
    <td  style="width:150px;">{$log['time']}</td>
318
    <td><code>{$log['info']}</code></td>
319
    <td>$button</td>
320
</tr>
321
$trace
322
TPL;
323
    }
324
325
    /**
326
     * Parse raw log text to array.
327
     *
328
     * @param $raw
329
     *
330
     * @return array
331
     */
332
    protected function parseLog($raw)
333
    {
334
        $logs = preg_split('/\[(\d{4}(?:-\d{2}){2} \d{2}(?::\d{2}){2})\] (\w+)\.(\w+):((?:(?!{"exception").)*)?/', trim($raw), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
335
336
        foreach ($logs as $index => $log) {
337
            if (preg_match('/^\d{4}/', $log)) {
338
                break;
339
            } else {
340
                unset($logs[$index]);
341
            }
342
        }
343
344
        if (empty($logs)) {
345
            return [];
346
        }
347
348
        $parsed = [];
349
350
        foreach (array_chunk($logs, 5) as $log) {
351
            $parsed[] = [
352
                'time'  => $log[0] ?? '',
353
                'env'   => $log[1] ?? '',
354
                'level' => $log[2] ?? '',
355
                'info'  => $log[3] ?? '',
356
                'trace' => trim($log[4] ?? ''),
357
            ];
358
        }
359
360
        unset($logs);
361
362
        rsort($parsed);
363
364
        return $parsed;
365
    }
366
}
367