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