diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f20d146..c2fb9ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/en/1.0.0/). +## 2.4.3 - 2024-07-02 + +- Fix a serialization of closure error when using an exception in a log's context. + ## 2.4.2 - 2024-06-28 - Fix an incorrectly named variable in the `Post_Handler` class. diff --git a/inc/handler/class-post-handler.php b/inc/handler/class-post-handler.php index ede30ed7..091d3a45 100644 --- a/inc/handler/class-post-handler.php +++ b/inc/handler/class-post-handler.php @@ -13,6 +13,7 @@ use Monolog\Logger; use Spatie\Backtrace\Backtrace; use Spatie\Backtrace\Frame as SpatieFrame; +use Throwable; /** * Post Log Handler @@ -197,6 +198,27 @@ public function process_queue() { ); if ( ! empty( $log_post_id ) ) { + // Sanitize the context to prevent an accidental serialize error. When serializing + // an exception, PHP will throw a Serialization of 'Closure' is not allowed error. + if ( ! empty( $log['context'] ) && is_array( $log['context'] ) ) { + $log['context'] = array_map( + function ( mixed $value ) { + if ( $value instanceof Throwable ) { + if ( method_exists( $value, '__toString' ) ) { + return $value->__toString(); + } elseif ( method_exists( $value, 'getTraceAsString' ) ) { + return $value->getTraceAsString(); + } + + return $value->getMessage(); + } + + return $value; + }, + $log['context'], + ); + } + \update_post_meta( $log_post_id, '_logger_record', $log ); $this->assign_terms( $log_post_id, $level, static::TAXONOMY_LOG_LEVEL ); @@ -221,6 +243,8 @@ public function process_queue() { * Process the queue when shutting down. * * Ensure that all logs are properly saved when shutting down (if any are left). + * + * @throws Throwable If an error occurs while processing the queue during testing. */ public function process_queue_shutdown() { if ( empty( $this->queue ) ) { @@ -232,7 +256,21 @@ public function process_queue_shutdown() { \switch_to_blog( $this->original_site_id ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog } - $this->process_queue(); + try { + $this->process_queue(); + } catch ( Throwable $e ) { + // Throw the exception if testing. + if ( defined( 'MANTLE_IS_TESTING' ) && MANTLE_IS_TESTING ) { + throw $e; + } + + // In the event of any error, log to the actual error log if an exception + // is thrown to prevent an exception from bubbling up. + error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + "AI Logger: Error processing queue during shutdown: {$e->getMessage()}", + E_ERROR, + ); + } if ( $switching ) { \restore_current_blog(); diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 4ba45224..61af1d45 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -2,6 +2,7 @@ namespace AI_Logger\Tests; use AI_Logger\AI_Logger; +use AI_Logger\Handler\Post_Handler; use Mantle\Testkit\Test_Case; use Monolog\Handler\NullHandler; use Monolog\Handler\TestHandler; @@ -130,6 +131,30 @@ public function test_with_context() { ) ); } + + public function test_serialize_exception() { + $logger = ai_logger()->with_handlers( + [ + $handler = new Post_Handler(), + ] + ); + + $logger->error( + 'This is a test', + [ + 'error' => new \InvalidArgumentException( 'Test Exception', 500 ), + ] + ); + + $handler->process_queue_shutdown(); + + $this->assertPostExists( + [ + 'post_title' => 'This is a test', + 'post_type' => 'ai_log', + ] + ); + } }