Completed
Push — master ( 67caf0...bdc372 )
by Dmitry
13:07 queued 04:28
created

framework/log/FileTarget.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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()](http://www.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](http://www.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 2
    public function init()
81
    {
82 2
        parent::init();
83 2
        if ($this->logFile === null) {
84
            $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log';
85
        } else {
86 2
            $this->logFile = Yii::getAlias($this->logFile);
87
        }
88 2
        $logPath = dirname($this->logFile);
89 2
        if (!is_dir($logPath)) {
90
            FileHelper::createDirectory($logPath, $this->dirMode, true);
91
        }
92 2
        if ($this->maxLogFiles < 1) {
93
            $this->maxLogFiles = 1;
94
        }
95 2
        if ($this->maxFileSize < 1) {
96
            $this->maxFileSize = 1;
97
        }
98 2
    }
99
100
    /**
101
     * Writes log messages to a file.
102
     * Starting from version 2.0.14, this method throws LogRuntimeException in case the log can not be exported.
103
     * @throws InvalidConfigException if unable to open the log file for writing
104
     * @throws LogRuntimeException if unable to write complete log to file
105
     */
106 2
    public function export()
107
    {
108 2
        $text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n";
109 2
        if (($fp = @fopen($this->logFile, 'a')) === false) {
110
            throw new InvalidConfigException("Unable to append to log file: {$this->logFile}");
111
        }
112 2
        @flock($fp, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
113 2
        if ($this->enableRotation) {
114
            // clear stat cache to ensure getting the real current file size and not a cached one
115
            // this may result in rotating twice when cached file size is used on subsequent calls
116 2
            clearstatcache();
117
        }
118 2
        if ($this->enableRotation && @filesize($this->logFile) > $this->maxFileSize * 1024) {
119 2
            $this->rotateFiles();
120 2
            @flock($fp, LOCK_UN);
121 2
            @fclose($fp);
122 2
            $writeResult = @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
123 2
            if ($writeResult === false) {
124
                $error = error_get_last();
125
                throw new LogRuntimeException("Unable to export log through file!: {$error['message']}");
126
            }
127 2
            $textSize = strlen($text);
128 2
            if ($writeResult < $textSize) {
129 2
                throw new LogRuntimeException("Unable to export whole log through file! Wrote $writeResult out of $textSize bytes.");
130
            }
131
        } else {
132 2
            $writeResult = @fwrite($fp, $text);
133 2
            if ($writeResult === false) {
134
                $error = error_get_last();
135
                throw new LogRuntimeException("Unable to export log through file!: {$error['message']}");
136
            }
137 2
            $textSize = strlen($text);
138 2
            if ($writeResult < $textSize) {
139
                throw new LogRuntimeException("Unable to export whole log through file! Wrote $writeResult out of $textSize bytes.");
140
            }
141 2
            @flock($fp, LOCK_UN);
142 2
            @fclose($fp);
143
        }
144 2
        if ($this->fileMode !== null) {
145
            @chmod($this->logFile, $this->fileMode);
146
        }
147 2
    }
148
149
    /**
150
     * Rotates log files.
151
     */
152 2
    protected function rotateFiles()
153
    {
154 2
        $file = $this->logFile;
155 2
        for ($i = $this->maxLogFiles; $i >= 0; --$i) {
156
            // $i == 0 is the original log file
157 2
            $rotateFile = $file . ($i === 0 ? '' : '.' . $i);
158 2
            if (is_file($rotateFile)) {
159
                // suppress errors because it's possible multiple processes enter into this section
160 2
                if ($i === $this->maxLogFiles) {
161 2
                    @unlink($rotateFile);
162 2
                    continue;
163
                }
164 2
                $newFile = $this->logFile . '.' . ($i + 1);
165 2
                $this->rotateByCopy ? $this->rotateByCopy($rotateFile, $newFile) : $this->rotateByRename($rotateFile, $newFile);
166 2
                if ($i === 0) {
167 2
                    $this->clearLogFile($rotateFile);
168
                }
169
            }
170
        }
171 2
    }
172
173
    /***
174
     * Clear log file without closing any other process open handles
175
     * @param string $rotateFile
176
     */
177 2
    private function clearLogFile($rotateFile)
178
    {
179 2
        if ($filePointer = @fopen($rotateFile, 'a')) {
180 2
            @ftruncate($filePointer, 0);
181 2
            @fclose($filePointer);
182
        }
183 2
    }
184
185
    /***
186
     * Copy rotated file into new file
187
     * @param string $rotateFile
188
     * @param string $newFile
189
     */
190 1
    private function rotateByCopy($rotateFile, $newFile)
191
    {
192 1
        @copy($rotateFile, $newFile);
193 1
        if ($this->fileMode !== null) {
194
            @chmod($newFile, $this->fileMode);
195
        }
196 1
    }
197
198
    /**
199
     * Renames rotated file into new file
200
     * @param string $rotateFile
201
     * @param string $newFile
202
     */
203 1
    private function rotateByRename($rotateFile, $newFile)
204
    {
205 1
        @rename($rotateFile, $newFile);
206 1
    }
207
}
208