Completed
Push — 6.0 ( a4ef3c...64a49e )
by yun
04:55
created

File::save()   B

Complexity

Conditions 9
Paths 44

Size

Total Lines 39
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 10.6426

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 9
eloc 21
c 4
b 0
f 0
nc 44
nop 1
dl 0
loc 39
ccs 16
cts 22
cp 0.7272
crap 10.6426
rs 8.0555
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\log\driver;
14
15
use think\App;
16
use think\contract\LogHandlerInterface;
17
18
/**
19
 * 本地化调试输出到文件
20
 */
21
class File implements LogHandlerInterface
22
{
23
    /**
24
     * 配置参数
25
     * @var array
26
     */
27
    protected $config = [
28
        'time_format'  => 'c',
29
        'single'       => false,
30
        'file_size'    => 2097152,
31
        'path'         => '',
32
        'apart_level'  => [],
33
        'max_files'    => 0,
34
        'json'         => false,
35
        'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
36
        'format'       => '[%s][%s] %s',
37
    ];
38
39
    // 实例化并传入参数
40 3
    public function __construct(App $app, $config = [])
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
41
    {
42 3
        if (is_array($config)) {
43 3
            $this->config = array_merge($this->config, $config);
44
        }
45
46 3
        if (empty($this->config['format'])) {
47
            $this->config['format'] = '[%s][%s] %s';
48
        }
49
50 3
        if (empty($this->config['path'])) {
51 1
            $this->config['path'] = $app->getRuntimePath() . 'log';
52
        }
53
54 3
        if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
55 3
            $this->config['path'] .= DIRECTORY_SEPARATOR;
56
        }
57 3
    }
58
59
    /**
60
     * 日志写入接口
61
     * @access public
62
     * @param array $log 日志信息
63
     * @return bool
64
     */
65 2
    public function save(array $log): bool
66
    {
67 2
        $destination = $this->getMasterLogFile();
68
69 2
        $path = dirname($destination);
70 2
        !is_dir($path) && mkdir($path, 0755, true);
71
72 2
        $info = [];
73
74
        // 日志信息封装
75 2
        $time = date($this->config['time_format']);
76
77 2
        foreach ($log as $type => $val) {
78 2
            $message = [];
79 2
            foreach ($val as $msg) {
80 2
                if (!is_string($msg)) {
81
                    $msg = var_export($msg, true);
82
                }
83
84 2
                $message[] = $this->config['json'] ?
85
                    json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) :
86 2
                    sprintf($this->config['format'], $time, $type, $msg);
87
            }
88
89 2
            if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) {
90
                // 独立记录的日志级别
91
                $filename = $this->getApartLevelFile($path, $type);
92
                $this->write($message, $filename);
93
                continue;
94
            }
95
96 2
            $info[$type] = $message;
97
        }
98
99 2
        if ($info) {
100 2
            return $this->write($info, $destination);
101
        }
102
103
        return true;
104
    }
105
106
    /**
107
     * 日志写入
108
     * @access protected
109
     * @param array  $message     日志信息
110
     * @param string $destination 日志文件
111
     * @return bool
112
     */
113 2
    protected function write(array $message, string $destination): bool
114
    {
115
        // 检测日志文件大小,超过配置大小则备份日志文件重新生成
116 2
        $this->checkLogSize($destination);
117
118 2
        $info = [];
119
120 2
        foreach ($message as $type => $msg) {
121 2
            $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg;
122
        }
123
124 2
        $message = implode(PHP_EOL, $info) . PHP_EOL;
125
126 2
        return error_log($message, 3, $destination);
127
    }
128
129
    /**
130
     * 获取主日志文件名
131
     * @access public
132
     * @return string
133
     */
134 2
    protected function getMasterLogFile(): string
135
    {
136
137 2
        if ($this->config['max_files']) {
138
            $files = glob($this->config['path'] . '*.log');
139
140
            try {
141
                if (count($files) > $this->config['max_files']) {
0 ignored issues
show
Bug introduced by
It seems like $files can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, 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

141
                if (count(/** @scrutinizer ignore-type */ $files) > $this->config['max_files']) {
Loading history...
142
                    unlink($files[0]);
143
                }
144
            } catch (\Exception $e) {
145
                //
146
            }
147
        }
148
149 2
        if ($this->config['single']) {
150
            $name        = is_string($this->config['single']) ? $this->config['single'] : 'single';
151
            $destination = $this->config['path'] . $name . '.log';
152
        } else {
153
154 2
            if ($this->config['max_files']) {
155
                $filename = date('Ymd') . '.log';
156
            } else {
157 2
                $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . '.log';
158
            }
159
160 2
            $destination = $this->config['path'] . $filename;
161
        }
162
163 2
        return $destination;
164
    }
165
166
    /**
167
     * 获取独立日志文件名
168
     * @access public
169
     * @param string $path 日志目录
170
     * @param string $type 日志类型
171
     * @return string
172
     */
173
    protected function getApartLevelFile(string $path, string $type): string
174
    {
175
176
        if ($this->config['single']) {
177
            $name = is_string($this->config['single']) ? $this->config['single'] : 'single';
178
179
            $name .= '_' . $type;
180
        } elseif ($this->config['max_files']) {
181
            $name = date('Ymd') . '_' . $type;
182
        } else {
183
            $name = date('d') . '_' . $type;
184
        }
185
186
        return $path . DIRECTORY_SEPARATOR . $name . '.log';
187
    }
188
189
    /**
190
     * 检查日志文件大小并自动生成备份文件
191
     * @access protected
192
     * @param string $destination 日志文件
193
     * @return void
194
     */
195 2
    protected function checkLogSize(string $destination): void
196
    {
197 2
        if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) {
198
            try {
199
                rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination));
200
            } catch (\Exception $e) {
201
                //
202
            }
203
        }
204 2
    }
205
}
206