1<?php 2/** 3 * File Storage stream for Logging 4 * 5 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 6 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 7 * 8 * Licensed under The MIT License 9 * For full copyright and license information, please see the LICENSE.txt 10 * Redistributions of files must retain the above copyright notice. 11 * 12 * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 13 * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project 14 * @package Cake.Log.Engine 15 * @since CakePHP(tm) v 1.3 16 * @license https://opensource.org/licenses/mit-license.php MIT License 17 */ 18 19App::uses('BaseLog', 'Log/Engine'); 20App::uses('Hash', 'Utility'); 21App::uses('CakeNumber', 'Utility'); 22 23/** 24 * File Storage stream for Logging. Writes logs to different files 25 * based on the type of log it is. 26 * 27 * @package Cake.Log.Engine 28 */ 29class FileLog extends BaseLog { 30 31/** 32 * Default configuration values 33 * 34 * @var array 35 * @see FileLog::__construct() 36 */ 37 protected $_defaults = array( 38 'path' => LOGS, 39 'file' => null, 40 'types' => null, 41 'scopes' => array(), 42 'rotate' => 10, 43 'size' => 10485760, // 10MB 44 'mask' => null, 45 ); 46 47/** 48 * Path to save log files on. 49 * 50 * @var string 51 */ 52 protected $_path = null; 53 54/** 55 * Log file name 56 * 57 * @var string 58 */ 59 protected $_file = null; 60 61/** 62 * Max file size, used for log file rotation. 63 * 64 * @var int 65 */ 66 protected $_size = null; 67 68/** 69 * Constructs a new File Logger. 70 * 71 * Config 72 * 73 * - `types` string or array, levels the engine is interested in 74 * - `scopes` string or array, scopes the engine is interested in 75 * - `file` Log file name 76 * - `path` The path to save logs on. 77 * - `size` Used to implement basic log file rotation. If log file size 78 * reaches specified size the existing file is renamed by appending timestamp 79 * to filename and new log file is created. Can be integer bytes value or 80 * human reabable string values like '10MB', '100KB' etc. 81 * - `rotate` Log files are rotated specified times before being removed. 82 * If value is 0, old versions are removed rather then rotated. 83 * - `mask` A mask is applied when log files are created. Left empty no chmod 84 * is made. 85 * 86 * @param array $config Options for the FileLog, see above. 87 */ 88 public function __construct($config = array()) { 89 $config = Hash::merge($this->_defaults, $config); 90 parent::__construct($config); 91 } 92 93/** 94 * Sets protected properties based on config provided 95 * 96 * @param array $config Engine configuration 97 * @return array 98 */ 99 public function config($config = array()) { 100 parent::config($config); 101 102 if (!empty($config['path'])) { 103 $this->_path = $config['path']; 104 } 105 if (Configure::read('debug') && !is_dir($this->_path)) { 106 mkdir($this->_path, 0775, true); 107 } 108 109 if (!empty($config['file'])) { 110 $this->_file = $config['file']; 111 if (substr($this->_file, -4) !== '.log') { 112 $this->_file .= '.log'; 113 } 114 } 115 if (!empty($config['size'])) { 116 if (is_numeric($config['size'])) { 117 $this->_size = (int)$config['size']; 118 } else { 119 $this->_size = CakeNumber::fromReadableSize($config['size']); 120 } 121 } 122 123 return $this->_config; 124 } 125 126/** 127 * Implements writing to log files. 128 * 129 * @param string $type The type of log you are making. 130 * @param string $message The message you want to log. 131 * @return bool success of write. 132 */ 133 public function write($type, $message) { 134 $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; 135 $filename = $this->_getFilename($type); 136 if (!empty($this->_size)) { 137 $this->_rotateFile($filename); 138 } 139 140 $pathname = $this->_path . $filename; 141 if (empty($this->_config['mask'])) { 142 return file_put_contents($pathname, $output, FILE_APPEND); 143 } 144 145 $exists = file_exists($pathname); 146 $result = file_put_contents($pathname, $output, FILE_APPEND); 147 static $selfError = false; 148 if (!$selfError && !$exists && !chmod($pathname, (int)$this->_config['mask'])) { 149 $selfError = true; 150 trigger_error(__d( 151 'cake_dev', 'Could not apply permission mask "%s" on log file "%s"', 152 array($this->_config['mask'], $pathname)), E_USER_WARNING); 153 $selfError = false; 154 } 155 return $result; 156 } 157 158/** 159 * Get filename 160 * 161 * @param string $type The type of log. 162 * @return string File name 163 */ 164 protected function _getFilename($type) { 165 $debugTypes = array('notice', 'info', 'debug'); 166 167 if (!empty($this->_file)) { 168 $filename = $this->_file; 169 } elseif ($type === 'error' || $type === 'warning') { 170 $filename = 'error.log'; 171 } elseif (in_array($type, $debugTypes)) { 172 $filename = 'debug.log'; 173 } else { 174 $filename = $type . '.log'; 175 } 176 177 return $filename; 178 } 179 180/** 181 * Rotate log file if size specified in config is reached. 182 * Also if `rotate` count is reached oldest file is removed. 183 * 184 * @param string $filename Log file name 185 * @return mixed True if rotated successfully or false in case of error, otherwise null. 186 * Void if file doesn't need to be rotated. 187 */ 188 protected function _rotateFile($filename) { 189 $filepath = $this->_path . $filename; 190 if (version_compare(PHP_VERSION, '5.3.0') >= 0) { 191 clearstatcache(true, $filepath); 192 } else { 193 clearstatcache(); 194 } 195 196 if (!file_exists($filepath) || 197 filesize($filepath) < $this->_size 198 ) { 199 return null; 200 } 201 202 if ($this->_config['rotate'] === 0) { 203 $result = unlink($filepath); 204 } else { 205 $result = rename($filepath, $filepath . '.' . time()); 206 } 207 208 $files = glob($filepath . '.*'); 209 if ($files) { 210 $filesToDelete = count($files) - $this->_config['rotate']; 211 while ($filesToDelete > 0) { 212 unlink(array_shift($files)); 213 $filesToDelete--; 214 } 215 } 216 217 return $result; 218 } 219 220} 221