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 <dev@alphaframework.org>
15: * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
16: * @copyright Copyright (c) 2016, 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: try {
113: $logsDir = $config->get('app.file.store.dir').'logs';
114:
115: if (!file_exists($logsDir)) {
116: if (!mkdir($logsDir, 0766)) {
117: throw new PHPException('Could not create the directory ['.$logsDir.']');
118: }
119: }
120:
121: $fp = fopen($this->path, 'a+');
122: if (!fputcsv($fp, $line, ',', '"', '\\')) {
123: throw new PHPException('Could not write to the CSV file ['.$this->path.']');
124: }
125:
126: if ($this->checkFileSize() >= $this->maxSize) {
127: $this->backupFile();
128: }
129: } catch (\Exception $e) {
130: echo 'Unable to write to the log file ['.$this->path.'], error ['.$e->getMessage().']';
131: exit;
132: }
133: }
134: }
135: }
136:
137: /**
138: * Returns the size in megabytes of the log file on disc.
139: *
140: * @return float
141: *
142: * @since 1.0
143: */
144: private function checkFileSize()
145: {
146: clearstatcache();
147: $size = filesize($this->path);
148:
149: return ($size / 1024) /1024;
150: }
151:
152: /**
153: * Creates a backup of the log file, which has the same file name and location as the
154: * current file plus a timestamp appended.
155: *
156: * @since 1.0
157: */
158: private function backupFile()
159: {
160: // generate the name of the backup file name to contain a timestampe
161: $backName = str_replace('.log', '-backup-'.date('y-m-d').'.log', $this->path);
162:
163: // renames the logfile as the value of $backName
164: rename($this->path, $backName);
165:
166: FileUtils::zip($backName, $backName.'.zip');
167: unlink($backName);
168:
169: // creates a new log file, and sets it's permission for writing!
170: $fp = fopen($this->path, 'a+'); // remember set directory permissons to allow creation!
171: fclose($fp);
172: //s ets the new permission to rw+:rw+:rw+
173: chmod($this->path, 0666);
174: }
175:
176: /**
177: * {@inheritdoc}
178: */
179: public function renderLog($cols)
180: {
181: // render the start of the table
182: $body = '<table class="table">';
183: $body .= '<tr>';
184: foreach ($cols as $heading) {
185: $body .= '<th>'.$heading.'</th>';
186: }
187: $body .= '</tr>';
188:
189: $fp = fopen($this->path, 'r');
190:
191: while (($line = fgetcsv($fp)) !== false) {
192: $body .= '<tr>';
193:
194: for ($col = 0; $col < count($line); ++$col) {
195:
196: // if it is an error log, render the error types field in different colours
197: if ($col == 1 && $cols[1] == 'Level') {
198: switch ($line[$col]) {
199: case 'DEBUG':
200: $body .= '<td class="debug">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
201: break;
202: case 'INFO':
203: $body .= '<td class="info">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
204: break;
205: case 'WARN':
206: $body .= '<td class="warn">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
207: break;
208: case 'ERROR':
209: $body .= '<td class="error">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
210: break;
211: case 'FATAL':
212: $body .= '<td class="fatal">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
213: break;
214: case 'SQL':
215: $body .= '<td class="sql">'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
216: break;
217: default:
218: $body .= '<td>'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
219: break;
220: }
221: } else {
222: if ($cols[$col] == 'Message') {
223: $body .= '<td><pre>'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</pre></td>';
224: } else {
225: $body .= '<td>'.htmlentities($line[$col], ENT_COMPAT, 'utf-8').'</td>';
226: }
227: }
228: }
229:
230: $body .= '</tr>';
231: }
232:
233: $body .= '</table>';
234:
235: return $body;
236: }
237: }
238: