Passed
Pull Request — master (#7)
by Dmitriy
07:20
created

FileTarget   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Test Coverage

Coverage 88.24%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 37
c 1
b 0
f 0
dl 0
loc 89
rs 10
ccs 30
cts 34
cp 0.8824
wmc 11

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 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 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 [[logFile]]. If the size of the log file exceeds
15
 * [[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 [[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 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
    /**
42
     * @var FileRotatorInterface
43
     */
44
    private ?FileRotatorInterface $rotator;
45
46 15
    public function __construct(
47
        string $logFile = '/tmp/app.log',
48
        FileRotatorInterface $rotator = null,
49
        int $dirMode = 0775,
50
        int $fileMode = null
51
    ) {
52 15
        $this->logFile = $logFile;
53 15
        $this->rotator = $rotator;
54 15
        $this->dirMode = $dirMode;
55 15
        $this->fileMode = $fileMode;
56
    }
57
58
    /**
59
     * Writes log messages to a file.
60
     * Starting from version 2.0.14, this method throws LogRuntimeException in case the log can not be exported.
61
     * @throws LogRuntimeException if unable to open or write complete log to file
62
     */
63 14
    public function export(): void
64
    {
65 14
        $logPath = dirname($this->logFile);
66
67 14
        if (!file_exists($logPath)) {
68 2
            FileHelper::createDirectory($logPath, $this->dirMode);
69
        }
70
71 14
        $text = implode("\n", array_map([$this, 'formatMessage'], $this->getMessages())) . "\n";
72
73 14
        if (($fp = fopen($this->logFile, 'ab')) === false) {
74
            throw new LogRuntimeException("Unable to append to log file: {$this->logFile}");
75
        }
76
77 14
        @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

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

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

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