FileTarget   A
last analyzed

Complexity

Total Complexity 10

Size/Duplication

Total Lines 82
Duplicated Lines 0 %

Test Coverage

Coverage 76.47%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 10
eloc 31
c 2
b 0
f 0
dl 0
loc 82
ccs 26
cts 34
cp 0.7647
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B export() 0 33 6
A checkWrittenResult() 0 16 3
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. The directory
42
     * containing the log files will be automatically created if not existing.
43
     * @param FileRotatorInterface|null $rotator The instance that takes care of rotating files.
44
     * @param int $dirMode The permission to be set for newly created directories. This value will be used by PHP
45
     * `chmod()` function. No umask will be applied. Defaults to 0775, meaning the directory is read-writable by owner
46
     * and group, but read-only for other users.
47
     * @param int|null $fileMode The permission to be set for newly created log files. This value will be used by PHP
48
     * `chmod()` function. No umask will be applied. If not set, the permission will be determined by the current
49
     * environment.
50
     */
51 11
    public function __construct(
52
        private string $logFile = '/tmp/app.log',
53
        private ?FileRotatorInterface $rotator = null,
54
        private int $dirMode = 0775,
55
        private ?int $fileMode = null
56
    ) {
57 11
        parent::__construct();
58
    }
59
60 9
    protected function export(): void
61
    {
62 9
        $logPath = dirname($this->logFile);
63
64 9
        if (!file_exists($logPath)) {
65 2
            FileHelper::ensureDirectory($logPath, $this->dirMode);
66
        }
67
68 9
        $text = $this->formatMessages("\n");
69 9
        $filePointer = FileHelper::openFile($this->logFile, 'ab');
70 8
        flock($filePointer, LOCK_EX);
71
72 8
        if ($this->rotator !== null) {
73
            // clear stat cache to ensure getting the real current file size and not a cached one
74
            // this may result in rotating twice when cached file size is used on subsequent calls
75 6
            clearstatcache();
76
        }
77
78 8
        if ($this->rotator !== null && $this->rotator->shouldRotateFile($this->logFile)) {
79 4
            flock($filePointer, LOCK_UN);
80 4
            fclose($filePointer);
81 4
            $this->rotator->rotateFile($this->logFile);
82 4
            $writeResult = file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
83
        } else {
84 8
            $writeResult = fwrite($filePointer, $text);
85 8
            flock($filePointer, LOCK_UN);
86 8
            fclose($filePointer);
87
        }
88
89 8
        $this->checkWrittenResult($writeResult, $text);
90
91 8
        if ($this->fileMode !== null) {
92 2
            chmod($this->logFile, $this->fileMode);
93
        }
94
    }
95
96
    /**
97
     * Checks the written result.
98
     *
99
     * @param false|int $writeResult The number of bytes written to the file, or FALSE if an error occurs.
100
     * @param string $text The text written to the file.
101
     *
102
     * @throws RuntimeException For unable to export log through file.
103
     */
104 8
    private function checkWrittenResult(false|int $writeResult, string $text): void
105
    {
106 8
        if ($writeResult === false) {
107
            throw new RuntimeException(sprintf(
108
                'Unable to export log through file: %s',
109
                error_get_last()['message'] ?? '',
110
            ));
111
        }
112
113 8
        $textSize = strlen($text);
114
115 8
        if ($writeResult < $textSize) {
116
            throw new RuntimeException(sprintf(
117
                'Unable to export whole log through file. Wrote %d out of %d bytes.',
118
                $writeResult,
119
                $textSize,
120
            ));
121
        }
122
    }
123
}
124