FileLog.php 6.2 KB

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