diff --git a/classes/QueryLog.php b/classes/QueryLog.php index 7b78b0d..4dd32c6 100644 --- a/classes/QueryLog.php +++ b/classes/QueryLog.php @@ -9,6 +9,8 @@ namespace DebugBarElasticPress; +use ElasticPress\Utils; + defined( 'ABSPATH' ) || exit; /** @@ -31,6 +33,7 @@ public function setup() { add_action( 'ep_remote_request', array( $this, 'log_query' ), 10, 2 ); add_action( 'admin_init', array( $this, 'action_admin_init' ) ); add_action( 'admin_init', array( $this, 'maybe_clear_log' ) ); + add_action( 'init', array( $this, 'maybe_disable' ) ); /** * Handle query storage as JSON strings. @@ -57,9 +60,33 @@ public function action_admin_init() { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && isset( $_POST['ep_enable_logging'] ) ) { check_admin_referer( 'ep-debug-options' ); - update_site_option( 'ep_enable_logging', (int) $_POST['ep_enable_logging'] ); + update_site_option( 'ep_enable_logging', $this->sanitize_enable_logging( $_POST['ep_enable_logging'] ) ); + update_site_option( 'ep_query_log_by_status', sanitize_text_field( $_POST['ep_query_log_by_status'] ) ); + if ( ! empty( $_POST['ep_query_log_by_context'] ) ) { + update_site_option( 'ep_query_log_by_context', array_map( 'sanitize_text_field', $_POST['ep_query_log_by_context'] ) ); + } else { + update_site_option( 'ep_query_log_by_context', [] ); + } } else { - register_setting( 'ep-debug', 'ep_enable_logging', 'intval' ); + register_setting( + 'ep-debug', + 'ep_enable_logging', + [ 'sanitize_callback' => [ $this, 'sanitize_enable_logging' ] ] + ); + register_setting( + 'ep-debug', + 'ep_query_log_by_status', + [ 'sanitize_callback' => 'sanitize_text_field' ] + ); + register_setting( + 'ep-debug', + 'ep_query_log_by_context', + [ + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) ? array_map( 'sanitize_text_field', $value ) : []; + }, + ] + ); } } @@ -73,11 +100,7 @@ public function maybe_clear_log() { return; } - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - delete_site_option( 'ep_query_log' ); - } else { - delete_option( 'ep_query_log' ); - } + Utils\delete_option( 'ep_query_log' ); wp_safe_redirect( remove_query_arg( 'ep_clear_query_log' ) ); exit(); @@ -147,67 +170,31 @@ public function is_bulk_index_error( $query ) { * @since 1.3 */ public function log_query( $query, $type ) { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $enabled = get_site_option( 'ep_enable_logging' ); - } else { - $enabled = get_option( 'ep_enable_logging' ); + if ( ! $this->is_enabled() ) { + return; } - if ( empty( $enabled ) ) { + if ( ! $this->should_log_by_context() ) { return; } - /** - * This filter allows you to map query types to callables. If the callable returns true, - * that query will be logged. - * - * @var array - * @since 1.3 - * @since 2.1.0 Added `bulk_index` - */ - $allowed_log_types = apply_filters( - 'ep_debug_bar_allowed_log_types', - array( - 'put_mapping' => array( $this, 'is_query_error' ), - 'delete_network_alias' => array( $this, 'is_query_error' ), - 'create_network_alias' => array( $this, 'is_query_error' ), - 'bulk_index' => array( $this, 'is_bulk_index_error' ), - 'bulk_index_posts' => array( $this, 'is_query_error' ), - 'delete_index' => array( $this, 'maybe_log_delete_index' ), - 'create_pipeline' => array( $this, 'is_query_error' ), - 'get_pipeline' => array( $this, 'is_query_error' ), - 'query' => array( $this, 'is_query_error' ), - ), - $query, - $type - ); - - if ( isset( $allowed_log_types[ $type ] ) ) { - $do_log = call_user_func( $allowed_log_types[ $type ], $query ); - - if ( ! $do_log ) { - return; - } - } else { + if ( ! $this->should_log_by_status( $query, $type ) ) { return; } - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $log = get_site_option( 'ep_query_log', array() ); - } else { - $log = get_option( 'ep_query_log', array() ); - } + $log = Utils\get_option( 'ep_query_log', [] ); $log[] = array( 'query' => $query, 'type' => $type, ); - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_query_log', $log ); - } else { - update_option( 'ep_query_log', $log ); + // Storing this log would exceed the limit + if ( mb_strlen( maybe_serialize( $log ) ) > $this->get_logging_storage_limit() ) { + return; } + + Utils\update_option( 'ep_query_log', $log ); } /** @@ -216,13 +203,10 @@ public function log_query( $query, $type ) { * @since 1.3 */ public function screen_options() { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $log = get_site_option( 'ep_query_log', array() ); - $enabled = get_site_option( 'ep_enable_logging' ); - } else { - $log = get_option( 'ep_query_log', array() ); - $enabled = get_option( 'ep_enable_logging' ); - } + $log = Utils\get_option( 'ep_query_log', array() ); + $enabled = Utils\get_option( 'ep_enable_logging' ); + $by_status = Utils\get_option( 'ep_query_log_by_status', 'failed' ); + $by_context = Utils\get_option( 'ep_query_log_by_context', [] ); if ( is_array( $log ) ) { $log = array_reverse( $log ); @@ -233,6 +217,8 @@ public function screen_options() { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $action = ''; } + + $is_time_limit = ! empty( $enabled ) && ! in_array( $enabled, [ '0', 0, '-1' ], true ); ?>
@@ -245,19 +231,79 @@ public function screen_options() { - + + + + + + + + +
+ +
- severe performance implications on your website. We generally recommend only enabling logging during dashboard indexing and disabling after.', 'debug-bar-elasticpress' ) ); ?> + + severe performance implications on your website.', 'debug-bar-elasticpress' ) ); + if ( $is_time_limit ) { + echo ' ' . wp_kses_post( + sprintf( + /* translators: date */ + __( 'Logging queries until %s.', 'debug-bar-elasticpress' ), + wp_date( 'Y-m-d H:i:s', $enabled ) + ) + ); + } + ?> + +
+ +
+
+
+
+
+

+ %s', 'debug-bar-elasticpress' ), + size_format( $this->get_logging_storage_limit() ) + ) + ); + ?> +

+

@@ -359,21 +405,33 @@ public function maybe_add_request_type( array $args, string $path, array $query_ * @return array */ public function maybe_add_request_context( array $args ) : array { - $args['ep_context'] = 'public'; + $args['ep_context'] = $this->get_current_context(); + + return $args; + } + + /** + * Get the current context + * + * @since 3.1.0 + * @return string + */ + protected function get_current_context() : string { + $context = 'public'; if ( is_admin() ) { - $args['ep_context'] = 'admin'; + $context = 'admin'; } if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - $args['ep_context'] = 'ajax'; + $context = 'ajax'; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { - $args['ep_context'] = 'rest'; + $context = 'rest'; } - return $args; + return $context; } /** @@ -428,4 +486,128 @@ protected function determine_request_query_type( array $request_args, string $pa return $type; } + + /** + * Whether logging is enabled or not + * + * @since 3.1.0 + * @return boolean + */ + protected function is_enabled() : bool { + $enabled = Utils\get_option( 'ep_enable_logging' ); + + return ! empty( $enabled ); + } + + /** + * Whether the current context should or not be logged + * + * @since 3.1.0 + * @return boolean + */ + protected function should_log_by_context() { + $by_context = Utils\get_option( 'ep_query_log_by_context', [] ); + + return empty( $by_context ) || in_array( $this->get_current_context(), $by_context, true ); + } + + /** + * Whether a query (and a type) should be logged or not + * + * @since 3.1.0 + * @param array $query Remote request arguments + * @param string $type Query type + * @return boolean + */ + protected function should_log_by_status( array $query, $type ) : bool { + $by_status = Utils\get_option( 'ep_query_log_by_status', 'failed' ); + + if ( 'all' === $by_status ) { + return true; + } + + /** + * This filter allows you to map query types to callables. If the callable returns true, + * that query will be logged. + * + * @var array + * @since 1.3 + * @since 2.1.0 Added `bulk_index` + */ + $allowed_log_types = apply_filters( + 'ep_debug_bar_allowed_log_types', + array( + 'put_mapping' => array( $this, 'is_query_error' ), + 'delete_network_alias' => array( $this, 'is_query_error' ), + 'create_network_alias' => array( $this, 'is_query_error' ), + 'bulk_index' => array( $this, 'is_bulk_index_error' ), + 'bulk_index_posts' => array( $this, 'is_query_error' ), + 'delete_index' => array( $this, 'maybe_log_delete_index' ), + 'create_pipeline' => array( $this, 'is_query_error' ), + 'get_pipeline' => array( $this, 'is_query_error' ), + 'query' => array( $this, 'is_query_error' ), + ), + $query, + $type + ); + + if ( ! isset( $allowed_log_types[ $type ] ) ) { + return false; + } + + return call_user_func( $allowed_log_types[ $type ], $query ); + } + + /** + * Return the size limit for stored logs + * + * @since 3.1.0 + * @return integer + */ + protected function get_logging_storage_limit() : int { + /** + * Filter the log size limit + * + * @since 3.1.0 + * @hook ep_debug_bar_log_size_limit + * @param {int} $number Log size limit + * @return {int} New limit + */ + return apply_filters( 'ep_debug_bar_log_size_limit', MB_IN_BYTES ); + } + + /** + * Conditionally disable logging based on period + * + * @since 3.1.0 + */ + public function maybe_disable() { + $enabled = Utils\get_option( 'ep_enable_logging' ); + + $is_time_limit = ! empty( $enabled ) && ! in_array( $enabled, [ '0', 0, '-1' ], true ); + if ( ! $is_time_limit || $enabled > wp_date( 'U' ) ) { + return; + } + + Utils\update_option( 'ep_enable_logging', 0 ); + } + + /** + * Sanitize the ep_enable_logging option, conditionally setting it as a time limit + * + * @since 3.1.0 + * @param mixed $value Value sent + * @return mixed + */ + public function sanitize_enable_logging( $value ) { + $value = sanitize_text_field( $_POST['ep_enable_logging'] ); + + if ( 'time_limit' === $value ) { + $value = wp_date( 'U', strtotime( '+5 minutes' ) ); + } else { + $value = (int) ! empty( $value ); + } + + return $value; + } }