Passed
Push — master ( 31a27c...56b513 )
by Evgeniy
02:04
created

FileTarget   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 87
Duplicated Lines 0 %

Test Coverage

Coverage 89.19%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 11
eloc 38
c 2
b 0
f 0
dl 0
loc 87
ccs 33
cts 37
cp 0.8919
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
B export() 0 34 7
A checkWrittenResult() 0 9 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
/**
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 7
        parent::__construct();
54 7
    }
55
56
    /**
57
     * Writes log messages to a file.
58
     *
59
     * @throws RuntimeException if unable to open or write complete log to file
60
     */
61 6
    public function export(): void
62
    {
63 6
        $logPath = dirname($this->logFile);
64
65 6
        if (!file_exists($logPath)) {
66 2
            FileHelper::createDirectory($logPath, $this->dirMode);
67
        }
68
69 6
        $text = $this->formatMessages("\n");
70
71 6
        if (($fp = fopen($this->logFile, 'ab')) === false) {
72
            throw new RuntimeException("Unable to append to log file: {$this->logFile}");
73
        }
74
75 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

75
        /** @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...
76 6
        if ($this->rotator !== null) {
77
            // clear stat cache to ensure getting the real current file size and not a cached one
78
            // this may result in rotating twice when cached file size is used on subsequent calls
79 4
            clearstatcache();
80
        }
81 6
        if ($this->rotator !== null && @filesize($this->logFile) > $this->rotator->getMaxFileSize() * 1024) {
82 3
            @flock($fp, LOCK_UN);
83 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

83
            /** @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...
84 3
            $this->rotator->rotateFile($this->logFile);
85 3
            $writeResult = @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
86 3
            $this->checkWrittenResult($writeResult, $text);
87
        } else {
88 6
            $writeResult = @fwrite($fp, $text);
89 6
            $this->checkWrittenResult($writeResult, $text);
90 6
            @flock($fp, LOCK_UN);
91 6
            @fclose($fp);
92
        }
93 6
        if ($this->fileMode !== null) {
94 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

94
            /** @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...
95
        }
96 6
    }
97
98 6
    private function checkWrittenResult($writeResult, string $text): void
99
    {
100 6
        if ($writeResult === false) {
101
            $error = error_get_last();
102
            throw new RuntimeException("Unable to export log through file: {$error['message']}");
103
        }
104 6
        $textSize = strlen($text);
105 6
        if ($writeResult < $textSize) {
106
            throw new RuntimeException("Unable to export whole log through file! Wrote $writeResult out of $textSize bytes.");
107
        }
108 6
    }
109
}
110