Passed
Push — master ( bb9381...992d6d )
by Alexander
08:39
created

FileTarget::checkWrittenResult()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.7085

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 9
rs 10
ccs 4
cts 7
cp 0.5714
crap 3.7085
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Log\Target\File;
6
7
use Yiisoft\Files\FileHelper;
8
use Yiisoft\Log\LogRuntimeException;
9
use Yiisoft\Log\Target;
10
11
/**
12
 * FileTarget records log messages in a file.
13
 *
14
 * The log file is specified via {@see logFile}. If the size of the log file exceeds
15
 * {@see maxFileSize} (in kilo-bytes), a rotation will be performed, which renames
16
 * the current log file by suffixing the file name with '.1'. All existing log
17
 * files are moved backwards by one place, i.e., '.2' to '.3', '.1' to '.2', and so on.
18
 * The property {@see maxLogFiles} specifies how many history files to keep.
19
 */
20
class FileTarget extends Target
21
{
22
    /**
23
     * @var string log file path. If not set, it will use the "/tmp/app.log" file.
24
     * The directory containing the log files will be automatically created if not existing.
25
     */
26
    private string $logFile;
27
    /**
28
     * @var int|null the permission to be set for newly created log files.
29
     * This value will be used by PHP chmod() function. No umask will be applied.
30
     * If not set, the permission will be determined by the current environment.
31
     */
32
    private ?int $fileMode;
33
    /**
34
     * @var int the permission to be set for newly created directories.
35
     * This value will be used by PHP chmod() function. No umask will be applied.
36
     * Defaults to 0775, meaning the directory is read-writable by owner and group,
37
     * but read-only for other users.
38
     */
39
    private int $dirMode;
40
41
    private ?FileRotatorInterface $rotator;
42
43 7
    public function __construct(
44
        string $logFile = '/tmp/app.log',
45
        FileRotatorInterface $rotator = null,
46
        int $dirMode = 0775,
47
        int $fileMode = null
48
    ) {
49 7
        $this->logFile = $logFile;
50 7
        $this->rotator = $rotator;
51 7
        $this->dirMode = $dirMode;
52 7
        $this->fileMode = $fileMode;
53
    }
54
55
    /**
56
     * Writes log messages to a file.
57
     * @throws LogRuntimeException if unable to open or write complete log to file
58
     */
59 6
    public function export(): void
60
    {
61 6
        $logPath = dirname($this->logFile);
62
63 6
        if (!file_exists($logPath)) {
64 2
            FileHelper::createDirectory($logPath, $this->dirMode);
65
        }
66
67 6
        $text = implode("\n", array_map([$this, 'formatMessage'], $this->getMessages())) . "\n";
68
69 6
        if (($fp = fopen($this->logFile, 'ab')) === false) {
70
            throw new LogRuntimeException("Unable to append to log file: {$this->logFile}");
71
        }
72
73 6
        @flock($fp, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for flock(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

73
        /** @scrutinizer ignore-unhandled */ @flock($fp, LOCK_EX);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
74 6
        if ($this->rotator !== null) {
75
            // clear stat cache to ensure getting the real current file size and not a cached one
76
            // this may result in rotating twice when cached file size is used on subsequent calls
77 4
            clearstatcache();
78
        }
79 6
        if ($this->rotator !== null && @filesize($this->logFile) > $this->rotator->getMaxFileSize() * 1024) {
80 3
            @flock($fp, LOCK_UN);
81 3
            @fclose($fp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

81
            /** @scrutinizer ignore-unhandled */ @fclose($fp);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
82 3
            $this->rotator->rotateFile($this->logFile);
83 3
            $writeResult = @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
84 3
            $this->checkWrittenResult($writeResult, $text);
85
        } else {
86 6
            $writeResult = @fwrite($fp, $text);
87 6
            $this->checkWrittenResult($writeResult, $text);
88 6
            @flock($fp, LOCK_UN);
89 6
            @fclose($fp);
90
        }
91 6
        if ($this->fileMode !== null) {
92 2
            @chmod($this->logFile, $this->fileMode);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

92
            /** @scrutinizer ignore-unhandled */ @chmod($this->logFile, $this->fileMode);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
93
        }
94
    }
95
96 6
    private function checkWrittenResult($writeResult, string $text): void
97
    {
98 6
        if ($writeResult === false) {
99
            $error = error_get_last();
100
            throw new LogRuntimeException("Unable to export log through file: {$error['message']}");
101
        }
102 6
        $textSize = strlen($text);
103 6
        if ($writeResult < $textSize) {
104
            throw new LogRuntimeException("Unable to export whole log through file! Wrote $writeResult out of $textSize bytes.");
105
        }
106
    }
107
}
108