PluginProbe ʕ •ᴥ•ʔ
Robin Image Optimizer – Unlimited Image Optimization, WebP & AVIF / trunk
Robin Image Optimizer – Unlimited Image Optimization, WebP & AVIF vtrunk
2.0.5 trunk 1.3.7 1.4.0 1.4.1 1.4.2 1.4.6 1.5.0 1.5.3 1.5.6 1.5.8 1.6.5 1.6.6 1.6.9 1.7.0 1.7.4 1.8.1 1.8.2 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4
robin-image-optimizer / libs / factory / logger / includes / class-logger.php
robin-image-optimizer / libs / factory / logger / includes Last commit date
class-log-export.php 5 months ago class-logger.php 5 months ago
class-logger.php
452 lines
1 <?php
2
3 namespace WBCR\Factory_Logger_359;
4
5 // Exit if accessed directly
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 /**
11 * Adds ability to log application message into .log file.
12 *
13 * It has 4 core levels:
14 * - info: generic log message
15 * - warning: log possible exceptions states or unusual
16 * - error: log error-related logs
17 * - debug: log stack traces, big outputs, etc.
18 *
19 * Each level has its constant. See LEVEL_* prefix.
20 *
21 * Additionally it is possible to configure flush interval and file name.
22 *
23 * Usage examples:
24 *
25 * ```php
26 * // Info message level
27 * $this->info('Some generic message, good to know');
28 *
29 * // Warning message level
30 * $this->warning('Something does not work or unusual');
31 *
32 * // Error message level
33 * $this->error('Something critical happened');
34 *
35 * // Debug message level
36 * $this->debug('Some message used for debug purposed. Could be stack trace.');
37 * ```
38 *
39 * @version 1.0
40 */
41 class Logger {
42
43 const LEVEL_INFO = 'info';
44 const LEVEL_WARNING = 'warning';
45 const LEVEL_ERROR = 'error';
46 const LEVEL_DEBUG = 'debug';
47
48 /**
49 * @var \Wbcr_Factory600_Plugin Plugin class.
50 */
51 public $plugin;
52
53 /**
54 * @var null|string Request hash.
55 */
56 public $hash = null;
57
58 /**
59 * @var null|string Directory where log file would be saved.
60 */
61 public $dir = null;
62
63 /**
64 * @var string File log name where logs would be flushed.
65 */
66 public $file = 'app.log';
67
68 /**
69 * @var int Flushing interval. When $_logs would reach this number of items they would be flushed to log file.
70 */
71 public $flush_interval = 1000;
72
73 /**
74 * @var int Rotate size in bytes. Default: 500 Kb.
75 */
76 public $rotate_size = 512000;
77
78 /**
79 * @var int Number of rotated files. When size of $rotate_size matches current file, current file would be rotated.
80 * For example, there are 10 files, current file became size of $rotate_size, third file would be deleted, two first
81 * shifted and empty one created.
82 */
83 public $rotate_limit = 10;
84
85 /**
86 * @var array List of logs to be dumped.
87 */
88 private $_logs = [];
89
90 /**
91 * Logger constructor.
92 *
93 * @param \Wbcr_Factory600_Plugin $plugin
94 * @param array $settings
95 */
96 public function __construct( $plugin, $settings = [] ) {
97 $this->plugin = $plugin;
98 $this->init( $settings );
99 }
100
101 /**
102 * Initiate object.
103 *
104 * @param array $settings
105 */
106 public function init( $settings ) {
107 $this->hash = substr( uniqid(), - 6, 6 );
108
109 if ( is_array( $settings ) && ! empty( $settings ) ) {
110 foreach ( $settings as $key => $value ) {
111 if ( isset( $this->$key ) ) {
112 $this->$key = $value;
113 }
114 }
115 }
116
117 add_action( 'shutdown', [ $this, 'shutdown_flush' ], 9999, 0 );
118 }
119
120 /**
121 * Get directory to save collected logs.
122 *
123 * In addition to that, it manages log rotation so that it does not become too big.
124 *
125 * @return string|false false on failure, string on success.
126 */
127 public function get_dir() {
128 $base_dir = $this->get_base_dir();
129 if ( $base_dir === null ) {
130 return false;
131 }
132
133 $root_file = $base_dir . $this->file;
134
135 // Check whether file exists and it exceeds rotate size, then should rotate it copy
136 if ( file_exists( $root_file ) && filesize( $root_file ) >= $this->rotate_size ) {
137 $name_split = explode( '.', $this->file );
138
139 if ( ! empty( $name_split ) && isset( $name_split[0] ) ) {
140 $name_split[0] = trim( $name_split[0] );
141
142 for ( $i = $this->rotate_limit; $i >= 0; $i-- ) {
143 $cur_name = $name_split[0] . $i;
144 $cur_path = $base_dir . $cur_name . '.log';
145
146 $next_path = $i !== 0 ? $base_dir . $name_split[0] . ( $i - 1 ) . '.log' : $root_file;
147
148 if ( file_exists( $next_path ) ) {
149 @copy( $next_path, $cur_path );
150 }
151 }
152 }
153
154 // Need to empty root file as it was supposed to be copied to next rotation :)
155 @file_put_contents( $root_file, '' );
156 }
157
158 return $root_file;
159 }
160
161 /**
162 * Get base directory, location of logs.
163 *
164 * @return null|string NULL in case of failure, string on success.
165 */
166 public function get_base_dir() {
167 $plugin_slug = $this->plugin->plugin_slug;
168
169 if ( empty( $this->dir ) ) {
170 $base_path = wp_normalize_path( trailingslashit( WP_CONTENT_DIR ) . "logs/{$plugin_slug}/" );
171 } else {
172 $base_path = wp_normalize_path( trailingslashit( $this->dir ) . "{$plugin_slug}/" );
173 }
174
175 /*
176 $folders = glob( $base_path . 'logs-*' );
177 if ( ! empty( $folders ) ) {
178 $exploded_path = explode( '/', trim( $folders[0] ) );
179 $selected_logs_folder = array_pop( $exploded_path );
180 } else {
181 if ( function_exists( 'wp_salt' ) ) {
182 $hash = md5( wp_salt() );
183 } else {
184 $hash = md5( AUTH_KEY );
185 }
186
187 $selected_logs_folder = 'logs-' . $hash;
188 }
189
190 $path = $base_path . $selected_logs_folder . '/';
191 */
192
193 $path = $base_path;
194 if ( ! file_exists( $path ) ) {
195 @mkdir( $path, 0755, true );
196 }
197
198 // Create .htaccess file to protect log files
199 $htaccess_path = $path . '.htaccess';
200
201 if ( ! file_exists( $htaccess_path ) ) {
202 $htaccess_content = 'deny from all';
203 @file_put_contents( $htaccess_path, $htaccess_content );
204 }
205
206 // Create index.htm file in case .htaccess is not support as a fallback
207 $index_path = $path . 'index.html';
208
209 if ( ! file_exists( $index_path ) ) {
210 @file_put_contents( $index_path, '' );
211 }
212
213 return $path;
214 }
215
216 /**
217 * Get all available log paths.
218 *
219 * @return array|bool
220 */
221 public function get_all() {
222 $base_dir = $this->get_base_dir();
223
224 if ( $base_dir === null ) {
225 return false;
226 }
227
228 $glob_path = $base_dir . '*.log';
229
230 return glob( $glob_path );
231 }
232
233 /**
234 * Get total log size in bytes.
235 *
236 * @return int
237 * @see size_format() for formatting.
238 */
239 public function get_total_size() {
240 $logs = $this->get_all();
241 $bytes = 0;
242
243 if ( empty( $logs ) ) {
244 return $bytes;
245 }
246
247 foreach ( $logs as $log ) {
248 $bytes += @filesize( $log );
249 }
250
251 return $bytes;
252 }
253
254 /**
255 * Empty all log files and deleted rotated ones.
256 *
257 * @return bool
258 */
259 public function clean_up() {
260
261 $base_dir = $this->get_base_dir();
262
263 if ( $base_dir === null ) {
264 return false;
265 }
266
267 $glob_path = $base_dir . '*.log';
268
269 $files = glob( $glob_path );
270
271 if ( $files === false ) {
272 return false;
273 }
274
275 if ( empty( $files ) ) {
276 return true;
277 }
278
279 $unlinked_count = 0;
280
281 foreach ( $files as $file ) {
282 if ( @unlink( $file ) ) {
283 ++$unlinked_count;
284 }
285 }
286
287 return count( $files ) === $unlinked_count;
288 }
289
290 /**
291 * Flush all messages.
292 *
293 * @return bool
294 */
295 public function flush() {
296
297 $messages = $this->_logs;
298
299 $this->_logs = [];
300
301 if ( empty( $messages ) ) {
302 return false;
303 }
304
305 $file_content = PHP_EOL . implode( PHP_EOL, $messages );
306 $is_put = @file_put_contents( $this->get_dir(), $file_content, FILE_APPEND );
307
308 return $is_put !== false;
309 }
310
311 /**
312 * Flush all messages.
313 */
314 public function shutdown_flush() {
315 $end_line = '-------------------------------';
316 if ( ! empty( $this->_logs ) ) {
317 $this->_logs[] = $end_line;
318 }
319
320 $this->flush();
321 }
322
323 /**
324 *
325 * @param $level
326 * @param $message
327 *
328 * @return string
329 */
330 public function get_format( $level, $message ) {
331
332 // Example: 17-03-2021 13:44:23 [site.com][info] Message
333 $template = '%s [%s][%s] %s';
334 $date = date_i18n( 'd-m-Y H:i:s' );
335
336 $ip = isset( $_SERVER['SERVER_NAME'] ) ? $_SERVER['SERVER_NAME'] : '';
337
338 return sprintf( $template, $date, $ip, $level, $message );
339 }
340
341 /**
342 * Get latest file content.
343 *
344 * @return bool|string
345 */
346 public function get_content() {
347 if ( ! file_exists( $this->get_dir() ) ) {
348 return null;
349 }
350
351 return htmlspecialchars( @file_get_contents( $this->get_dir() ) );
352 }
353
354 /**
355 * Get Export object.
356 *
357 * @return bool|Log_Export
358 */
359 public function get_export() {
360 return new Log_Export( $this, "{$this->plugin->plugin_slug}_log_export-{datetime}.zip" );
361 }
362
363 /**
364 * Add new log message.
365 *
366 * @param string $level Log level.
367 * @param string $message Message to log.
368 *
369 * @return bool
370 */
371 public function add( $level, $message ) {
372
373 $this->_logs[] = $this->get_format( $level, $message );
374
375 if ( count( $this->_logs ) >= $this->flush_interval ) {
376 $this->flush();
377 }
378
379 return true;
380 }
381
382 /**
383 * Add info level log.
384 *
385 * @param string $message Message to log.
386 */
387 public function info( $message ) {
388 $this->add( self::LEVEL_INFO, $message );
389 }
390
391 /**
392 * Add error level log.
393 *
394 * @param string $message Message to log.
395 */
396 public function error( $message ) {
397 $this->add( self::LEVEL_ERROR, $message );
398 }
399
400 /**
401 * Add debug level log.
402 *
403 * @param $message
404 */
405 public function debug( $message ) {
406 $this->add( self::LEVEL_DEBUG, $message );
407 }
408
409 /**
410 * Add warning level log.
411 *
412 * @param string $message Message to log.
413 */
414 public function warning( $message ) {
415 $this->add( self::LEVEL_WARNING, $message );
416 }
417
418 /**
419 * Writes information to log about memory.
420 *
421 * @since 1.3.6
422 */
423 public function memory_usage() {
424 $memory_avail = ini_get( 'memory_limit' );
425 $memory_used = number_format( memory_get_usage( true ) / ( 1024 * 1024 ), 2 );
426 $memory_peak = number_format( memory_get_peak_usage( true ) / ( 1024 * 1024 ), 2 );
427
428 $this->info( sprintf( 'Memory: %s (avail) / %sM (used) / %sM (peak)', $memory_avail, $memory_used, $memory_peak ) );
429 }
430
431 /**
432 * Prettify log content.
433 *
434 * Helps to convert log file content into easy-to-read HTML.
435 *
436 * Usage example:
437 *
438 * @return bool|mixed|string
439 */
440 public function prettify() {
441 $content = $this->get_content();
442
443 if ( ! empty( $content ) ) {
444 $replace = "<div class='wbcr-log-row wbcr_logger_level_$4'><strong>$1 $2</strong> [$3]<div class='wbcr_logger_level'>$4</div>$5</div>";
445
446 $content = str_replace( [ "\n", "\r<br>" ], [ '<br>', "\r\n" ], $content );
447 $content = preg_replace( '/^(\S+)\s*(\S+)\s*\[(.+)\]\s*\[(.+)\]\s*(.*)$/m', $replace, $content );
448 }
449 return $content;
450 }
451 }
452