Completed
Push — develop ( c95aa7...49d071 )
by John
03:12
created

LogProviderFile::writeLine()   C

Complexity

Conditions 8
Paths 31

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
dl 0
loc 32
rs 5.3846
c 2
b 0
f 1
cc 8
eloc 18
nc 31
nop 1
1
<?php
2
3
namespace Alpha\Util\Logging;
4
5
use Alpha\Util\Config\ConfigProvider;
6
use Alpha\Exception\PHPException;
7
use Alpha\Util\File\FileUtils;
8
9
/**
10
 * Generic log file class to encapsulate common file I/O and rendering calls.
11
 *
12
 * @since 1.0
13
 *
14
 * @author John Collins <[email protected]>
15
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
16
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
17
 * All rights reserved.
18
 *
19
 * <pre>
20
 * Redistribution and use in source and binary forms, with or
21
 * without modification, are permitted provided that the
22
 * following conditions are met:
23
 *
24
 * * Redistributions of source code must retain the above
25
 *   copyright notice, this list of conditions and the
26
 *   following disclaimer.
27
 * * Redistributions in binary form must reproduce the above
28
 *   copyright notice, this list of conditions and the
29
 *   following disclaimer in the documentation and/or other
30
 *   materials provided with the distribution.
31
 * * Neither the name of the Alpha Framework nor the names
32
 *   of its contributors may be used to endorse or promote
33
 *   products derived from this software without specific
34
 *   prior written permission.
35
 *
36
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
37
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
38
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
39
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
41
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
43
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
45
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
46
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
47
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
48
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49
 * </pre>
50
 */
51
class LogProviderFile implements LogProviderInterface
52
{
53
    /**
54
     * The log file path.
55
     *
56
     * @var string
57
     *
58
     * @since 1.0
59
     */
60
    private $path;
61
62
    /**
63
     * The maximum size of the log file in megabytes before a backup is created and a
64
     * new file is created, default is 5.
65
     *
66
     * @var int
67
     *
68
     * @since 1.0
69
     */
70
    private $maxSize = 5;
71
72
    /**
73
     * Set the file path.
74
     *
75
     * @param string $path
76
     *
77
     * @since 2.0
78
     */
79
    public function setPath($path)
80
    {
81
        $this->path = $path;
82
    }
83
84
    /**
85
     * Set the max log size in megabytes.
86
     *
87
     * @param int $maxSize
88
     *
89
     * @since 1.0
90
     */
91
    public function setMaxSize($maxSize)
92
    {
93
        $this->maxSize = $maxSize;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function writeLine($line)
100
    {
101
        $config = ConfigProvider::getInstance();
102
103
        if ($this->path != '') {
104
            try {
105
                $fp = fopen($this->path, 'a+');
106
                fputcsv($fp, $line, ',', '"', '\\');
107
108
                if ($this->checkFileSize() >= $this->maxSize) {
109
                    $this->backupFile();
110
                }
111
            } catch (\Exception $e) {
112
                $logsDir = $config->get('app.file.store.dir').'logs';
113
114
                if (!file_exists($logsDir)) {
115
                    if (!mkdir($logsDir, 0766)) {
116
                        throw new PHPException('Could not create the directory ['.$logsDir.']');
117
                    }
118
                }
119
120
                $fp = fopen($this->path, 'a+');
121
                if (!fputcsv($fp, $line, ',', '"', '\\')) {
122
                    throw new PHPException('Could not write to the CSV file ['.$this->path.']');
123
                }
124
125
                if ($this->checkFileSize() >= $this->maxSize) {
126
                    $this->backupFile();
127
                }
128
            }
129
        }
130
    }
131
132
    /**
133
     * Returns the size in megabytes of the log file on disc.
134
     *
135
     * @return float
136
     *
137
     * @since 1.0
138
     */
139
    private function checkFileSize()
140
    {
141
        clearstatcache();
142
        $size = filesize($this->path);
143
144
        return ($size / 1024) /1024;
145
    }
146
147
    /**
148
     * Creates a backup of the log file, which has the same file name and location as the
149
     * current file plus a timestamp appended.
150
     *
151
     * @since 1.0
152
     */
153
    private function backupFile()
154
    {
155
        // generate the name of the backup file name to contain a timestampe
156
        $backName = str_replace('.log', '-backup-'.date('y-m-d').'.log', $this->path);
157
158
        // renames the logfile as the value of $backName
159
        rename($this->path, $backName);
160
161
        FileUtils::zip($backName, $backName.'.zip');
162
        unlink($backName);
163
164
        // creates a new log file, and sets it's permission for writing!
165
        $fp = fopen($this->path, 'a+'); // remember set directory permissons to allow creation!
166
        fclose($fp);
167
        //s ets the new permission to rw+:rw+:rw+
168
        chmod($this->path, 0666);
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function renderLog($cols)
175
    {
176
        // render the start of the table
177
        $body = '<table class="table">';
178
        $body .= '<tr>';
179
        foreach ($cols as $heading) {
180
            $body .= '<th>'.$heading.'</th>';
181
        }
182
        $body .= '</tr>';
183
184
        $fp = fopen($this->path, 'r');
185
186
        while (($line = fgetcsv($fp)) !== false) {
187
            $body .= '<tr>';
188
189
            for ($col = 0; $col < count($line); ++$col) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
190
191
                // if it is an error log, render the error types field in different colours
192
                if ($col == 1 && $cols[1] == 'Level') {
193
                    switch ($line[$col]) {
194
                        case 'DEBUG':
195
                            $body .= '<td class="debug">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
196
                        break;
197
                        case 'INFO':
198
                            $body .= '<td class="info">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
199
                        break;
200
                        case 'WARN':
201
                            $body .= '<td class="warn">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
202
                        break;
203
                        case 'ERROR':
204
                            $body .= '<td class="error">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
205
                        break;
206
                        case 'FATAL':
207
                            $body .= '<td class="fatal">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
208
                        break;
209
                        case 'SQL':
210
                            $body .= '<td class="sql">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
211
                        break;
212
                        default:
213
                            $body .= '<td>'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
214
                        break;
215
                    }
216
                } else {
217
                    if ($cols[$col] == 'Message') {
218
                        $body .= '<td><pre>'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</pre></td>';
219
                    } else {
220
                        $body .= '<td>'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
221
                    }
222
                }
223
            }
224
225
            $body .= '</tr>';
226
        }
227
228
        $body .= '</table>';
229
230
        return $body;
231
    }
232
}
233