Completed
Push — 6.0 ( cb8a0b...bb9f45 )
by yun
06:00
created

File::write()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 4
b 0
f 0
nc 3
nop 2
dl 0
loc 14
ccs 7
cts 7
cp 1
crap 3
rs 10
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 3
    }
54
55
    /**
56
     * 日志写入接口
57
     * @access public
58
     * @param array $log 日志信息
59
     * @return bool
60
     */
61 2
    public function save(array $log): bool
62
    {
63 2
        $destination = $this->getMasterLogFile();
64
65 2
        $path = dirname($destination);
66 2
        !is_dir($path) && mkdir($path, 0755, true);
67
68 2
        $info = [];
69
70
        // 日志信息封装
71 2
        $time = date($this->config['time_format']);
72
73 2
        foreach ($log as $type => $val) {
74 2
            $message = [];
75 2
            foreach ($val as $msg) {
76 2
                if (!is_string($msg)) {
77
                    $msg = var_export($msg, true);
78
                }
79
80 2
                $message[] = $this->config['json'] ?
81
                    json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) :
82 2
                    sprintf($this->config['format'], $time, $type, $msg);
83
            }
84
85 2
            if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) {
86
                // 独立记录的日志级别
87
                $filename = $this->getApartLevelFile($path, $type);
88
                $this->write($message, $filename);
89
                continue;
90
            }
91
92 2
            $info[$type] = $message;
93
        }
94
95 2
        if ($info) {
96 2
            return $this->write($info, $destination);
97
        }
98
99
        return true;
100
    }
101
102
    /**
103
     * 日志写入
104
     * @access protected
105
     * @param array  $message     日志信息
106
     * @param string $destination 日志文件
107
     * @return bool
108
     */
109 2
    protected function write(array $message, string $destination): bool
110
    {
111
        // 检测日志文件大小,超过配置大小则备份日志文件重新生成
112 2
        $this->checkLogSize($destination);
113
114 2
        $info = [];
115
116 2
        foreach ($message as $type => $msg) {
117 2
            $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg;
118
        }
119
120 2
        $message = implode(PHP_EOL, $info) . PHP_EOL;
121
122 2
        return error_log($message, 3, $destination);
123
    }
124
125
    /**
126
     * 获取主日志文件名
127
     * @access public
128
     * @return string
129
     */
130 2
    protected function getMasterLogFile(): string
131
    {
132 2
        if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
133 2
            $this->config['path'] .= DIRECTORY_SEPARATOR;
134
        }
135
136 2
        if ($this->config['max_files']) {
137
            $files = glob($this->config['path'] . '*.log');
138
139
            try {
140
                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

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