PluginProbe
Debug Log Manager – Conveniently Monitor and Inspect Errors / 1.5.3
Debug Log Manager – Conveniently Monitor and Inspect Errors v1.5.3
2.5.2 2.5.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.3.0 1.3.1 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.8.0 1.8.2 1.8.3 1.8.4 All 49 releases
debug-log-manager / classes / class-debug-log.php

class-debug-log.php in Debug Log Manager – Conveniently Monitor and Inspect Errors 1.5.3, at classes/class-debug-log.php

506 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace DLM\Classes;
4
5 /**
6 * Class related to the debug log file and entries
7 *
8 * @since 1.0.0
9 */
10 class Debug_Log {
11
12 // The wp_config object
13 private $wp_config;
14
15 /**
16 * Class constructor
17 */
18 public function __construct() {
19
20 $this->wp_config = new WP_Config_Transformer; // already in the DLM\Classes namespace, so, no need to repeat
21
22 }
23
24 /**
25 * Get status of WP_DEBUG
26 *
27 * @since 1.0.0
28 */
29 public function get_status() {
30
31 $value = get_option( 'debug_log_manager' );
32 $status = $value['status'];
33 $date_time = wp_date( 'M j, Y - H:i:s', strtotime( $value['on'] ) );
34
35 return '<div id="debug-log-status" class="dlm-log-status"><strong>Error Logging</strong>: '. esc_html( ucfirst( $status ) ) .' on '. esc_html( $date_time ) .'<div id="dlm-log-toggle-hint"></div></div>';
36
37 }
38
39 /**
40 * Get log auto-refresh status
41 *
42 * @since 1.3.0
43 */
44 public function get_autorefresh_status() {
45
46 if ( false !== get_option( 'debug_log_manager_autorefresh' ) ) {
47
48 $autorefresh_status = get_option( 'debug_log_manager_autorefresh' );
49
50 } else {
51
52 $autorefresh_status = 'disabled';
53 update_option( 'debug_log_manager_autorefresh', $autorefresh_status, false );
54
55 }
56
57 return '<div id="debug-autorefresh-status" class="dlm-autorefresh-status"><strong>Auto-Refresh</strong>: ' . ucfirst( $autorefresh_status ) . '</div>';
58
59 }
60
61 /**
62 * Enable / disable WP_DEBUG
63 *
64 * @since 1.0.0
65 */
66 public function toggle_debugging() {
67
68 if ( isset( $_REQUEST ) ) {
69
70 $log_info = get_option( 'debug_log_manager' );
71 $dlm_debug_log_file_path = get_option( 'debug_log_manager_file_path' );
72
73 $date_time = wp_date( 'M j, Y - H:i:s' ); // Localized according to WP timezone settings
74 $date_time_for_option = date( 'M j, Y H:i:s' ); // in UTC
75
76 if ( 'disabled' == $log_info['status'] ) {
77
78 $option_value = array(
79 'status' => 'enabled',
80 'on' => $date_time_for_option,
81 );
82
83 update_option( 'debug_log_manager', $option_value, false );
84
85 // If WP_DEBUG_LOG is defined, copy content of existing debug.log file into the log file created by this plugin
86
87 if ( $this->wp_config->exists( 'constant', 'WP_DEBUG_LOG' ) ) {
88
89 $wp_debug_log_const = $this->wp_config->get_value( 'constant', 'WP_DEBUG_LOG' );
90
91 if ( in_array( $wp_debug_log_const, array( 'true', 'false' ), true ) ) {
92 $wp_debug_log_const = (bool) $wp_debug_log_const;
93 }
94
95 if ( is_bool( $wp_debug_log_const ) ) {
96 // WP_DEBUG_LOG is true or false. Log file is in default location of /wp-content/debug.log.
97
98 if ( is_file( WP_CONTENT_DIR . '/debug.log' ) ) {
99 // Copy existing debug log content to this plugin's debug log.
100 $default_debug_log_content = file_get_contents( WP_CONTENT_DIR . '/debug.log' );
101 file_put_contents( $dlm_debug_log_file_path, $default_debug_log_content );
102 unlink( realpath( WP_CONTENT_DIR . '/debug.log' ) ); // delete existing debug log
103 }
104
105 } elseif ( is_string( $wp_debug_log_const ) ) {
106 // WP_DEBUG_LOG is custom path to log file. Copy existing debug log content to this plugin's debug log.
107
108 if ( is_file( $wp_debug_log_const ) && ( $wp_debug_log_const != $dlm_debug_log_file_path ) ) {
109 $custom_debug_log_content = file_get_contents( $wp_debug_log_const );
110 file_put_contents( $dlm_debug_log_file_path, $custom_debug_log_content );
111 unlink( $wp_debug_log_const ); // delete existing debug log
112 }
113
114 }
115
116 $copy = true; // existing debug.log file's entries are copied to this plugin's debug.log file
117
118 } else {
119
120 $copy = false; // existing debug.log file's entries are NOT copied to this plugin's debug.log file
121
122 }
123
124 // Prepare entries from the debug log for the data table
125
126 $errors_master_list = json_decode( $this->get_processed_entries(), true );
127
128 $n = 1;
129 $entries = array();
130
131 foreach ( $errors_master_list as $error ) {
132
133 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
134 $occurrence_count = count( $error['occurrences'] );
135
136 $entry = array(
137 $n,
138 $error['type'],
139 $error['details'],
140 $localized_timestamp . '<br /><span class="dlm-faint">(' . $occurrence_count . ' occurrences logged)<span>',
141 );
142
143 $entries[] = $entry;
144
145 $n++;
146
147 }
148
149 // Define Debug constants in wp-config.php
150
151 $options = array(
152 'add' => true, // Add the config if missing.
153 'raw' => true, // Display value in raw format without quotes.
154 'normalize' => false, // Normalize config output using WP Coding Standards.
155 );
156
157 $this->wp_config->update( 'constant', 'WP_DEBUG', 'true', $options );
158
159 $options = array(
160 'add' => true, // Add the config if missing.
161 'raw' => false, // Display value in raw format without quotes.
162 'normalize' => false, // Normalize config output using WP Coding Standards.
163 );
164
165 $this->wp_config->update( 'constant', 'WP_DEBUG_LOG', get_option( 'debug_log_manager_file_path' ), $options );
166
167 $options = array(
168 'add' => true, // Add the config if missing.
169 'raw' => true, // Display value in raw format without quotes.
170 'normalize' => false, // Normalize config output using WP Coding Standards.
171 );
172
173 $this->wp_config->update( 'constant', 'WP_DEBUG_DISPLAY', 'false', $options );
174
175 // Get the debug.log file size
176
177 $log_file_path = get_option( 'debug_log_manager_file_path' );
178 $log_file_shortpath = str_replace( sanitize_text_field( $_SERVER['DOCUMENT_ROOT'] ), "", $log_file_path );
179 $file_size = size_format( (int) filesize( $log_file_path ) );
180
181 // Assemble data to return
182
183 $data = array(
184 'status' => 'enabled',
185 'copy' => $copy,
186 'message' => '<strong>Error Logging</strong>: Enabled on ' . esc_html( $date_time ),
187 'entries' => $entries,
188 'size' => $file_size,
189 );
190
191 echo json_encode( $data );
192
193 } elseif ( 'enabled' == $log_info['status'] ) {
194
195 $option_value = array(
196 'status' => 'disabled',
197 'on' => $date_time_for_option,
198 );
199
200 update_option( 'debug_log_manager', $option_value, false );
201
202 // Remove Debug constants in wp-config.php
203
204 $this->wp_config->remove( 'constant', 'WP_DEBUG' );
205 $this->wp_config->remove( 'constant', 'WP_DEBUG_LOG' );
206 $this->wp_config->remove( 'constant', 'WP_DEBUG_DISPLAY' );
207
208 // Assemble data to return
209
210 $data = array(
211 'status' => 'disabled',
212 'copy' => false,
213 'message' => '<strong>Error Logging</strong>: Disabled on ' . esc_html( $date_time ),
214 'entries' => '',
215 'size' => '',
216 );
217
218 echo json_encode( $data );
219
220 } else {}
221
222 }
223
224 }
225
226 /**
227 * Toggle auto-refresh of entries table
228 *
229 * @since 1.3.0
230 */
231 public function toggle_autorefresh() {
232
233 if ( isset( $_REQUEST ) ) {
234
235 $autorefresh_status = get_option( 'debug_log_manager_autorefresh' );
236
237 if ( $autorefresh_status == 'disabled' ) {
238
239 update_option( 'debug_log_manager_autorefresh', 'enabled', false );
240
241 $data = array(
242 'status' => 'enabled',
243 'message' => '<strong>Auto-Refresh</strong>: Enabled',
244 );
245
246 echo json_encode( $data );
247
248 } elseif ( $autorefresh_status == 'enabled' ) {
249
250 update_option( 'debug_log_manager_autorefresh', 'disabled', false );
251
252 $data = array(
253 'status' => 'disabled',
254 'message' => '<strong>Auto-Refresh</strong>: Disabled',
255 );
256
257 echo json_encode( $data );
258
259 } else {}
260
261 }
262
263 }
264
265 /**
266 * Get the processed debug log data
267 *
268 * @return string $errors_master_list The processed error log entries
269 * @since 1.2.0
270 */
271 public function get_processed_entries() {
272
273 $debug_log_file_path = get_option( 'debug_log_manager_file_path' );
274
275 // Read the erros log file, reverse the order of the entries, prune to the latest 5000 entries
276 $log = file_get_contents( $debug_log_file_path );
277 $log = str_replace( "[\\", "^\\", $log ); // certain error message contains the '[\' string, which will make the following split via explode() to split lines at places in the message it's not supposed to. So, we temporarily replace those with '^\'
278 $log = str_replace( "[internal function]", "^internal function^", $log );
279 $lines = explode("[", $log);
280
281 // Put back the missing '[' after explode operation
282 $prepended_lines = array();
283 foreach ( $lines as $line ) {
284 if ( !empty($line) ) {
285 $line = str_replace( "UTC]", "UTC]@@@", $line ); // add '@@@' as marker/separator after time stamp
286 $line = str_replace( "Stack trace:", "<hr />Stack trace:", $line ); // add line break for stack trace section
287 if ( strpos( $line, 'PHP Fatal' ) !== false ) {
288 $line = str_replace( "#", "<hr />#", $line ); // add line break on PHP Fatal error's stack trace lines
289 }
290 $line = str_replace( "Argument <hr />#", "Argument #", $line ); // remove hr on certain error message
291 $line = str_replace( "parameter <hr />#", "parameter #", $line ); // remove hr on certain error message
292 $line = str_replace( "the <hr />#", "the #", $line ); // remove hr on certain error message
293 $line = str_replace( "^\\", "[\\", $line ); // reverse the temporary replacement of '[\' with '^\'
294 $line = str_replace( "^internal function^", "[internal function]", $line );
295 $prepended_line = '[' . $line;
296 $prepended_lines[] = $prepended_line;
297 }
298 }
299
300 $lines_newest_first = array_reverse( $prepended_lines );
301 $latest_lines = array_slice( $lines_newest_first, 0, 50000 );
302
303 // Will hold error details types
304 $errors_master_list = array();
305
306 foreach( $latest_lines as $line ) {
307
308 $line = explode("@@@ ", $line); // split the line using the '@@@' marker/separator defined earlier
309
310 $timestamp = str_replace( [ "[", "]" ], "", $line[0] );
311 if ( array_key_exists('1', $line) ) {
312 $error = $line[1];
313 } else {
314 $error = 'No error message specified...';
315 }
316
317 if ( strpos( $error, 'PHP Fatal' ) !== false ) {
318 $error_type = 'PHP Fatal';
319 $error_details = str_replace( "PHP Fatal error: ", "", $error );
320 $error_details = str_replace( "PHP Fatal: ", "", $error_details );
321 } elseif ( strpos( $error, 'PHP Warning' ) !== false ) {
322 $error_type = 'PHP Warning';
323 $error_details = str_replace( "PHP Warning: ", "", $error );
324 } elseif ( strpos( $error, 'PHP Notice' ) !== false ) {
325 $error_type = 'PHP Notice';
326 $error_details = str_replace( "PHP Notice: ", "", $error );
327 } elseif ( strpos( $error, 'PHP Deprecated' ) !== false ) {
328 $error_type = 'PHP Deprecated';
329 $error_details = str_replace( "PHP Deprecated: ", "", $error );
330 } elseif ( strpos( $error, 'PHP Parse' ) !== false ) {
331 $error_type = 'PHP Parse';
332 $error_details = str_replace( "PHP Parse error: ", "", $error );
333 } elseif ( strpos( $error, 'WordPress database error' ) !== false ) {
334 $error_type = 'Database';
335 $error_details = str_replace( "WordPress database error ", "", $error );
336 } elseif ( strpos( $error, 'JavaScript Error' ) !== false ) {
337 $error_type = 'JavaScript';
338 $error_details = str_replace( "JavaScript Error: ", "", $error );
339 } else {
340 $error_type = 'Other';
341 $error_details = $error;
342 }
343
344 // https://www.php.net/manual/en/function.array-search.php#120784
345 if ( array_search( trim( $error_details ), array_column( $errors_master_list, 'details' ) ) === false ) {
346
347 $errors_master_list[] = array(
348 'type' => $error_type,
349 'details' => trim( $error_details ),
350 'occurrences' => array( $timestamp ),
351 );
352
353 } else {
354
355 $error_position = array_search( trim( $error_details ), array_column( $errors_master_list, 'details' ) ); // integer
356
357 array_push( $errors_master_list[$error_position]['occurrences'], $timestamp );
358
359 }
360
361 }
362
363 return json_encode( $errors_master_list );
364
365 }
366
367 /**
368 * Get the latest entries for auto-refresh feature
369 *
370 * @since 1.0.0
371 */
372 public function get_latest_entries() {
373
374 $errors_master_list = json_decode( $this->get_processed_entries(), true );
375
376 $n = 1;
377 $entries = array();
378
379 foreach ( $errors_master_list as $error ) {
380
381 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
382 $occurrence_count = count( $error['occurrences'] );
383
384 $entry = array(
385 $n,
386 $error['type'],
387 $error['details'],
388 $localized_timestamp . '<br /><span class="dlm-faint">(' . $occurrence_count . ' occurrences logged)<span>',
389 );
390
391 $entries[] = $entry;
392
393 $n++;
394
395 }
396
397 $data = array(
398 'entries' => $entries,
399 );
400
401 echo json_encode( $data );
402
403 }
404
405 /**
406 * Get debug log in data table format
407 *
408 * @since 1.0.0
409 */
410 public function get_entries_datatable() {
411
412 ?>
413 <div>
414 <select id="errorTypeFilter" class="dlm-error-type-filter">
415 <option value="">All Error Types</option>
416 <option value="PHP Fatal">PHP Fatal</option>
417 <option value="PHP Warning">PHP Warning</option>
418 <option value="PHP Notice">PHP Notice</option>
419 <option value="PHP Deprecated">PHP Deprecated</option>
420 <option value="PHP Parse">PHP Parse</option>
421 <option value="Database">Database</option>
422 <option value="JavaScript">JavaScript</option>
423 <option value="Other">Other</option>
424 </select>
425 </div>
426 <table id="debug-log" class="wp-list-table widefat striped">
427 <thead>
428 <tr>
429 <th class="debug-log-number">#</th>
430 <th class="debug-log-error-type">Error Type</th>
431 <th class="debug-log-error-details">Details</th>
432 <th class="debug-log-timestamp">Last Occurrence</th>
433 </tr>
434 </thead>
435 <tbody>
436 <?php
437
438 $errors_master_list = json_decode( $this->get_processed_entries(), true );
439
440 $n = 1;
441
442 foreach ( $errors_master_list as $error ) {
443
444 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
445 $occurrence_count = count( $error['occurrences'] );
446 ?>
447
448 <tr>
449 <td><?php echo esc_html( $n ); ?></td>
450 <td><?php echo esc_html( $error['type'] ); ?></td>
451 <td><?php echo wp_kses( $error['details'], 'post' ); ?></td>
452 <td><?php echo esc_html( $localized_timestamp ); ?><br /><span class="dlm-faint">(<?php echo esc_html( $occurrence_count ); ?> occurrences logged)<span></td>
453 </tr>
454
455 <?php
456 $n++;
457
458 }
459
460 ?>
461 </tbody>
462 </table>
463 <?php
464
465 }
466
467 /**
468 * Clear log file
469 *
470 * @since 1.0.0
471 */
472 public function clear_log() {
473
474 $debug_log_file_path = get_option( 'debug_log_manager_file_path' );
475
476 file_put_contents( $debug_log_file_path, '' );
477
478 echo true;
479
480 }
481
482 /**
483 * Log javascript errors
484 *
485 * @since 1.4.0
486 */
487 public function log_js_errors() {
488
489 // Since we are using XHR for the js error logging, JSON data comes in via php://input
490 $request = json_decode(urldecode(file_get_contents('php://input')), true); // an array
491
492 // Verify error content and nonce and then log the JS error
493 // Source: https://plugins.svn.wordpress.org/lh-javascript-error-log/trunk/lh-javascript-error-log.php
494 if ( isset( $request['message'] ) && isset( $request['script'] ) && isset( $request['lineNo'] ) && isset( $request['columnNo'] ) && ! empty( $request['nonce'] ) && wp_verify_nonce( $request['nonce'], DLM_SLUG ) ) {
495
496 error_log( 'JavaScript Error: ' . $request['message'] . ' in ' . $request['script'] . ' on line ' . $request['lineNo'] . ' column ' . $request['columnNo'] . ' at ' . get_site_url() . $request['pageUrl'] );
497
498 } else {
499
500 wp_die();
501
502 }
503
504 }
505
506 }