Logger::createStatusFile()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.1481

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
ccs 2
cts 3
cp 0.6667
rs 10
cc 2
nc 2
nop 0
crap 2.1481
1
<?php
2
3
namespace WebStream\Log;
4
5
use WebStream\IO\File;
0 ignored issues
show
Bug introduced by
The type WebStream\IO\File was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use WebStream\IO\Reader\FileReader;
0 ignored issues
show
Bug introduced by
The type WebStream\IO\Reader\FileReader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use WebStream\IO\Writer\SimpleFileWriter;
0 ignored issues
show
Bug introduced by
The type WebStream\IO\Writer\SimpleFileWriter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use WebStream\Container\Container;
0 ignored issues
show
Bug introduced by
The type WebStream\Container\Container was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use WebStream\Exception\Extend\IOException;
0 ignored issues
show
Bug introduced by
The type WebStream\Exception\Extend\IOException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use WebStream\Exception\Extend\LoggerException;
0 ignored issues
show
Bug introduced by
The type WebStream\Exception\Extend\LoggerException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use WebStream\Log\Outputter\ILazyWriter;
12
use WebStream\Log\Outputter\IOutputter;
13
14
/**
15
 * Loggerクラス
16
 * @author Ryuichi Tanaka
17
 * @since 2012/01/16
18
 * @version 0.7
19
 */
