      1: <?php
      2:
      3: /**
      4:  *
      5:  * Generic log file class to encapsulate common file I/O and rendering calls
      6:  *
      7:  * @package alpha::util
      8:  * @since 1.0
      9:  * @author John Collins <dev@alphaframework.org>
     10:  * @version $Id: LogFile.inc 1756 2014-04-08 22:54:10Z alphadevx $
     11:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
     12:  * @copyright Copyright (c) 2014, John Collins (founder of Alpha Framework).
     13:  * All rights reserved.
     14:  *
     15:  * <pre>
     16:  * Redistribution and use in source and binary forms, with or
     17:  * without modification, are permitted provided that the
     18:  * following conditions are met:
     19:  *
     20:  * * Redistributions of source code must retain the above
     21:  *   copyright notice, this list of conditions and the
     22:  *   following disclaimer.
     23:  * * Redistributions in binary form must reproduce the above
     24:  *   copyright notice, this list of conditions and the
     25:  *   following disclaimer in the documentation and/or other
     26:  *   materials provided with the distribution.
     27:  * * Neither the name of the Alpha Framework nor the names
     28:  *   of its contributors may be used to endorse or promote
     29:  *   products derived from this software without specific
     30:  *   prior written permission.
     31:  *
     32:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
     33:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
     34:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     35:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     36:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
     37:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     38:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     39:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     40:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     41:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     42:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     43:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     44:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     45:  * </pre>
     46:  *
     47:  */
     48: class LogFile {
     49:     /**
     50:      * The log file path
     51:      *
     52:      * @var string
     53:      * @since 1.0
     54:      */
     55:     private $path;
     56:
     57:     /**
     58:      * An array of column headers use when rendering the log file
     59:      *
     60:      * @var array
     61:      * @since 1.0
     62:      */
     63:     private $columnHeadings;
     64:
     65:     /**
     66:      * The value seperator to use in the file (default is a pipe)
     67:      *
     68:      * @var string
     69:      * @since 1.0
     70:      */
     71:     private $seperator = '|';
     72:
     73:     /**
     74:      * The maximum size of the log file in megabytes before a backup is created and a
     75:      * new file is created, default is 5
     76:      *
     77:      * @var integer
     78:      * @since 1.0
     79:      */
     80:     private $MaxSize = 5;
     81:
     82:     /**
     83:      * The constructor
     84:      *
     85:      * @param string $path
     86:      * @since 1.0
     87:      */
     88:     public function __construct($path) {
     89:         $this->path = $path;
     90:     }
     91:
     92:     /**
     93:      * Set the max log size in megabytes
     94:      *
     95:      * @param integer $MaxSize
     96:      * @since 1.0
     97:      */
     98:     public function setMaxSize($MaxSize) {
     99:         $this->MaxSize = $MaxSize;
    100:     }
    101:
    102:     /**
    103:      * Set the value seperator
    104:      *
    105:      * @param string $seperator
    106:      * @since 1.0
    107:      */
    108:     public function setSeperator($seperator) {
    109:         $this->seperator = $seperator;
    110:     }
    111:
    112:     /**
    113:      * Writes a line of data to the log file
    114:      *
    115:      * $param array $line
    116:      * @since 1.0
    117:      */
    118:     public function writeLine($line) {
    119:         global $config;
    120:
    121:         $mergedLine = '';
    122:
    123:         $colCount = count($line);
    124:
    125:         for($i = 0; $i < $colCount; $i++) {
    126:             /*
    127:              * we need to ensure that the seperator is not in the value anywhere, as it
    128:              * would cause problems later when reading the log
    129:              */
    130:             $value = str_replace($this->seperator, '', $line[$i]);
    131:             if ($i == ($colCount-1))
    132:                 $mergedLine .= $value.$this->seperator."\n";
    133:             else
    134:                 $mergedLine .= $value.$this->seperator;
    135:         }
    136:
    137:         try{
    138:             file_put_contents($this->path, $mergedLine, FILE_APPEND|LOCK_EX);
    139:
    140:             if($this->checkFileSize() >= $this->MaxSize) {
    141:                 $this->backupFile();
    142:             }
    143:         }catch(Exception $e) {
    144:             try {
    145:                 $logsDir = $config->get('app.file.store.dir').'logs';
    146:
    147:                 if(!file_exists($logsDir))
    148:                     mkdir($logsDir, 0766);
    149:
    150:                 file_put_contents($this->path, $mergedLine, FILE_APPEND|LOCK_EX);
    151:
    152:                 if($this->checkFileSize() >= $this->MaxSize) {
    153:                     $this->backupFile();
    154:                 }
    155:             }catch(Exception $e) {
    156:                 echo '<p class="error"><br>Unable to write to the log file ['.$this->path.'], error ['.$e->getMessage().']</p>';
    157:                 exit;
    158:             }
    159:         }
    160:     }
    161:
    162:     /**
    163:      * Returns the size in megabytes of the log file on disc
    164:      *
    165:      * @return float
    166:      * @since 1.0
    167:      */
    168:     private function checkFileSize() {
    169:         $size = filesize($this->path);
    170:
    171:         return ($size/1024)/1024;
    172:     }
    173:
    174:     /**
    175:      * Creates a backup of the log file, which has the same file name and location as the
    176:      * current file plus a timestamp appended
    177:      *
    178:      * @since 1.0
    179:      */
    180:     private function backupFile() {
    181:         // generate the name of the backup file name to contain a timestampe
    182:         $backName = str_replace('.log', '-backup-'.date("y-m-d H.i.s").'.log', $this->path);
    183:
    184:         // renames the logfile as the value of $backName
    185:         rename($this->path, $backName);
    186:         //creates a new log file, and sets it's permission for writting!
    187:         $fp = fopen($this->path, 'a+'); // remember set directory permissons to allow creation!
    188:         fclose($fp);
    189:         //sets the new permission to rw+:rw+:rw+
    190:         chmod($this->path, 0666);
    191:     }
    192:
    193:     /**
    194:      * Renders a log file as a HTML table
    195:      *
    196:      * $param array $cols The headings to use when rendering the log file
    197:      * @since 1.0
    198:      */
    199:     public function renderLog($cols) {
    200:         // render the start of the table
    201:         echo '<table class="table">';
    202:         echo '<tr>';
    203:         foreach($cols as $heading)
    204:             echo '<th>'.$heading.'</th>';
    205:         echo '</tr>';
    206:
    207:         // now read the file and render the data
    208:         $LogFile = file_get_contents($this->path);
    209:         $fields = explode($this->seperator, $LogFile);
    210:         $totalLines = (count($fields)-1)/count($cols);
    211:
    212:         for($line = 0; $line < $totalLines; $line++) {
    213:             $count = count($cols);
    214:
    215:             echo '<tr>';
    216:
    217:             for($col = 0; $col < $count; $col++) {
    218:                 $index = ($line*count($cols))+$col;
    219:
    220:                 // if it is an error log, render the error types field in different colours
    221:                 if($col == 1 && $cols[1] == 'Level'){
    222:                     switch($fields[$index]) {
    223:                         case 'DEBUG':
    224:                             echo '<td class="debug">'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    225:                         break;
    226:                         case 'INFO':
    227:                             echo '<td class="info">'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    228:                         break;
    229:                         case 'WARN':
    230:                             echo '<td class="warn">'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    231:                         break;
    232:                         case 'ERROR':
    233:                             echo '<td class="error">'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    234:                         break;
    235:                         case 'FATAL':
    236:                             echo '<td class="fatal">'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    237:                         break;
    238:                         case 'SQL':
    239:                             echo '<td class="sql">'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    240:                         break;
    241:                         default:
    242:                             echo '<td>'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    243:                         break;
    244:                     }
    245:                 }else{
    246:                     if($cols[$col] == 'Message')
    247:                         echo '<td><pre>'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</pre></td>';
    248:                     else
    249:                         echo '<td>'.htmlentities($fields[$index], ENT_COMPAT, 'utf-8').'</td>';
    250:                 }
    251:             }
    252:
    253:             echo '</tr>';
    254:         }
    255:
    256:         echo '</table>';
    257:     }
    258: }
    259: ?>