Passed
Pull Request — 8.0 (#3055)
by wj
02:23
created

File   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Test Coverage

Coverage 57.14%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 80
c 3
b 0
f 0
dl 0
loc 189
ccs 40
cts 70
cp 0.5714
rs 9.6
wmc 35

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 5
A write() 0 14 3
B save() 0 39 9
A checkLogSize() 0 6 4
B getMasterLogFile() 0 39 10
A getApartLevelFile() 0 14 4
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: 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 9
    public function __construct(App $app, $config = [])
41
    {
42 9
        if (is_array($config)) {
43 9
            $this->config = array_merge($this->config, $config);
44
        }
45
46 9
        if (empty($this->config['format'])) {
47
            $this->config['format'] = '[%s][%s] %s';
48
        }
49
50 9
        if (empty($this->config['path'])) {
51 3
            $this->config['path'] = $app->getRuntimePath() . 'log';
52
        }
53
54 9
        if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
55 9
            $this->config['path'] .= DIRECTORY_SEPARATOR;
56
        }
57
    }
58
59
    /**
60
     * 日志写入接口
61
     * @access public
62
     * @param array $log 日志信息
63
     * @return bool
64
     */
65 6
    public function save(array $log): bool
66
    {
67 6
        $destination = $this->getMasterLogFile();
68
69 6
        $path = dirname($destination);
70 6
        !is_dir($path) && mkdir($path, 0755, true);
71
72 6
        $info = [];
73
74
        // 日志信息封装
75 6
        $time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']);
76
77 6
        foreach ($log as $type => $val) {
78 6
            $message = [];
79 6
            foreach ($val as $msg) {
80 6
                if (!is_string($msg)) {
81
                    $msg = var_export($msg, true);
82
                }
83
84 6
                $message[] = $this->config['json'] ?
85
                json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) :
86 6
                sprintf($this->config['format'], $time, $type, $msg);
87
            }
88
89 6
            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 6
            $info[$type] = $message;
97
        }
98
99 6
        if ($info) {
100 6
            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 6
    protected function write(array $message, string $destination): bool
114
    {
115
        // 检测日志文件大小,超过配置大小则备份日志文件重新生成
116 6
        $this->checkLogSize($destination);
117
118 6
        $info = [];
119
120 6
        foreach ($message as $type => $msg) {
121 6
            $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg;
122
        }
123
124 6
        $message = implode(PHP_EOL, $info) . PHP_EOL;
125
126 6
        return error_log($message, 3, $destination);
127
    }
128
129
    /**
130
     * 获取主日志文件名
131
     * @access public
132
     * @return string
133
     */
134 6
    protected function getMasterLogFile(): string
135
    {
136
137 6
        if ($this->config['max_files']) {
138
            $files = [];
139
            foreach (scandir($this->config['path']) as $name) {
140
                if (!str_ends_with($name, '.log') || !is_file($this->config['path'] . $name)) {
141
                    continue;
142
                }
143
144
                $files[] = $this->config['path'] . $name;
145
            }
146
147
            try {
148
                if (count($files) > $this->config['max_files']) {
149
                    set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
0 ignored issues
show
Unused Code introduced by
The parameter $errno is not used and could be removed. ( Ignorable by Annotation )

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

149
                    set_error_handler(function (/** @scrutinizer ignore-unused */ $errno, $errstr, $errfile, $errline) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $errfile is not used and could be removed. ( Ignorable by Annotation )

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

149
                    set_error_handler(function ($errno, $errstr, /** @scrutinizer ignore-unused */ $errfile, $errline) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $errstr is not used and could be removed. ( Ignorable by Annotation )

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

149
                    set_error_handler(function ($errno, /** @scrutinizer ignore-unused */ $errstr, $errfile, $errline) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $errline is not used and could be removed. ( Ignorable by Annotation )

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

149
                    set_error_handler(function ($errno, $errstr, $errfile, /** @scrutinizer ignore-unused */ $errline) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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