Passed
Pull Request — master (#47)
by Dmitriy
21:53 queued 08:55
created

FileTarget::export()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 20
c 1
b 0
f 0
nc 16
nop 0
dl 0
loc 33
ccs 20
cts 20
cp 1
crap 6
rs 8.9777
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Log\Target\File;
6
7
use RuntimeException;
8
use Yiisoft\Files\FileHelper;
9
use Yiisoft\Log\Target;
10
11
use function chmod;
12
use function clearstatcache;
13
use function dirname;
14
use function error_get_last;
15
use function fclose;
16
use function file_exists;
17
use function file_put_contents;
18
use function flock;
19
use function fwrite;
20
use function sprintf;
21
use function strlen;
22
23
use const FILE_APPEND;
24
use const LOCK_EX;
25
use const LOCK_UN;
26
27
/**
28
 * FileTarget records log messages in a file.
29
 *
30
 * The log file is specified via {@see FileTarget::$logFile}.
31
 *
32
 * If {@see FileRotator} is used and the size of the log file exceeds {@see FileRotator::$maxFileSize},
33
 * a rotation will be performed, which renames the current log file by suffixing the file name with '.1'.
34
 * All existing log files are moved backwards by one place, i.e., '.2' to '.3', '.1' to '.2', and so on.
35
 * If compression is enabled {@see FileRotator::$compressRotatedFiles}, the rotated files will be compressed
36
 * into the '.gz' format. The property {@see FileRotator::$maxFiles} specifies how many history files to keep.
37
 */
38
final class FileTarget extends Target
39
{
40
    /**
41
     * @param string $logFile The log file path. If not set, it will use the "/tmp/app.log" file.
42
     * @param FileRotatorInterface|null $rotator The instance that takes care of rotating files.
43
     * @param int $dirMode The permission to be set for newly created directories.
44
     * @param int|null $fileMode The permission to be set for newly created log files.
45
     */
46 11
    public function __construct(
47
        private string $logFile = '/tmp/app.log',
48
        private ?FileRotatorInterface $rotator = null,
49
        private int $dirMode = 0775,
50
        private ?int $fileMode = null
51
    ) {
52 11
        parent::__construct();
53
    }
54
55 9
    protected function export(): void
56
    {
57 9
        $logPath = dirname($this->logFile);
58
59 9
        if (!file_exists($logPath)) {
60 2
            FileHelper::ensureDirectory($logPath, $this->dirMode);
61
        }
62
63 9
        $text = $this->formatMessages("\n");
64 9
        $filePointer = FileHelper::openFile($this->logFile, 'ab');
65 8
        flock($filePointer, LOCK_EX);
66
67 8
        if ($this->rotator !== null) {
68
            // clear stat cache to ensure getting the real current file size and not a cached one
69
            // this may result in rotating twice when cached file size is used on subsequent calls
70 6
            clearstatcache();
71
        }
72
73 8
        if ($this->rotator !== null && $this->rotator->shouldRotateFile($this->logFile)) {
74 4
            flock($filePointer, LOCK_UN);
75 4
            fclose($filePointer);
76 4
            $this->rotator->rotateFile($this->logFile);
77 4
            $writeResult = file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
78
        } else {
79 8
            $writeResult = fwrite($filePointer, $text);
80 8
            flock($filePointer, LOCK_UN);
81 8
            fclose($filePointer);
82
        }
83
84 8
        $this->checkWrittenResult($writeResult, $text);
85
86 8
        if ($this->fileMode !== null) {
87 2
            chmod($this->logFile, $this->fileMode);
88
        }
89
    }
90
91
    /**
92
     * Checks the written result.
93
     *
94
     * @param false|int $writeResult The number of bytes written to the file, or FALSE if an error occurs.
95
     * @param string $text The text written to the file.
96
     *
97
     * @throws RuntimeException For unable to export log through file.
98
     */
99 8
    private function checkWrittenResult(false|int $writeResult, string $text): void
100
    {
101 8
        if ($writeResult === false) {
102
            throw new RuntimeException(sprintf(
103
                'Unable to export log through file: %s',
104
                error_get_last()['message'] ?? '',
105
            ));
106
        }
107
108 8
        $textSize = strlen($text);
109
110 8
        if ($writeResult < $textSize) {
111
            throw new RuntimeException(sprintf(
112
                'Unable to export whole log through file. Wrote %d out of %d bytes.',
113
                $writeResult,
114
                $textSize,
115
            ));
116
        }
117
    }
118
}
119