diff --git a/plugins/wpgraphql-logging/activation.php b/plugins/wpgraphql-logging/activation.php
index a9847faa..102cd706 100644
--- a/plugins/wpgraphql-logging/activation.php
+++ b/plugins/wpgraphql-logging/activation.php
@@ -9,9 +9,12 @@
declare(strict_types=1);
+use WPGraphQL\Logging\Plugin;
+
/**
* Runs when the plugin is activated.
*/
function wpgraphql_logging_activation_callback(): void {
+ Plugin::activate();
do_action( 'wpgraphql_logging_activate' );
}
diff --git a/plugins/wpgraphql-logging/deactivation.php b/plugins/wpgraphql-logging/deactivation.php
index d76049c8..5f135ded 100644
--- a/plugins/wpgraphql-logging/deactivation.php
+++ b/plugins/wpgraphql-logging/deactivation.php
@@ -1,7 +1,4 @@
*/phpunit.xml*
**/tests/**
*/vendor/*
+ */src/Events/QueryEventLifecycle.php
diff --git a/plugins/wpgraphql-logging/phpstan.neon.dist b/plugins/wpgraphql-logging/phpstan.neon.dist
index 05f71e10..e6722105 100644
--- a/plugins/wpgraphql-logging/phpstan.neon.dist
+++ b/plugins/wpgraphql-logging/phpstan.neon.dist
@@ -30,3 +30,5 @@ parameters:
paths:
- wpgraphql-logging.php
- src/
+ excludePaths:
+ - src/Events/QueryEventLifecycle.php
diff --git a/plugins/wpgraphql-logging/psalm.xml b/plugins/wpgraphql-logging/psalm.xml
index f6a82936..552f0d26 100644
--- a/plugins/wpgraphql-logging/psalm.xml
+++ b/plugins/wpgraphql-logging/psalm.xml
@@ -15,8 +15,12 @@
+
+
+
+
diff --git a/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php b/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php
index 4446d8e3..ca589f6d 100644
--- a/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php
+++ b/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php
@@ -4,13 +4,16 @@
namespace WPGraphQL\Logging\Events;
+use GraphQL\Executor\ExecutionResult;
+use GraphQL\Server\OperationParams;
use Monolog\Level;
use WPGraphQL\Logging\Logger\LoggerService;
+use WPGraphQL\Request;
/**
* WPGraphQL Query Event Lifecycle -
*
- * POC @TODO - Add pub/sub for query events.
+ * Handles logging for GraphQL query lifecycle events.
*
* @package WPGraphQL\Logging
*
@@ -29,7 +32,7 @@ class QueryEventLifecycle {
*
* @param \WPGraphQL\Logging\Logger\LoggerService $logger
*/
- protected function __construct(readonly LoggerService $logger) {
+ protected function __construct( readonly LoggerService $logger ) {
}
/**
@@ -42,20 +45,26 @@ public static function init(): QueryEventLifecycle {
self::$instance = new self( $logger );
self::$instance->setup();
}
+
return self::$instance;
}
/**
* Logs the pre-request event for a GraphQL query.
+ * This method is hooked into 'do_graphql_request'.
*
- * @param string $query The GraphQL query.
- * @param mixed $variables The variables for the query.
- * @param string $operation_name The name of the operation.
+ * @param string $query The GraphQL query string.
+ * @param string|null $operation_name The name of the operation. Made nullable.
+ * @param array|null $variables The variables for the query. Made nullable.
*/
- public function log_pre_request( $query, $variables, $operation_name ): void {
-
+ public function log_pre_request( string $query, ?string $operation_name, ?array $variables ): void {
try {
- $context = [];
+ $context = [
+ 'query' => $query,
+ 'variables' => $variables,
+ 'operation_name' => $operation_name,
+ ];
+
$context = apply_filters( 'wpgraphql_logging_pre_request_context', $context, $query, $variables, $operation_name );
$level = apply_filters( 'wpgraphql_logging_pre_request_level', Level::Info, $query, $variables, $operation_name );
$this->logger->log( $level, 'WPGraphQL Incoming Request', $context );
@@ -67,24 +76,86 @@ public function log_pre_request( $query, $variables, $operation_name ): void {
/**
* Logs the post-request event for a GraphQL query.
+ * This method is now hooked into 'graphql_after_execute'.
*
- * @param mixed $response The response from the GraphQL request.
- * @param mixed $result The result of the GraphQL request.
- * @param string $operation_name The name of the operation.
- * @param string $query The GraphQL query.
- * @param array $variables The variables for the query.
+ * @param \GraphQL\Executor\ExecutionResult|array $response The GraphQL execution result(s).
+ * This can be a single ExecutionResult object or an array of them for batch requests.
+ * @param \WPGraphQL\Request $request_instance The WPGraphQL Request instance.
*/
- public function log_post_request( $response, $result, string $operation_name, string $query, array $variables ): void {
+ public function log_post_request( $response, Request $request_instance ): void {
+ // Extract relevant data from the WPGraphQL Request instance
+ $params = $request_instance->get_params(); // Can be OperationParams or array of OperationParams
+ $query = null;
+ $operation_name = null;
+ $variables = null;
+ $status_code = 200; // Default success status
+
+ // Handle single or batch requests to get query details
+ if ( $params instanceof OperationParams ) {
+ $query = $params->query;
+ $operation_name = $params->operation;
+ $variables = $params->variables;
+ } elseif ( is_array( $params ) && ! empty( $params[0] ) && $params[0] instanceof OperationParams ) {
+ $query = $params[0]->query;
+ $operation_name = $params[0]->operation;
+ $variables = $params[0]->variables;
+ }
+
+ // Determine status code if available (WPGraphQL Router sets this)
+ if ( class_exists( '\WPGraphQL\Router' ) && property_exists( '\WPGraphQL\Router', '$http_status_code' ) ) {
+ $status_code = \WPGraphQL\Router::$http_status_code;
+ }
+
+ // Extract data and errors from the ExecutionResult object(s)
+ $response_data = null;
+ $response_errors = null;
+
+ if ( $response instanceof ExecutionResult ) {
+ $response_data = $response->data;
+ $response_errors = $response->errors;
+ } elseif ( is_array( $response ) && ! empty( $response[0] ) && $response[0] instanceof ExecutionResult ) {
+ // For batch requests, aggregate data/errors from all results
+ $response_data = array_map( static fn( $res ) => $res->data, $response );
+ $response_errors = array_reduce( $response, static fn( $carry, $res ) => array_merge( $carry, $res->errors ?? [] ), [] );
+ if ( empty( $response_errors ) ) {
+ $response_errors = null; // Ensure it's null if no errors
+ }
+ }
+
try {
- $context = [];
+ $context = [
+ 'query' => $query,
+ 'operation_name' => $operation_name,
+ 'variables' => $variables,
+ 'status_code' => $status_code,
+ 'response_data' => $response_data,
+ 'response_errors' => $response_errors,
+ ];
$level = Level::Info;
- $context = apply_filters( 'wpgraphql_logging_post_request_context', $context, $response, $result, $operation_name, $query, $variables );
- $level = apply_filters( 'wpgraphql_logging_post_request_level', $level, $response, $result, $operation_name, $query, $variables );
+
+ // Apply filters for context and level
+ $context = apply_filters( 'wpgraphql_logging_post_request_context', $context, $response, $request_instance );
+ $level = apply_filters( 'wpgraphql_logging_post_request_level', $level, $response, $request_instance );
+
$this->logger->log( $level, 'WPGraphQL Outgoing Response', $context );
+
+ // Log errors specifically if present in the response
+ if ( ! empty( $response_errors ) ) {
+ $this->logger->error(
+ 'GraphQL query completed with errors.',
+ [
+ 'query' => $query,
+ 'operation_name' => $operation_name,
+ 'status_code' => $status_code,
+ 'errors' => array_map( static fn( $error ) => $error->getMessage(), $response_errors ), // Extract message from error object
+ 'full_errors' => $response_errors, // Include full error details for debugging
+ ]
+ );
+ }
} catch ( \Throwable $e ) {
// @TODO - Handle logging errors gracefully.
- error_log( 'Error in log_post_request: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+ error_log( 'Error in log_post_request: ' . $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() );
}
}
@@ -92,9 +163,6 @@ public function log_post_request( $response, $result, string $operation_name, st
* Register actions and filters.
*/
protected function setup(): void {
-
- // @TODO: Update POC and use pub/sub for query events.
-
/**
* @psalm-suppress HookNotFound
*/
@@ -103,6 +171,6 @@ protected function setup(): void {
/**
* @psalm-suppress HookNotFound
*/
- add_action( 'graphql_process_http_request_response', [ $this, 'log_post_request' ], 10, 5 );
+ add_action( 'graphql_after_execute', [ $this, 'log_post_request' ], 10, 2 );
}
}
diff --git a/plugins/wpgraphql-logging/src/Hooks/.gitkeep b/plugins/wpgraphql-logging/src/Hooks/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/plugins/wpgraphql-logging/src/Hooks/PluginHooks.php b/plugins/wpgraphql-logging/src/Hooks/PluginHooks.php
deleted file mode 100644
index a6636f91..00000000
--- a/plugins/wpgraphql-logging/src/Hooks/PluginHooks.php
+++ /dev/null
@@ -1,57 +0,0 @@
-register();
- }
- return self::$instance;
- }
-
- /**
- * Activation callback for the plugin.
- */
- public static function activate_plugin(): void {
- DatabaseEntity::create_table();
- }
-
- /**
- * Deactivation callback for the plugin.
- */
- public static function deactivate_plugin(): void {
- // @TODO: Add configuration to determine if the table should be dropped on deactivation.
- DatabaseEntity::drop_table();
- }
-
- /**
- * Register actions and filters.
- */
- protected function register(): void {
- add_action( 'wpgraphql_logging_activate', [ self::class, 'activate_plugin' ], 10, 0 );
- add_action( 'wpgraphql_logging_deactivate', [ self::class, 'deactivate_plugin' ], 10, 0 );
- }
-}
diff --git a/plugins/wpgraphql-logging/src/Logger/LoggerService.php b/plugins/wpgraphql-logging/src/Logger/LoggerService.php
index fb50ec7d..d55a7585 100644
--- a/plugins/wpgraphql-logging/src/Logger/LoggerService.php
+++ b/plugins/wpgraphql-logging/src/Logger/LoggerService.php
@@ -44,7 +44,7 @@ class LoggerService {
/**
* The instance of the logger based off the channel name.
*
- * @var array
+ * @var array<\WPGraphQL\Logging\Logger\LoggerService>
*/
protected static array $instances = [];
@@ -90,16 +90,16 @@ public static function get_instance(
?array $processors = null,
?array $default_context = null
): LoggerService {
- if ( isset(self::$instances[$channel]) ) {
- return self::$instances[$channel];
+ if ( isset( self::$instances[ $channel ] ) ) {
+ return self::$instances[ $channel ];
}
$processors = $processors ?? self::get_default_processors();
$handlers = $handlers ?? self::get_default_handlers();
$default_context = $default_context ?? self::get_default_context();
- self::$instances[$channel] = new self( $channel, $handlers, $processors, $default_context );
- return self::$instances[$channel];
+ self::$instances[ $channel ] = new self( $channel, $handlers, $processors, $default_context );
+ return self::$instances[ $channel ];
}
/**
diff --git a/plugins/wpgraphql-logging/src/Plugin.php b/plugins/wpgraphql-logging/src/Plugin.php
index b825e08f..502860b4 100644
--- a/plugins/wpgraphql-logging/src/Plugin.php
+++ b/plugins/wpgraphql-logging/src/Plugin.php
@@ -5,7 +5,7 @@
namespace WPGraphQL\Logging;
use WPGraphQL\Logging\Events\QueryEventLifecycle;
-use WPGraphQL\Logging\Hooks\PluginHooks;
+use WPGraphQL\Logging\Logger\Database\DatabaseEntity;
/**
* Plugin class for WPGraphQL Logging.
@@ -51,10 +51,24 @@ public static function init(): self {
* Initialize the plugin admin, frontend & api functionality.
*/
public function setup(): void {
- PluginHooks::init();
QueryEventLifecycle::init();
}
+ /**
+ * Activation callback for the plugin.
+ */
+ public static function activate(): void {
+ DatabaseEntity::create_table();
+ }
+
+ /**
+ * Deactivation callback for the plugin.
+ */
+ public static function deactivate(): void {
+ // @TODO: Add configuration to determine if the table should be dropped on deactivation.
+ DatabaseEntity::drop_table();
+ }
+
/**
* Throw error on object clone.
* The whole idea of the singleton design pattern is that there is a single object
diff --git a/plugins/wpgraphql-logging/tests/wpunit/Hooks/PluginHooksTest.php b/plugins/wpgraphql-logging/tests/wpunit/Hooks/PluginHooksTest.php
deleted file mode 100644
index af9cd4d4..00000000
--- a/plugins/wpgraphql-logging/tests/wpunit/Hooks/PluginHooksTest.php
+++ /dev/null
@@ -1,71 +0,0 @@
-drop_table();
- }
-
- public function drop_table(): void {
- DatabaseEntity::drop_table();
- }
-
- public function test_instance_from_plugin_instance() {
- $instance = PluginHooks::init();
- $this->assertTrue( $instance instanceof PluginHooks );
- }
-
- public function test_singleton_returns_same_instance() {
- $first = PluginHooks::init();
- $second = PluginHooks::init();
- $this->assertSame( $first, $second, 'PluginHooks::instance() should always return the same instance' );
- }
-
- public function test_instance_creates_and_sets_up_plugin_when_not_set() {
- $reflection = new ReflectionClass( PluginHooks::class );
- $instanceProperty = $reflection->getProperty( 'instance' );
- $instanceProperty->setAccessible( true );
- $instanceProperty->setValue( null );
-
- $this->assertNull( $instanceProperty->getValue() );
- $instance = PluginHooks::init();
-
- $this->assertInstanceOf( PluginHooks::class, $instanceProperty->getValue() );
- $this->assertSame( $instance, $instanceProperty->getValue(), 'Plugin::instance() should set the static instance property' );
- }
-
-
- public function test_database_table_creation() {
- global $wpdb;
-
- // Setup plugin and run activation hook
- PluginHooks::init();
- wpgraphql_logging_activation_callback();
-
- // Check if the table now exists
- $table_exists = $wpdb->get_var( $wpdb->prepare(
- "SHOW TABLES LIKE %s",
- DatabaseEntity::get_table_name()
- ) );
-
- $this->assertEquals( DatabaseEntity::get_table_name(), $table_exists, 'Database table should be created by setup method.' );
- }
-}
diff --git a/plugins/wpgraphql-logging/wpgraphql-logging.php b/plugins/wpgraphql-logging/wpgraphql-logging.php
index 7a1f8a6d..5eeb07d1 100644
--- a/plugins/wpgraphql-logging/wpgraphql-logging.php
+++ b/plugins/wpgraphql-logging/wpgraphql-logging.php
@@ -140,4 +140,6 @@ function wpgraphql_logging_load_textdomain(): void {
add_action( 'init', 'wpgraphql_logging_load_textdomain', 1, 0 );
/** @psalm-suppress HookNotFound */
-wpgraphql_logging_init();
+add_action( 'plugins_loaded', static function (): void {
+ wpgraphql_logging_init();
+}, 10, 0 );