Passed
Push — master ( 9dbdd9...d5a428 )
by Alexander
04:15
created

framework/log/FileTarget.php (5 issues)

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\log;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\helpers\FileHelper;
13
14
/**
15
 * FileTarget records log messages in a file.
16
 *
17
 * The log file is specified via [[logFile]]. If the size of the log file exceeds
18
 * [[maxFileSize]] (in kilo-bytes), a rotation will be performed, which renames
19
 * the current log file by suffixing the file name with '.1'. All existing log
20
 * files are moved backwards by one place, i.e., '.2' to '.3', '.1' to '.2', and so on.
21
 * The property [[maxLogFiles]] specifies how many history files to keep.
22
 *
23
 * @author Qiang Xue <[email protected]>
24
 * @since 2.0
25
 */
26
class FileTarget extends Target
27
{
28
    /**
29
     * @var string log file path or [path alias](guide:concept-aliases). If not set, it will use the "@runtime/logs/app.log" file.
30
     * The directory containing the log files will be automatically created if not existing.
31
     */
32
    public $logFile;
33
    /**
34
     * @var bool whether log files should be rotated when they reach a certain [[maxFileSize|maximum size]].
35
     * Log rotation is enabled by default. This property allows you to disable it, when you have configured
36
     * an external tools for log rotation on your server.
37
     * @since 2.0.3
38
     */
39
    public $enableRotation = true;
40
    /**
41
     * @var int maximum log file size, in kilo-bytes. Defaults to 10240, meaning 10MB.
42
     */
43
    public $maxFileSize = 10240; // in KB
44
    /**
45
     * @var int number of log files used for rotation. Defaults to 5.
46
     */
47
    public $maxLogFiles = 5;
48
    /**
49
     * @var int the permission to be set for newly created log files.
50
     * This value will be used by PHP chmod() function. No umask will be applied.
51
     * If not set, the permission will be determined by the current environment.
52
     */
53
    public $fileMode;
54
    /**
55
     * @var int the permission to be set for newly created directories.
56
     * This value will be used by PHP chmod() function. No umask will be applied.
57
     * Defaults to 0775, meaning the directory is read-writable by owner and group,
58
     * but read-only for other users.
59
     */
60
    public $dirMode = 0775;
61
    /**
62
     * @var bool Whether to rotate log files by copy and truncate in contrast to rotation by
63
     * renaming files. Defaults to `true` to be more compatible with log tailers and is windows
64
     * systems which do not play well with rename on open files. Rotation by renaming however is
65
     * a bit faster.
66
     *
67
     * The problem with windows systems where the [rename()](https://secure.php.net/manual/en/function.rename.php)
68
     * function does not work with files that are opened by some process is described in a
69
     * [comment by Martin Pelletier](https://secure.php.net/manual/en/function.rename.php#102274) in
70
     * the PHP documentation. By setting rotateByCopy to `true` you can work
71
     * around this problem.
72
     */
73
    public $rotateByCopy = true;
74
75
76
    /**
77
     * Initializes the route.
78
     * This method is invoked after the route is created by the route manager.
79
     */
80 3
    public function init()
81
    {
82 3
        parent::init();
83 3
        if ($this->logFile === null) {
84
            $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log';
85
        } else {
86 3
            $this->logFile = Yii::getAlias($this->logFile);
87
        }
88 3
        if ($this->maxLogFiles < 1) {
89
            $this->maxLogFiles = 1;
90
        }
91 3
        if ($this->maxFileSize < 1) {
92
            $this->maxFileSize = 1;
93
        }
94 3
    }
95
96
    /**
97
     * Writes log messages to a file.
98
     * Starting from version 2.0.14, this method throws LogRuntimeException in case the log can not be exported.
99
     * @throws InvalidConfigException if unable to open the log file for writing
100
     * @throws LogRuntimeException if unable to write complete log to file
101
     */
102 2
    public function export()
103
    {
104 2
        if (strpos($this->logFile, '://') === false || strncmp($this->logFile, 'file://', 7) === 0) {
105 2
            $logPath = dirname($this->logFile);
106 2
            FileHelper::createDirectory($logPath, $this->dirMode, true);
107
        }
108
109 2
        $text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n";
110 2
        if (($fp = @fopen($this->logFile, 'a')) === false) {
111
            throw new InvalidConfigException("Unable to append to log file: {$this->logFile}");
112
        }
113 2
        @flock($fp, LOCK_EX);
114 2
        if ($this->enableRotation) {
115
            // clear stat cache to ensure getting the real current file size and not a cached one
116
            // this may result in rotating twice when cached file size is used on subsequent calls
117 2
            clearstatcache();
118
        }
119 2
        if ($this->enableRotation && @filesize($this->logFile) > $this->maxFileSize * 1024) {
120 2
            @flock($fp, LOCK_UN);
121 2
            @fclose($fp);
122 2
            $this->rotateFiles();
123 2
            $writeResult = @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
124 2
            if ($writeResult === false) {
125
                $error = error_get_last();
126
                throw new LogRuntimeException("Unable to export log through file ({$this->logFile})!: {$error['message']}");
127
            }
128 2
            $textSize = strlen($text);
129 2
            if ($writeResult < $textSize) {
130 2
                throw new LogRuntimeException("Unable to export whole log through file ({$this->logFile})! Wrote $writeResult out of $textSize bytes.");
131
            }
132
        } else {
133 2
            $writeResult = @fwrite($fp, $text);
134 2
            if ($writeResult === false) {
135
                $error = error_get_last();
136
                throw new LogRuntimeException("Unable to export log through file ({$this->logFile})!: {$error['message']}");
137
            }
138 2
            $textSize = strlen($text);
139 2
            if ($writeResult < $textSize) {
140
                throw new LogRuntimeException("Unable to export whole log through file ({$this->logFile})! Wrote $writeResult out of $textSize bytes.");
141
            }
142 2
            @flock($fp, LOCK_UN);
143 2
            @fclose($fp);
144
        }
145 2
        if ($this->fileMode !== null) {
146
            @chmod($this->logFile, $this->fileMode);
147
        }
148 2
    }
149
150
    /**
151
     * Rotates log files.
152
     */
153 2
    protected function rotateFiles()
154
    {
155 2
        $file = $this->logFile;
156 2
        for ($i = $this->maxLogFiles; $i >= 0; --$i) {
157
            // $i == 0 is the original log file
158 2
            $rotateFile = $file . ($i === 0 ? '' : '.' . $i);
159 2
            if (is_file($rotateFile)) {
160
                // suppress errors because it's possible multiple processes enter into this section
161 2
                if ($i === $this->maxLogFiles) {
162 2
                    @unlink($rotateFile);
163 2
                    continue;
164
                }
165 2
                $newFile = $this->logFile . '.' . ($i + 1);
166 2
                $this->rotateByCopy ? $this->rotateByCopy($rotateFile, $newFile) : $this->rotateByRename($rotateFile, $newFile);
167 2
                if ($i === 0) {
168 2
                    $this->clearLogFile($rotateFile);
169
                }
170
            }
171
        }
172 2
    }
173
174
    /***
175
     * Clear log file without closing any other process open handles
176
     * @param string $rotateFile
177
     */
178 2
    private function clearLogFile($rotateFile)
179
    {
180 2
        if ($filePointer = @fopen($rotateFile, 'a')) {
181 2
            @ftruncate($filePointer, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ftruncate(). 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

181
            /** @scrutinizer ignore-unhandled */ @ftruncate($filePointer, 0);

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...
182 2
            @fclose($filePointer);
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

182
            /** @scrutinizer ignore-unhandled */ @fclose($filePointer);

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...
183
        }
184 2
    }
185
186
    /***
187
     * Copy rotated file into new file
188
     * @param string $rotateFile
189
     * @param string $newFile
190
     */
191 1
    private function rotateByCopy($rotateFile, $newFile)
192
    {
193 1
        @copy($rotateFile, $newFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). 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

193
        /** @scrutinizer ignore-unhandled */ @copy($rotateFile, $newFile);

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...
194 1
        if ($this->fileMode !== null) {
195
            @chmod($newFile, $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

195
            /** @scrutinizer ignore-unhandled */ @chmod($newFile, $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...
196
        }
197 1
    }
198
199
    /**
200
     * Renames rotated file into new file
201
     * @param string $rotateFile
202
     * @param string $newFile
203
     */
204 1
    private function rotateByRename($rotateFile, $newFile)
205
    {
206 1
        @rename($rotateFile, $newFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). 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

206
        /** @scrutinizer ignore-unhandled */ @rename($rotateFile, $newFile);

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...
207 1
    }
208
}
209