PluginProbe ʕ •ᴥ•ʔ
LiteSpeed Cache / 7.8.1
LiteSpeed Cache v7.8.1
trunk 1.0.15 1.9.1.1 2.9.9.2 3.6.4 4.6 5.7.0.1 6.5.4 7.0.0.1 7.0.1 7.1 7.2 7.3 7.3.0.1 7.4 7.5 7.5.0.1 7.6 7.6.1 7.6.2 7.7 7.8 7.8.0.1 7.8.1
litespeed-cache / src / debug2.cls.php
litespeed-cache / src Last commit date
cdn 2 months ago data_structure 2 months ago activation.cls.php 2 months ago admin-display.cls.php 2 months ago admin-settings.cls.php 2 months ago admin.cls.php 2 months ago api.cls.php 2 months ago avatar.cls.php 2 months ago base.cls.php 2 months ago cdn.cls.php 2 months ago cloud-auth-callback.trait.php 2 months ago cloud-auth-ip.trait.php 2 months ago cloud-auth.trait.php 2 months ago cloud-misc.trait.php 2 months ago cloud-node.trait.php 2 months ago cloud-request.trait.php 2 months ago cloud.cls.php 2 months ago conf.cls.php 2 months ago control.cls.php 2 months ago core.cls.php 2 months ago crawler-map.cls.php 2 months ago crawler.cls.php 2 months ago css.cls.php 2 months ago data.cls.php 2 months ago data.upgrade.func.php 2 months ago db-optm.cls.php 2 months ago debug2.cls.php 2 months ago doc.cls.php 2 months ago error.cls.php 2 months ago esi.cls.php 2 months ago file.cls.php 2 months ago guest.cls.php 2 months ago gui.cls.php 2 months ago health.cls.php 2 months ago htaccess.cls.php 2 months ago img-optm-manage.trait.php 2 months ago img-optm-pull.trait.php 2 months ago img-optm-send.trait.php 2 months ago img-optm.cls.php 2 months ago import.cls.php 2 months ago import.preset.cls.php 2 months ago lang.cls.php 2 months ago localization.cls.php 2 months ago media.cls.php 2 months ago metabox.cls.php 2 months ago object-cache-wp.cls.php 2 months ago object-cache.cls.php 2 months ago object.lib.php 2 months ago optimize.cls.php 2 months ago optimizer.cls.php 2 months ago placeholder.cls.php 2 months ago purge.cls.php 2 months ago report.cls.php 2 months ago rest.cls.php 2 months ago root.cls.php 2 months ago router.cls.php 2 months ago str.cls.php 2 months ago tag.cls.php 2 months ago task.cls.php 2 months ago tool.cls.php 2 months ago ucss.cls.php 2 months ago utility.cls.php 2 months ago vary.cls.php 2 months ago vpi.cls.php 2 months ago
debug2.cls.php
717 lines
1 <?php
2 /**
3 * The plugin logging class.
4 *
5 * @package LiteSpeed
6 * @since 1.1.2
7 */
8
9 namespace LiteSpeed;
10
11 defined( 'WPINC' ) || exit();
12
13 /**
14 * Centralized debug logging utilities for LiteSpeed Cache.
15 */
16 class Debug2 extends Root {
17
18 /**
19 * Active log file path.
20 *
21 * @var string
22 */
23 private static $log_path;
24
25 /**
26 * Directory prefix for all log files.
27 *
28 * @var string
29 */
30 private static $log_path_prefix;
31
32 /**
33 * Request-specific log line prefix.
34 *
35 * @var string
36 */
37 private static $_prefix;
38
39 const TYPE_CLEAR_LOG = 'clear_log';
40 const TYPE_BETA_TEST = 'beta_test';
41 const TYPE_DOWNLOAD_LOG = 'download_log';
42
43 const BETA_TEST_URL = 'beta_test_url';
44
45 const BETA_TEST_URL_WP = 'https://downloads.wordpress.org/plugin/litespeed-cache.zip';
46
47 /**
48 * Constructor.
49 *
50 * NOTE: until LSCWP_LOG is defined, calls to WP filters are not logged to
51 * avoid a recursion loop inside log_filters().
52 *
53 * @since 1.1.2
54 * @access public
55 */
56 public function __construct() {
57 self::$log_path_prefix = LITESPEED_STATIC_DIR . '/debug/';
58 // Maybe move legacy log files
59 $this->_maybe_init_folder();
60
61 self::$log_path = $this->path( 'debug' );
62
63 $ua = isset( $_SERVER['HTTP_USER_AGENT'] )
64 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) )
65 : '';
66
67 if ( '' !== $ua && 0 === strpos( $ua, 'lscache_' ) ) {
68 self::$log_path = $this->path( 'crawler' );
69 }
70
71 ! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', get_current_blog_id() );
72
73 if ( $this->conf( Base::O_DEBUG_LEVEL ) ) {
74 ! defined( 'LSCWP_LOG_MORE' ) && define( 'LSCWP_LOG_MORE', true );
75 }
76
77 defined( 'LSCWP_DEBUG_EXC_STRINGS' ) || define( 'LSCWP_DEBUG_EXC_STRINGS', $this->conf( Base::O_DEBUG_EXC_STRINGS ) );
78 }
79
80 /**
81 * Disable all functionalities temporarily (toggle).
82 *
83 * @since 7.4
84 * @access public
85 *
86 * @param int $time How long (in seconds) to disable LSC functions.
87 */
88 public static function tmp_disable( $time = 86400 ) {
89 $conf = Conf::cls();
90 $disabled = self::cls()->conf( Base::DEBUG_TMP_DISABLE );
91
92 if ( 0 === $disabled ) {
93 $conf->update_confs( [ Base::DEBUG_TMP_DISABLE => time() + (int) $time ] );
94 self::debug2( 'LiteSpeed Cache temporary disabled.' );
95 return;
96 }
97
98 $conf->update_confs( [ Base::DEBUG_TMP_DISABLE => 0 ] );
99 self::debug2( 'LiteSpeed Cache reactivated.' );
100 }
101
102 /**
103 * Is the temporary disable active? If expired, re-enable.
104 *
105 * @since 7.4
106 * @access public
107 *
108 * @return bool
109 */
110 public static function is_tmp_disable() {
111 $disabled_time = self::cls()->conf( Base::DEBUG_TMP_DISABLE );
112
113 if ( 0 === $disabled_time ) {
114 return false;
115 }
116
117 if ( time() < (int) $disabled_time ) {
118 return true;
119 }
120
121 Conf::cls()->update_confs( [ Base::DEBUG_TMP_DISABLE => 0 ] );
122 return false;
123 }
124
125 /**
126 * Ensure log directory exists and move legacy logs into it.
127 *
128 * @since 6.5
129 * @access private
130 */
131 private function _maybe_init_folder() {
132 if ( file_exists( self::$log_path_prefix . 'index.php' ) ) {
133 return;
134 }
135
136 File::save( self::$log_path_prefix . 'index.php', '<?php // Silence is golden.', true );
137
138 $logs = [ 'debug', 'debug.purge', 'crawler' ];
139 foreach ( $logs as $log ) {
140 $old_path = LSCWP_CONTENT_DIR . '/' . $log . '.log';
141 $new_path = $this->path( $log );
142 if ( file_exists( $old_path ) && ! file_exists( $new_path ) ) {
143 // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- Moving legacy log files during migration
144 rename( $old_path, $new_path );
145 }
146 }
147 }
148
149 /**
150 * Get absolute path for a log type.
151 *
152 * @since 6.5
153 * @param string $type Log type (debug|purge|crawler).
154 * @return string
155 */
156 public function path( $type ) {
157 return self::$log_path_prefix . self::FilePath( $type );
158 }
159
160 /**
161 * Get fixed filename for a log type.
162 *
163 * @since 6.5
164 * @param string $type Log type (debug|debug.purge|crawler).
165 * @return string
166 */
167 public static function FilePath( $type ) {
168 if ( 'debug.purge' === $type ) {
169 $type = 'purge';
170 }
171 $key = defined( 'AUTH_KEY' ) ? AUTH_KEY : md5( __FILE__ );
172 $rand = substr( md5( substr( $key, -16 ) ), -16 );
173 return $type . $rand . '.log';
174 }
175
176 /**
177 * Write end-of-request markers and response timing.
178 *
179 * @since 4.7
180 * @access public
181 * @return void
182 */
183 public static function ended() {
184 $headers = headers_list();
185 foreach ( $headers as $key => $header ) {
186 if ( 0 === stripos( $header, 'Set-Cookie' ) ) {
187 unset( $headers[ $key ] );
188 }
189 }
190 self::debug( 'Response headers', $headers );
191
192 $elapsed_time = number_format( ( microtime( true ) - LSCWP_TS_0 ) * 1000, 2 );
193 self::debug( "End response\n--------------------------------------------------Duration: " . $elapsed_time . " ms------------------------------\n" );
194 }
195
196 /**
197 * Run beta test upgrade. Accepts a direct ZIP URL or attempts to derive one.
198 *
199 * @since 2.9.5
200 * @access public
201 *
202 * @param string|false $zip ZIP URL or false to read from request.
203 * @return void
204 */
205 public function beta_test( $zip = false ) {
206 if ( ! $zip ) {
207 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
208 if ( empty( $_REQUEST[ self::BETA_TEST_URL ] ) ) {
209 return;
210 }
211
212 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
213 $zip = sanitize_text_field( wp_unslash( $_REQUEST[ self::BETA_TEST_URL ] ) );
214 if ( self::BETA_TEST_URL_WP !== $zip ) {
215 if ( 'latest' === $zip ) {
216 $zip = self::BETA_TEST_URL_WP;
217 } else {
218 // Generate zip url
219 $zip = $this->_package_zip( $zip );
220 }
221 }
222 }
223
224 if ( ! $zip ) {
225 self::debug( '[Debug2] ❌ No ZIP file' );
226 return;
227 }
228
229 self::debug( '[Debug2] ZIP file ' . $zip );
230
231 $update_plugins = get_site_transient( 'update_plugins' );
232 if ( ! is_object( $update_plugins ) ) {
233 $update_plugins = new \stdClass();
234 }
235
236 $plugin_info = new \stdClass();
237 $plugin_info->new_version = Core::VER;
238 $plugin_info->slug = Core::PLUGIN_NAME;
239 $plugin_info->plugin = Core::PLUGIN_FILE;
240 $plugin_info->package = $zip;
241 $plugin_info->url = 'https://wordpress.org/plugins/litespeed-cache/';
242
243 $update_plugins->response[ Core::PLUGIN_FILE ] = $plugin_info;
244
245 set_site_transient( 'update_plugins', $update_plugins );
246
247 Activation::cls()->upgrade();
248 }
249
250 /**
251 * Resolve a GitHub commit-ish into a downloadable ZIP URL via QC API.
252 *
253 * @since 2.9.5
254 * @access private
255 *
256 * @param string $commit Commit hash/branch/tag.
257 * @return string|false
258 */
259 private function _package_zip( $commit ) {
260 $data = [
261 'commit' => $commit,
262 ];
263 $res = Cloud::get( Cloud::API_BETA_TEST, $data );
264
265 if ( empty( $res['zip'] ) ) {
266 return false;
267 }
268
269 return $res['zip'];
270 }
271
272 /**
273 * Write purge headers into a dedicated purge log.
274 *
275 * @since 2.7
276 * @access public
277 *
278 * @param string $purge_header The Purge header value.
279 * @return void
280 */
281 public static function log_purge( $purge_header ) {
282 if ( ! defined( 'LSCWP_LOG' ) && ! defined( 'LSCWP_LOG_BYPASS_NOTADMIN' ) ) {
283 return;
284 }
285
286 $purge_file = self::cls()->path( 'purge' );
287
288 self::cls()->_init_request( $purge_file );
289
290 $msg = $purge_header . self::_backtrace_info( 6 );
291
292 File::append( $purge_file, self::format_message( $msg ) );
293 }
294
295 /**
296 * Initialize logging for current request if enabled.
297 *
298 * @since 1.1.0
299 * @access public
300 * @return void
301 */
302 public function init() {
303 if ( defined( 'LSCWP_LOG' ) ) {
304 return;
305 }
306
307 $debug = $this->conf( Base::O_DEBUG );
308 if ( Base::VAL_ON2 === $debug ) {
309 if ( ! $this->cls( 'Router' )->is_admin_ip() ) {
310 defined( 'LSCWP_LOG_BYPASS_NOTADMIN' ) || define( 'LSCWP_LOG_BYPASS_NOTADMIN', true );
311 return;
312 }
313 }
314
315 /**
316 * Check if hit URI includes/excludes
317 * This is after LSCWP_LOG_BYPASS_NOTADMIN to make `log_purge()` still work
318 *
319 * @since 3.0
320 */
321 $list = $this->conf( Base::O_DEBUG_INC );
322 if ( $list ) {
323 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
324 $result = Utility::str_hit_array( $request_uri, $list );
325 if ( ! $result ) {
326 return;
327 }
328 }
329
330 $list = $this->conf( Base::O_DEBUG_EXC );
331 if ( $list ) {
332 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
333 $result = Utility::str_hit_array( $request_uri, $list );
334 if ( $result ) {
335 return;
336 }
337 }
338
339 if ( ! defined( 'LSCWP_LOG' ) ) {
340 $this->_init_request();
341 define( 'LSCWP_LOG', true );
342 }
343 }
344
345 /**
346 * Create the initial log record with request context.
347 *
348 * @since 1.0.12
349 * @access private
350 *
351 * @param string|null $log_file Optional specific log file path.
352 * @return void
353 */
354 private function _init_request( $log_file = null ) {
355 if ( ! $log_file ) {
356 $log_file = self::$log_path;
357 }
358
359 // Rotate if exceeding configured size (MiB).
360 $log_file_size = (int) $this->conf( Base::O_DEBUG_FILESIZE );
361 if ( file_exists( $log_file ) && filesize( $log_file ) > $log_file_size * 1000000 ) {
362 File::save( $log_file, '' );
363 }
364
365 // Add extra spacing if last write was > 2 seconds ago.
366 if ( file_exists( $log_file ) && ( time() - filemtime( $log_file ) ) > 2 ) {
367 File::append( $log_file, "\n\n\n\n" );
368 }
369
370 if ( 'cli' === PHP_SAPI ) {
371 return;
372 }
373
374 $servervars = array(
375 'Query String' => '',
376 'HTTP_ACCEPT' => '',
377 'HTTP_USER_AGENT' => '',
378 'HTTP_ACCEPT_ENCODING' => '',
379 'HTTP_COOKIE' => '',
380 'REQUEST_METHOD' => '',
381 'SERVER_PROTOCOL' => '',
382 'X-LSCACHE' => '',
383 'LSCACHE_VARY_COOKIE' => '',
384 'LSCACHE_VARY_VALUE' => '',
385 'ESI_CONTENT_TYPE' => '',
386 );
387 $server = array_merge($servervars, $_SERVER);
388 $params = array();
389
390 if ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ) {
391 $server['SERVER_PROTOCOL'] .= ' (HTTPS) ';
392 }
393
394 $param = sprintf('💓 ------%s %s %s', $server['REQUEST_METHOD'], $server['SERVER_PROTOCOL'], strtok($server['REQUEST_URI'], '?'));
395
396 $qs = !empty($server['QUERY_STRING']) ? $server['QUERY_STRING'] : '';
397 if ( $this->conf( Base::O_DEBUG_COLLAPSE_QS ) ) {
398 $qs = $this->_omit_long_message( $qs );
399 if ( $qs ) {
400 $param .= ' ? ' . $qs;
401 }
402 $params[] = $param;
403 } else {
404 $params[] = $param;
405 $params[] = 'Query String: ' . $qs;
406 }
407
408 if ( ! empty( $server['HTTP_REFERER'] ) ) {
409 $params[] = 'HTTP_REFERER: ' . $this->_omit_long_message( $server['HTTP_REFERER'] );
410 }
411
412 if ( defined( 'LSCWP_LOG_MORE' ) ) {
413 $params[] = 'User Agent: ' . $this->_omit_long_message( $server['HTTP_USER_AGENT'] );
414 $params[] = 'Accept: ' . $server['HTTP_ACCEPT'];
415 $params[] = 'Accept Encoding: ' . $server['HTTP_ACCEPT_ENCODING'];
416 }
417
418 if ( isset( $_COOKIE['_lscache_vary'] ) ) {
419 $params[] = 'Cookie _lscache_vary: ' . sanitize_text_field( wp_unslash( $_COOKIE['_lscache_vary'] ) );
420 }
421
422 if ( defined( 'LSCWP_LOG_MORE' ) ) {
423 $params[] = 'X-LSCACHE: ' . ( ! empty( $server['X-LSCACHE'] ) ? 'true' : 'false' );
424 }
425 if ( $server['LSCACHE_VARY_COOKIE'] ) {
426 $params[] = 'LSCACHE_VARY_COOKIE: ' . $server['LSCACHE_VARY_COOKIE'];
427 }
428 if ( $server['LSCACHE_VARY_VALUE'] ) {
429 $params[] = 'LSCACHE_VARY_VALUE: ' . $server['LSCACHE_VARY_VALUE'];
430 }
431 if ( $server['ESI_CONTENT_TYPE'] ) {
432 $params[] = 'ESI_CONTENT_TYPE: ' . $server['ESI_CONTENT_TYPE'];
433 }
434
435 $request = array_map( __CLASS__ . '::format_message', $params );
436
437 File::append( $log_file, $request );
438 }
439
440 /**
441 * Trim long message to keep logs compact.
442 *
443 * @since 6.3
444 * @param string $msg Message.
445 * @return string
446 */
447 private function _omit_long_message( $msg ) {
448 if ( strlen( $msg ) > 53 ) {
449 $msg = substr( $msg, 0, 53 ) . '...';
450 }
451 return $msg;
452 }
453
454 /**
455 * Format a single log line with timestamp and prefix.
456 *
457 * @since 1.0.12
458 * @access private
459 *
460 * @param string $msg Message to log.
461 * @return string Formatted line.
462 */
463 private static function format_message( $msg ) {
464 if ( ! defined( 'LSCWP_LOG_TAG' ) ) {
465 return $msg . "\n";
466 }
467
468 if ( ! isset( self::$_prefix ) ) {
469 // address/identity.
470 if ( 'cli' === PHP_SAPI ) {
471 $addr = '=CLI=';
472 if ( isset( $_SERVER['USER'] ) ) {
473 $addr .= sanitize_text_field( wp_unslash( $_SERVER['USER'] ) );
474 } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
475 $addr .= sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
476 }
477 } else {
478 $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
479 $port = isset( $_SERVER['REMOTE_PORT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_PORT'] ) ) : '';
480 $addr = "$ip:$port";
481 }
482
483 self::$_prefix = sprintf( ' [%s %s %s] ', $addr, LSCWP_LOG_TAG, Str::rrand( 3 ) );
484 }
485
486 list( $usec, $sec ) = explode( ' ', microtime() );
487
488 // Use gmdate to avoid tz-related warnings; apply offset if defined.
489 $ts = gmdate( 'm/d/y H:i:s', (int) $sec + ( defined( 'LITESPEED_TIME_OFFSET' ) ? (int) LITESPEED_TIME_OFFSET : 0 ) );
490
491 return $ts . substr( $usec, 1, 4 ) . self::$_prefix . $msg . "\n";
492 }
493
494 /**
495 * Log a debug message.
496 *
497 * @since 1.1.3
498 * @access public
499 *
500 * @param string $msg Message to write.
501 * @param int|array $backtrace_limit Depth for backtrace or payload to append.
502 * @return void
503 */
504 public static function debug( $msg, $backtrace_limit = false ) {
505 if ( ! defined( 'LSCWP_LOG' ) ) {
506 return;
507 }
508
509 if ( defined( 'LSCWP_DEBUG_EXC_STRINGS' ) && Utility::str_hit_array( $msg, LSCWP_DEBUG_EXC_STRINGS ) ) {
510 return;
511 }
512
513 if ( false !== $backtrace_limit ) {
514 if ( ! is_numeric( $backtrace_limit ) ) {
515 $backtrace_limit = self::trim_longtext( $backtrace_limit );
516 if ( is_array( $backtrace_limit ) && 1 === count( $backtrace_limit ) && ! empty( $backtrace_limit[0] ) ) {
517 $msg .= ' --- ' . $backtrace_limit[0];
518 } else {
519 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
520 $msg .= ' --- ' . var_export( $backtrace_limit, true );
521 }
522 self::push( $msg );
523 return;
524 }
525
526 self::push( $msg, (int) $backtrace_limit + 1 );
527 return;
528 }
529
530 self::push( $msg );
531 }
532
533 /**
534 * Trim strings inside arrays/object dumps to reasonable length.
535 *
536 * @since 3.3
537 * @param mixed $backtrace_limit Data to trim.
538 * @return mixed
539 */
540 public static function trim_longtext( $backtrace_limit ) {
541 if ( is_array( $backtrace_limit ) ) {
542 $backtrace_limit = array_map( __CLASS__ . '::trim_longtext', $backtrace_limit );
543 }
544 if ( is_string( $backtrace_limit ) && strlen( $backtrace_limit ) > 500 ) {
545 $backtrace_limit = substr( $backtrace_limit, 0, 1000 ) . '...';
546 }
547 return $backtrace_limit;
548 }
549
550 /**
551 * Log a verbose debug message (requires O_DEBUG_LEVEL).
552 *
553 * @since 1.2.0
554 * @access public
555 *
556 * @param string $msg Message.
557 * @param int|array $backtrace_limit Backtrace depth or payload to append.
558 * @return void
559 */
560 public static function debug2( $msg, $backtrace_limit = false ) {
561 if ( ! defined( 'LSCWP_LOG_MORE' ) ) {
562 return;
563 }
564 self::debug( $msg, $backtrace_limit );
565 }
566
567 /**
568 * Append a message to the active log file.
569 *
570 * @since 1.1.0
571 * @access private
572 *
573 * @param string $msg Message.
574 * @param int|bool $backtrace_limit Backtrace depth.
575 * @return void
576 */
577 private static function push( $msg, $backtrace_limit = false ) {
578 if ( defined( 'LSCWP_LOG_MORE' ) && false !== $backtrace_limit ) {
579 $msg .= self::_backtrace_info( (int) $backtrace_limit );
580 }
581
582 File::append( self::$log_path, self::format_message( $msg ) );
583 }
584
585 /**
586 * Create a compact backtrace string.
587 *
588 * @since 2.7
589 * @access private
590 *
591 * @param int $backtrace_limit Depth.
592 * @return string
593 */
594 private static function _backtrace_info( $backtrace_limit ) {
595 $msg = '';
596 $limit = (int) $backtrace_limit;
597
598 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
599 $trace = debug_backtrace( false, $limit + 3 );
600
601 for ( $i = 2; $i <= $limit + 2; $i++ ) {
602 // 0 => _backtrace_info(), 1 => push().
603 if ( empty( $trace[ $i ]['class'] ) ) {
604 if ( empty( $trace[ $i ]['file'] ) ) {
605 break;
606 }
607 $log = "\n" . $trace[ $i ]['file'];
608 } else {
609 if ( __CLASS__ === $trace[ $i ]['class'] ) {
610 continue;
611 }
612
613 $args = '';
614 if ( ! empty( $trace[ $i ]['args'] ) ) {
615 foreach ( $trace[ $i ]['args'] as $v ) {
616 if ( is_array( $v ) ) {
617 $v = 'ARRAY';
618 }
619 if ( is_string( $v ) || is_numeric( $v ) ) {
620 $args .= $v . ',';
621 }
622 }
623 $args = substr( $args, 0, strlen( $args ) > 100 ? 100 : -1 );
624 }
625
626 $log = str_replace( 'Core', 'LSC', $trace[ $i ]['class'] ) . $trace[ $i ]['type'] . $trace[ $i ]['function'] . '(' . $args . ')';
627 }
628
629 if ( ! empty( $trace[ $i - 1 ]['line'] ) ) {
630 $log .= '@' . $trace[ $i - 1 ]['line'];
631 }
632 $msg .= " => $log";
633 }
634
635 return $msg;
636 }
637
638 /**
639 * Clear all log files (debug|purge|crawler).
640 *
641 * @since 1.6.6
642 * @access private
643 * @return void
644 */
645 private function _clear_log() {
646 $logs = [ 'debug', 'purge', 'crawler' ];
647 foreach ( $logs as $log ) {
648 File::save( $this->path( $log ), '' );
649 }
650 }
651
652 /**
653 * Download a log file.
654 *
655 * @since 7.0
656 * @access private
657 * @return void
658 */
659 private function _download_log() {
660 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in Router::verify_type()
661 $log_type = isset( $_GET['log'] ) ? sanitize_text_field( wp_unslash( $_GET['log'] ) ) : '';
662
663 $valid_logs = [ 'debug', 'purge', 'crawler' ];
664 if ( ! in_array( $log_type, $valid_logs, true ) ) {
665 wp_die( esc_html__( 'Invalid log type.', 'litespeed-cache' ) );
666 }
667
668 $file = $this->path( $log_type );
669 if ( ! file_exists( $file ) ) {
670 wp_die( esc_html__( 'Log file not found.', 'litespeed-cache' ) );
671 }
672
673 $filename = 'litespeed-' . $log_type . '-' . gmdate( 'Y-m-d_His' ) . '.log';
674
675 header( 'Content-Type: text/plain; charset=utf-8' );
676 header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
677 header( 'Content-Length: ' . filesize( $file ) );
678 header( 'Cache-Control: no-cache, no-store, must-revalidate' );
679 header( 'Pragma: no-cache' );
680 header( 'Expires: 0' );
681
682 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Direct file output for download
683 readfile( $file );
684 exit;
685 }
686
687 /**
688 * Handle requests routed to this class.
689 *
690 * @since 1.6.6
691 * @access public
692 * @return void
693 */
694 public function handler() {
695 $type = Router::verify_type();
696
697 switch ( $type ) {
698 case self::TYPE_CLEAR_LOG:
699 $this->_clear_log();
700 break;
701
702 case self::TYPE_BETA_TEST:
703 $this->beta_test();
704 break;
705
706 case self::TYPE_DOWNLOAD_LOG:
707 $this->_download_log();
708 return; // _download_log() calls exit, but return here for clarity
709
710 default:
711 break;
712 }
713
714 Admin::redirect();
715 }
716 }
717