Passed
Pull Request — master (#7)
by Alexander
01:20
created

FileTarget::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 10
ccs 5
cts 5
cp 1
crap 1
rs 10
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|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 15
    public function __construct(
44
        string $logFile = '/tmp/app.log',
45
        FileRotatorInterface $rotator = null,
46
        int $dirMode = 0775,
47
        int $fileMode = null
48
    ) {
49 15
        $this->logFile = $logFile;
50 15
        $this->rotator = $rotator;
51 15
        $this->dirMode = $dirMode;
52 15
        $this->fileMode = $fileMode;
53
    }
54
55
    /**
56
     * Writes log messages to a file.
57
     * Starting from version 2.0.14, this method throws LogRuntimeException in case the log can not be exported.
58
     * @throws LogRuntimeException if unable to open or write complete log to file
59
     */
60 14
    public function export(): void
61
    {
62 14
        $logPath = dirname($this->logFile);
63
64 14
        if (!file_exists($logPath)) {
65 2
            FileHelper::createDirectory($logPath, $this->dirMode);
66
        }
67
68 14
        $text = implode("\n", array_map([$this, 'formatMessage'], $this->getMessages())) . "\n";
69
70 14
        if (($fp = fopen($this->logFile, 'ab')) === false) {
71
            throw new LogRuntimeException("Unable to append to log file: {$this->logFile}");
72
        }
73
74 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

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

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

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