/includes/log-handlers/class-wc-log-handler-file.php

  1. <?php 
  2. if ( ! defined( 'ABSPATH' ) ) { 
  3. exit; // Exit if accessed directly 
  4.  
  5. /** 
  6. * Handles log entries by writing to a file. 
  7. * 
  8. * @class WC_Log_Handler_File 
  9. * @version 1.0.0 
  10. * @package WooCommerce/Classes/Log_Handlers 
  11. * @category Class 
  12. * @author WooThemes 
  13. */ 
  14. class WC_Log_Handler_File extends WC_Log_Handler { 
  15.  
  16. /** 
  17. * Stores open file handles. 
  18. * 
  19. * @var array 
  20. */ 
  21. protected $handles = array(); 
  22.  
  23. /** 
  24. * File size limit for log files in bytes. 
  25. * 
  26. * @var int 
  27. */ 
  28. protected $log_size_limit; 
  29.  
  30. /** 
  31. * Cache logs that could not be written. 
  32. * 
  33. * If a log is written too early in the request, pluggable functions may be unavailable. These 
  34. * logs will be cached and written on 'plugins_loaded' action. 
  35. * 
  36. * @var array 
  37. */ 
  38. protected $cached_logs = array(); 
  39.  
  40. /** 
  41. * Constructor for the logger. 
  42. * 
  43. * @param int $log_size_limit Optional. Size limit for log files. Default 5mb. 
  44. */ 
  45. public function __construct( $log_size_limit = null ) { 
  46.  
  47. if ( null === $log_size_limit ) { 
  48. $log_size_limit = 5 * 1024 * 1024; 
  49.  
  50. $this->log_size_limit = $log_size_limit; 
  51.  
  52. add_action( 'plugins_loaded', array( $this, 'write_cached_logs' ) ); 
  53.  
  54. /** 
  55. * Destructor. 
  56. * 
  57. * Cleans up open file handles. 
  58. */ 
  59. public function __destruct() { 
  60. foreach ( $this->handles as $handle ) { 
  61. if ( is_resource( $handle ) ) { 
  62. fclose( $handle ); 
  63.  
  64. /** 
  65. * Handle a log entry. 
  66. * 
  67. * @param int $timestamp Log timestamp. 
  68. * @param string $level emergency|alert|critical|error|warning|notice|info|debug 
  69. * @param string $message Log message. 
  70. * @param array $context { 
  71. * Additional information for log handlers. 
  72. * 
  73. * @type string $source Optional. Determines log file to write to. Default 'log'. 
  74. * @type bool $_legacy Optional. Default false. True to use outdated log format 
  75. * originally used in deprecated WC_Logger::add calls. 
  76. * } 
  77. * 
  78. * @return bool False if value was not handled and true if value was handled. 
  79. */ 
  80. public function handle( $timestamp, $level, $message, $context ) { 
  81.  
  82. if ( isset( $context['source'] ) && $context['source'] ) { 
  83. $handle = $context['source']; 
  84. } else { 
  85. $handle = 'log'; 
  86.  
  87. $entry = self::format_entry( $timestamp, $level, $message, $context ); 
  88.  
  89. return $this->add( $entry, $handle ); 
  90.  
  91. /** 
  92. * Builds a log entry text from timestamp, level and message. 
  93. * 
  94. * @param int $timestamp Log timestamp. 
  95. * @param string $level emergency|alert|critical|error|warning|notice|info|debug 
  96. * @param string $message Log message. 
  97. * @param array $context Additional information for log handlers. 
  98. * 
  99. * @return string Formatted log entry. 
  100. */ 
  101. protected static function format_entry( $timestamp, $level, $message, $context ) { 
  102.  
  103. if ( isset( $context['_legacy'] ) && true === $context['_legacy'] ) { 
  104. if ( isset( $context['source'] ) && $context['source'] ) { 
  105. $handle = $context['source']; 
  106. } else { 
  107. $handle = 'log'; 
  108. $message = apply_filters( 'woocommerce_logger_add_message', $message, $handle ); 
  109. $time = date_i18n( 'm-d-Y @ H:i:s' ); 
  110. $entry = "{$time} - {$message}"; 
  111. } else { 
  112. $entry = parent::format_entry( $timestamp, $level, $message, $context ); 
  113.  
  114. return $entry; 
  115.  
  116. /** 
  117. * Open log file for writing. 
  118. * 
  119. * @param string $handle Log handle. 
  120. * @param string $mode Optional. File mode. Default 'a'. 
  121. * @return bool Success. 
  122. */ 
  123. protected function open( $handle, $mode = 'a' ) { 
  124. if ( $this->is_open( $handle ) ) { 
  125. return true; 
  126.  
  127. $file = self::get_log_file_path( $handle ); 
  128.  
  129. if ( $file ) { 
  130. if ( ! file_exists( $file ) ) { 
  131. $temphandle = @fopen( $file, 'w+' ); 
  132. @fclose( $temphandle ); 
  133.  
  134. if ( defined( 'FS_CHMOD_FILE' ) ) { 
  135. @chmod( $file, FS_CHMOD_FILE ); 
  136.  
  137. if ( $resource = @fopen( $file, $mode ) ) { 
  138. $this->handles[ $handle ] = $resource; 
  139. return true; 
  140.  
  141. return false; 
  142.  
  143. /** 
  144. * Check if a handle is open. 
  145. * 
  146. * @param string $handle Log handle. 
  147. * @return bool True if $handle is open. 
  148. */ 
  149. protected function is_open( $handle ) { 
  150. return array_key_exists( $handle, $this->handles ) && is_resource( $this->handles[ $handle ] ); 
  151.  
  152. /** 
  153. * Close a handle. 
  154. * 
  155. * @param string $handle 
  156. * @return bool success 
  157. */ 
  158. protected function close( $handle ) { 
  159. $result = false; 
  160.  
  161. if ( $this->is_open( $handle ) ) { 
  162. $result = fclose( $this->handles[ $handle ] ); 
  163. unset( $this->handles[ $handle ] ); 
  164.  
  165. return $result; 
  166.  
  167. /** 
  168. * Add a log entry to chosen file. 
  169. * 
  170. * @param string $entry Log entry text 
  171. * @param string $handle Log entry handle 
  172. * 
  173. * @return bool True if write was successful. 
  174. */ 
  175. protected function add( $entry, $handle ) { 
  176. $result = false; 
  177.  
  178. if ( $this->should_rotate( $handle ) ) { 
  179. $this->log_rotate( $handle ); 
  180.  
  181. if ( $this->open( $handle ) && is_resource( $this->handles[ $handle ] ) ) { 
  182. $result = fwrite( $this->handles[ $handle ], $entry . PHP_EOL ); 
  183. } else { 
  184. $this->cache_log( $entry, $handle ); 
  185.  
  186. return false !== $result; 
  187.  
  188. /** 
  189. * Clear entries from chosen file. 
  190. * 
  191. * @param string $handle 
  192. * 
  193. * @return bool 
  194. */ 
  195. public function clear( $handle ) { 
  196. $result = false; 
  197.  
  198. // Close the file if it's already open. 
  199. $this->close( $handle ); 
  200.  
  201. /** 
  202. * $this->open( $handle, 'w' ) == Open the file for writing only. Place the file pointer at 
  203. * the beginning of the file, and truncate the file to zero length. 
  204. */ 
  205. if ( $this->open( $handle, 'w' ) && is_resource( $this->handles[ $handle ] ) ) { 
  206. $result = true; 
  207.  
  208. do_action( 'woocommerce_log_clear', $handle ); 
  209.  
  210. return $result; 
  211.  
  212. /** 
  213. * Remove/delete the chosen file. 
  214. * 
  215. * @param string $handle 
  216. * 
  217. * @return bool 
  218. */ 
  219. public function remove( $handle ) { 
  220. $removed = false; 
  221. $file = self::get_log_file_path( $handle ); 
  222.  
  223. if ( $file ) { 
  224. if ( is_file( $file ) && is_writable( $file ) ) { 
  225. $this->close( $handle ); // Close first to be certain no processes keep it alive after it is unlinked. 
  226. $removed = unlink( $file ); 
  227. do_action( 'woocommerce_log_remove', $handle, $removed ); 
  228.  
  229. return $removed; 
  230.  
  231. /** 
  232. * Check if log file should be rotated. 
  233. * 
  234. * Compares the size of the log file to determine whether it is over the size limit. 
  235. * 
  236. * @param string $handle Log handle 
  237. * @return bool True if if should be rotated. 
  238. */ 
  239. protected function should_rotate( $handle ) { 
  240. $file = self::get_log_file_path( $handle ); 
  241. if ( $file ) { 
  242. if ( $this->is_open( $handle ) ) { 
  243. $file_stat = fstat( $this->handles[ $handle ] ); 
  244. return $file_stat['size'] > $this->log_size_limit; 
  245. } elseif ( file_exists( $file ) ) { 
  246. return filesize( $file ) > $this->log_size_limit; 
  247. } else { 
  248. return false; 
  249. } else { 
  250. return false; 
  251.  
  252. /** 
  253. * Rotate log files. 
  254. * 
  255. * Logs are rotatated by prepending '.x' to the '.log' suffix. 
  256. * The current log plus 10 historical logs are maintained. 
  257. * For example: 
  258. * base.9.log -> [ REMOVED ] 
  259. * base.8.log -> base.9.log 
  260. * ... 
  261. * base.0.log -> base.1.log 
  262. * base.log -> base.0.log 
  263. * 
  264. * @param string $handle Log handle 
  265. */ 
  266. protected function log_rotate( $handle ) { 
  267. for ( $i = 8; $i >= 0; $i-- ) { 
  268. $this->increment_log_infix( $handle, $i ); 
  269. $this->increment_log_infix( $handle ); 
  270.  
  271. /** 
  272. * Increment a log file suffix. 
  273. * 
  274. * @param string $handle Log handle 
  275. * @param null|int $number Optional. Default null. Log suffix number to be incremented. 
  276. * @return bool True if increment was successful, otherwise false. 
  277. */ 
  278. protected function increment_log_infix( $handle, $number = null ) { 
  279. if ( null === $number ) { 
  280. $suffix = ''; 
  281. $next_suffix = '.0'; 
  282. } else { 
  283. $suffix = '.' . $number; 
  284. $next_suffix = '.' . ($number + 1); 
  285.  
  286. $rename_from = self::get_log_file_path( "{$handle}{$suffix}" ); 
  287. $rename_to = self::get_log_file_path( "{$handle}{$next_suffix}" ); 
  288.  
  289. if ( $this->is_open( $rename_from ) ) { 
  290. $this->close( $rename_from ); 
  291.  
  292. if ( is_writable( $rename_from ) ) { 
  293. return rename( $rename_from, $rename_to ); 
  294. } else { 
  295. return false; 
  296.  
  297.  
  298. /** 
  299. * Get a log file path. 
  300. * 
  301. * @param string $handle Log name. 
  302. * @return bool|string The log file path or false if path cannot be determined. 
  303. */ 
  304. public static function get_log_file_path( $handle ) { 
  305. if ( function_exists( 'wp_hash' ) ) { 
  306. return trailingslashit( WC_LOG_DIR ) . sanitize_file_name( $handle . '-' . wp_hash( $handle ) . '.log' ); 
  307. } else { 
  308. wc_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'woocommerce' ), '3.0' ); 
  309. return false; 
  310.  
  311. /** 
  312. * Cache log to write later. 
  313. * 
  314. * @param string $entry Log entry text 
  315. * @param string $handle Log entry handle 
  316. */ 
  317. protected function cache_log( $entry, $handle ) { 
  318. $this->cached_logs[] = array( 
  319. 'entry' => $entry,  
  320. 'handle' => $handle,  
  321. ); 
  322.  
  323. /** 
  324. * Write cached logs. 
  325. */ 
  326. public function write_cached_logs() { 
  327. foreach ( $this->cached_logs as $log ) { 
  328. $this->add( $log['entry'], $log['handle'] ); 
  329.  
.