Passed
Push — master ( b45126...8af3f1 )
by 世昌
03:18
created

FileLogger::save()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A FileLogger::write() 0 7 2
1
<?php
2
namespace suda\framework\debug\log\logger;
3
4
use ZipArchive;
5
use RecursiveIteratorIterator;
6
use RecursiveDirectoryIterator;
7
use suda\framework\debug\ConfigTrait;
8
use suda\framework\debug\log\LogLevel;
9
use suda\framework\debug\ConfigInterface;
10
use suda\framework\filesystem\FileSystem;
11
use suda\framework\debug\log\AbstractLogger;
12
use suda\framework\debug\log\logger\exception\FileLoggerException;
13
14
class FileLogger extends AbstractLogger implements ConfigInterface
15
{
16
    use ConfigTrait;
17
18
    /**
19
     * 文件
20
     *
21
     * @var resource
22
     */
23
    protected $temp;
24
25
    /**
26
     * 临时文件名
27
     *
28
     * @var string
29
     */
30
    protected $tempname;
31
32
    /**
33
     * 移除文件
34
     *
35
     * @var array
36
     */
37
    protected $removeFiles = [];
38
39
    /**
40
     * 最后的日志
41
     *
42
     * @var string
43
     */
44
    protected $latest;
45
46
    /**
47
     * 构建文件日志
48
     *
49
     * @param array $config
50
     */
51
    public function __construct(array $config = [])
52
    {
53
        $this->applyConfig($config);
54
        FileSystem::make($this->getConfig('save-path'));
55
        FileSystem::make($this->getConfig('save-dump-path'));
56
        FileSystem::make($this->getConfig('save-zip-path'));
57
        register_shutdown_function([$this, 'shutdown']);
58
    }
59
60
    /**
61
     * @return resource
62
     * @throws FileLoggerException
63
     */
64
    public function getAvailableWrite()
65
    {
66
        if (is_resource($this->temp)) {
67
            return $this->temp;
68
        }
69
        $this->prepareWrite();
70
        return $this->temp;
71
    }
72
73
    /**
74
     * @throws FileLoggerException
75
     */
76
    private function prepareWrite()
77
    {
78
        $msec = explode('.', microtime(true))[1];
79
        $save = $this->getConfig('save-path');
80
        $this->tempname = $save.'/'.date('YmdHis').'.'.$msec.'.log';
81
        $temp = fopen($this->tempname, 'w+');
82
        if ($temp !== false) {
83
            $this->temp = $temp;
84
        } else {
85
            throw new FileLoggerException(__METHOD__.':'.sprintf('cannot create log file'));
86
        }
87
        $this->latest = $save.'/'.$this->getConfig('file-name');
88
    }
89
90
    public function getDefaultConfig():array
91
    {
92
        return [
93
            'save-path' => './logs',
94
            'save-zip-path' => './logs/zip',
95
            'save-dump-path' => './logs/dump',
96
            'max-file-size' => 2097152,
97
            'file-name' => 'latest.log',
98
            'log-level' => 'debug',
99
            'log-format' => '[%level%] %message%',
100
        ];
101
    }
102
103
    /**
104
     * 打包文件
105
     */
106
    private function packLogFile()
107
    {
108
        $logFile = $this->latest;
109
        $path = preg_replace('/[\\\\]+/', '/', $this->getConfig('save-zip-path') .'/'.date('Y-m-d').'.zip');
110
        $zip = $this->getZipArchive($path);
111
        if ($zip !== null) {
112
            if ($zip->addFile($logFile, date('Y-m-d'). '-'. $zip->numFiles .'.log')) {
113
                array_push($this->removeFiles, $logFile);
114
            }
115
            if (is_dir($this->getConfig('save-dump-path'))) {
116
                $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(
117
                    $this->getConfig('save-dump-path'),
118
                    RecursiveDirectoryIterator::SKIP_DOTS
119
                ));
120
                foreach ($it as $dumpLog) {
121
                    if ($zip->addFile($dumpLog, 'dump/'.basename($dumpLog))) {
122
                        array_push($this->removeFiles, $dumpLog);
123
                    }
124
                }
125
            }
126
            $zip->close();
127
        } else {
128
            if (is_file($logFile) && file_exists($logFile)) {
129
                rename(
130
                    $logFile,
131
                    $this->getConfig('save-path') . '/' . date('Y-m-d'). '-'. substr(md5_file($logFile), 0, 8).'.log'
132
                );
133
            }
134
        }
135
    }
136
137
    /**
138
     * 获取压缩
139
     *
140
     * @param string $path
141
     * @return ZipArchive|null
142
     */
143
    protected function getZipArchive(string $path)
144
    {
145
        if (class_exists('ZipArchive')) {
146
            $zip = new ZipArchive;
147
            $res = $zip->open($path, ZipArchive::CREATE);
148
            if ($res === true) {
149
                return $zip;
150
            }
151
        }
152
        return null;
153
    }
154
155
    /**
156
     * 检查日志文件大小
157
     *
158
     * @return boolean
159
     */
160
    protected function checkSize():bool
161
    {
162
        $logFile = $this->latest;
163
        if (file_exists($logFile)) {
164
            if (filesize($logFile) > $this->getConfig('max-file-size')) {
165
                return true;
166
            }
167
        }
168
        return false;
169
    }
170
171
172
    /**
173
     * @param string $level
174
     * @param string $message
175
     * @param array $context
176
     * @return mixed|void
177
     * @throws FileLoggerException
178
     */
179
    public function log($level, string $message, array $context = [])
180
    {
181
        if (LogLevel::compare($level, $this->getConfig('log-level')) >= 0) {
182
            $replace = [];
183
            $message = $this->interpolate($message, $context);
184
            $replace['%level%'] = $level;
185
            $replace['%message%'] = $message;
186
            $write = strtr($this->getConfig('log-format'), $replace);
187
            fwrite($this->getAvailableWrite(), $write.PHP_EOL);
188
        }
189
    }
190
191
192
    protected function rollLatest()
193
    {
194
        if (isset($this->latest)) {
195
            $size = ftell($this->temp);
196
            fseek($this->temp, 0);
197
            if ($size > 0) {
198
                $body = fread($this->temp, $size);
199
                file_put_contents($this->latest, $body, FILE_APPEND);
200
            }
201
            fclose($this->temp);
202
            $this->temp = null;
203
            if ($this->tempname !== null) {
204
                unlink($this->tempname);
205
            }
206
        }
207
    }
208
209
    protected function removePackFiles()
210
    {
211
        foreach ($this->removeFiles as $file) {
212
            if (is_file($file) && file_exists($file)) {
213
                unlink($file);
214
            }
215
        }
216
    }
217
218
    public function write()
219
    {
220
        if ($this->checkSize()) {
221
            $this->packLogFile();
222
        }
223
        $this->rollLatest();
224
        $this->removePackFiles();
225
    }
226
227
    public function shutdown()
228
    {
229
        if (function_exists('fastcgi_finish_request')) {
230
            fastcgi_finish_request();
231
        }
232
        $this->write();
233
    }
234
235
    public function interpolate(string $message, array $context)
236
    {
237
        $replace = [];
238
        foreach ($context as $key => $val) {
239
            if (is_bool($val)) {
240
                $val = $val ? 'true' : 'false';
241
            } elseif (null === $val) {
242
                $val = 'null';
243
            }
244
            $replace['{' . $key . '}'] = $val;
245
        }
246
        return strtr($message, $replace);
247
    }
248
}
249