20
class Logger
21
{
22
    use LoggerUtils;
23
24
    /**
25
     * @var Logger ロガー
26
     */
27
    private static Logger $logger;
28
29
    /**
30
     * @var LoggerFormatter ロガーフォーマッタ
31
     */
32
    private static LoggerFormatter $formatter;
33
34
    /**
35
     * @var Container ログ設定コンテナ
36
     */
37
    private static Container $config;
38
39
    /**
40
     * @var Container ログ設定コンテナ
41
     */
42
    private Container $logConfig;
43
44
    /**
45
     * @var array<IOutputter> Outputterリスト
46
     */
47
    private array $outputters;
48
49
    /**
50
     * @var Container IOコンテナ
51
     */
52
    private Container $ioContainer;
53
54
    /**
55
     * @var File ログファイル
56
     */
57
    private File $logFile;
58
59
    /**
60
     * @var File ステータスファイル
61
     */
62
    private File $statusFile;
63
64
    /**
65 141
     * コンストラクタ
66
     * @param Container ログ設定コンテナ
0 ignored issues
show
Documentation Bug introduced by
The doc comment ログ設定コンテナ at position 0 could not be parsed: Unknown type name 'ログ設定コンテナ' at position 0 in ログ設定コンテナ.
Loading history...
67 141
     */
68 141
    private function __construct(Container $logConfig)
69
    {
70 141
        $this->logConfig = $logConfig;
71 141
        $this->outputters = [];
72
73 141
        $logFile = new File($logConfig->logPath);
74
        $statusFile = new File($logConfig->statusPath);
75 14
76 14
        $this->ioContainer = new Container();
77
78
        $this->ioContainer->statusReader = function () use ($statusFile) {
79
            return new FileReader($statusFile);
80
        };
81
        $this->ioContainer->statusWriter = function () use ($statusFile) {
82
            return new SimpleFileWriter($statusFile->getFilePath());
83
        };
84
        $this->ioContainer->logWriter = function () use ($logFile) {
85 141
            return new SimpleFileWriter($logFile->getFilePath());
86 141
        };
87 141
88
        $this->logFile = $logFile;
89
        $this->statusFile = $statusFile;
90
    }
91
92
    /**
93
     * デストラクタ
94
     */
95
    public function __destruct()
96
    {
97
        $this->directWrite();
98
    }
99
100
    /**
101
     * ログ設定を返却する
102
     * @return Container ログ設定
103
     */
104
    public function getConfig()
105
    {
106
        return $this->logConfig;
107
    }
108
109
    /**
110
     * 遅延書き出しを有効にする
111
     */
112
    public static function enableLazyWrite()
113
    {
114
        self::$logger->lazyWrite();
115
    }
116
117
    /**
118
     * 即時書き出しを有効にする
119
     */
120
    public static function enableDirectWrite()
121
    {
122
        self::$logger->directWrite();
123
    }
124
125
    /**
126 141
     * インスタンスを返却する
127
     * @return Logger ロガーインスタンス
128 141
     */
129
    public static function getInstance()
130
    {
131
        return self::$logger;
132
    }
133
134
    /**
135
     * ファイナライザ
136
     */
137
    public static function finalize()
138
    {
139
        self::$config = null;
140
        self::$logger = null;
141
        self::$formatter = null;
142
    }
143
144
    /**
145 141
     * Loggerを初期化する
146
     * @param Container $config
147 141
     */
148 141
    public static function init(Container $config)
149 141
    {
150 141
        self::$config = $config;
151
        self::$logger = new Logger($config);
152
        self::$formatter = new LoggerFormatter($config);
153
    }
154
155
    /**
156
     * Loggerが初期化済みかどうかチェックする
157
     * @return bool 初期化済みならtrue
158
     */
159
    public static function isInitialized()
160
    {
161
        return self::$logger !== null;
162
    }
163
164
    /**
165
     * Loggerメソッドの呼び出しを受ける
166
     * @param string メソッド名(ログレベル文字列)
0 ignored issues
show
Documentation Bug introduced by
The doc comment メソッド名(ログレベル文字列) at position 0 could not be parsed: Unknown type name 'メソッド名' at position 0 in メソッド名(ログレベル文字列).
Loading history...
167
     * @param array 引数
168
     */
169
    public static function __callStatic($level, $arguments)
170
    {
171
        if (self::$logger === null || self::$formatter === null) {
172
            if (self::$config !== null) {
173
                self::init(self::$config);
174
            } else {
175
                throw new LoggerException("Logger is not initialized.");
176
            }
177
        }
178
179
        call_user_func_array([self::$logger, "write"], array_merge([$level], $arguments));
180
    }
181
182
    /**
183 141
     * Outputterを設定する
184
     * @param array<IOutputter> $outputters Outputterリスト
185 141
     */
186 141
    public function setOutputter(array $outputters)
187
    {
188
        foreach ($outputters as $outputter) {
189
            if (!$outputter instanceof IOutputter) {
190 141
                throw new LoggerException("Log outputter must implement WebStream\Log\Outputter\IOutputter.");
191 141
            }
192
        }
193
        $this->outputters = $outputters;
194
    }
195
196
    /**
197
     * タイムスタンプを取得する
198
     * @return string タイムスタンプ
199
     */
200
    private function getTimeStamp()
0 ignored issues
show
Unused Code introduced by
The method getTimeStamp() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
201
    {
202
        date_default_timezone_set('Asia/Tokyo');
203
        $msec = sprintf("%2d", floatval(microtime()) * 100);
204
205
        return strftime("%Y-%m-%d %H:%M:%S") . "," . $msec;
206
    }
207
208
    /**
209
     * ログを書き出す
210
     * @param string ログレベル文字列
0 ignored issues
show
Documentation Bug introduced by
The doc comment ログレベル文字列 at position 0 could not be parsed: Unknown type name 'ログレベル文字列' at position 0 in ログレベル文字列.
Loading history...
211 141
     * @param string 出力文字列
212
     * @param array<mixed> 埋め込み値リスト
213 141
     */
214 44
    public function write($level, $msg, $context = null)
215
    {
216
        if ($this->logConfig->logLevel > $this->toLogLevelValue($level)) {
217 97
            return;
218
        }
219
220 4
        if (is_array($context) && count($context) > 0) {
221 4
            // sprintfと同様の展開
222 4
            // [a-zA-Z0-9_-\.] 以外もキーには指定可能だが仕様としてこれ以外は不可とする
223 4
            preg_match_all('/\{\s*([a-zA-Z0-9._-]+)\s*?\}/', $msg, $matches);
224
            foreach ($matches[1] as $index => $value) {
225
                if (array_key_exists($value, $context)) {
226
                    $matches[1][$index] = $context[$value];
227
                } else {
228 4
                    unset($matches[0][$index]);
229
                }
230
            }
231
            $msg = str_replace($matches[0], $matches[1], $msg);
232 97
        }
233
234
        // ログローテート処理
235 97
        $this->rotate();
236 97
237 97
        try {
238
            if (count($this->outputters) > 0) {
239
                foreach ($this->outputters as $outputter) {
240
                    $outputter->write(self::$formatter->getFormattedMessage($msg, $level));
241
                }
242
            } else {
243
                $this->ioContainer->logWriter->write(self::$formatter->getFormattedMessage($msg, $level) . PHP_EOL);
244
            }
245 97
        } catch (IOException $e) {
246
            throw new LoggerException($e);
247
        }
248
    }
249
250
    /**
251
     * ログファイルをアーカイブする
252 97
     * stream.log -> stream.(作成日時)-(現在日時).log
253
     * @param string ログファイルパス
0 ignored issues
show
Documentation Bug introduced by
The doc comment ログファイルパス at position 0 could not be parsed: Unknown type name 'ログファイルパス' at position 0 in ログファイルパス.
Loading history...
254
     */
255 97
    private function rotate()
256
    {
257
        // ログファイルがない場合はローテートしない
258
        if (!$this->logFile->exists()) {
259
            return;
260 97
        }
261 14
262 83
        // ログローテート実行
263
        if ($this->logConfig->rotateCycle !== null) {
264
            $this->rotateByCycle();
265 97
        } elseif ($this->logConfig->rotateSize !== null) {
266
            $this->rotateBySize();
267
        }
268
    }
269
270
    /**
271
     * ログステータスファイルに書きこむ
272
     * @throws IOException
273
     */
274
    private function writeStatus()
275
    {
276
        $this->ioContainer->statusWriter->write(intval(preg_replace('/^.*\s/', '', microtime())));
277
    }
278
279
    /**
280
     * ログステータスファイルを読み込む
281 14
     * @return int UnixTime
282
     * @throws LoggerException
283 14
     */
284 14
    private function readStatus()
285
    {
286
        $content = $this->ioContainer->statusReader->read();
287
        if (!preg_match('/^\d{10}$/', $content)) {
288 14
            throw new LoggerException("Invalid log state file contents: " . $content);
289
        }
290
291
        return intval($content);
292
    }
293
294 14
    /**
295
     * ステータスファイルを作成する
296
     */
297 14
    private function createStatusFile()
298
    {
299
        // ステータスファイルがない場合は書きだす
300 14
        if (!$this->statusFile->exists()) {
301
            $this->writeStatus();
302
        }
303
    }
304
305
    /**
306
     * ローテートを実行する
307 8
     * @param int 作成日時のUnixTime
0 ignored issues
show
Documentation Bug introduced by
The doc comment 作成日時のUnixTime at position 0 could not be parsed: Unknown type name '作成日時のUnixTime' at position 0 in 作成日時のUnixTime.
Loading history...
308
     * @param int 現在日時のUnixTime
309 8
     */
310 8
    private function runRotate($from, $to)
311 8
    {
312 8
        $fromDate = date("YmdHis", $from);
313 8
        $toDate = date("YmdHis", $to);
314
        $archivePath = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $archivePath is dead and can be removed.
Loading history...
315
        if (preg_match('/(.*)\.(.+)/', $this->logConfig->logPath, $matches)) {
316
            $archivePath = "$matches[1].${fromDate}-${toDate}.$matches[2]";
317
            // mvを実行
318
            $this->logFile->renameTo($archivePath);
319 8
            // ステータスファイルを削除
320
            $this->statusFile->delete();
321
        }
322
    }
323
324
    /**
325
     * 時間単位でローテートする
326
     * stream.log -> stream.(作成日時)-(現在日時).log
327 14
     */
328 14
    private function rotateByCycle()
329 14
    {
330
        $this->createStatusFile();
331 14
        $now = intval(preg_replace('/^.*\s/', '', microtime()));
332 14
        $createdAt = $this->readStatus();
333 8
334
        $hour = intval(floor(($now - $createdAt) / 3600));
335 14
        if ($hour >= $this->logConfig->rotateCycle) {
336
            $this->runRotate($createdAt, $now);
337
        }
338
    }
339
340
    /**
341
     * サイズ単位でローテートする
342
     * stream.log -> stream.(作成日時)-(現在日時).log
343
     */
344
    private function rotateBySize()
345
    {
346
        $this->createStatusFile();
347
        $now = intval(preg_replace('/^.*\s/', '', microtime()));
348
        $createdAt = $this->readStatus();
349
350
        $sizeKb = (int) floor($this->logFile->length() / 1024);
351
        // 指定したサイズより大きければローテート
352
        if ($sizeKb >= $this->logConfig->rotateSize) {
353
            $this->runRotate($createdAt, $now);
354
        }
355
    }
356
357
    /**
358
     * ログ出力パスを返却する
359
     * @return string ログ出力パス
360
     */
361
    public function getLogPath()
362
    {
363
        return $this->logConfig->logPath;
364
    }
365
366
    /**
367
     * ログローテートサイクルを返却する
368
     * @return string ログ出力パス
369
     */
370
    public function getLogRotateCycle()
371
    {
372
        return $this->logConfig->rotateCycle;
373
    }
374
375
    /**
376
     * ログローテートサイズを返却する
377
     * @return string ログ出力パス
378
     */
379
    public function getLogRotateSize()
380
    {
381
        return $this->logConfig->rotateSize;
382
    }
383
384
    /**
385
     * 遅延書き出しを有効にする
386
     */
387
    public function lazyWrite()
388
    {
389
        foreach ($this->outputters as $outputter) {
390
            if ($outputter instanceof ILazyWriter) {
391
                $outputter->enableLazyWrite();
392
            }
393
        }
394
    }
395
396
    /**
397
     * 即時書き出しを有効にする
398 1
     */
399 1
    public function directWrite()
400 1
    {
401
        foreach ($this->outputters as $outputter) {
402
            if ($outputter instanceof ILazyWriter) {
403 1
                $outputter->enableDirectWrite();
404
            }
405
        }
406
    }
407
}
408