FileLog.php 5.4 KB

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