Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions plugins/wpgraphql-logging/activation.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
}
8 changes: 5 additions & 3 deletions plugins/wpgraphql-logging/deactivation.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<?php

declare(strict_types=1);

/**
* Deactivation Hook
*
Expand All @@ -10,9 +7,14 @@
* @since 0.0.1
*/

declare(strict_types=1);

use WPGraphQL\Logging\Plugin;

/**
* Runs when the plugin is deactivated.
*/
function wpgraphql_logging_deactivation_callback(): void {
Plugin::deactivate();
do_action( 'wpgraphql_logging_deactivate' );
}
1 change: 1 addition & 0 deletions plugins/wpgraphql-logging/phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<exclude-pattern>*/phpunit.xml*</exclude-pattern>
<exclude-pattern>**/tests/**</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/src/Events/QueryEventLifecycle.php</exclude-pattern>

<!-- ===================================== -->
<!-- CLI Arguments & Version Config -->
Expand Down
2 changes: 2 additions & 0 deletions plugins/wpgraphql-logging/phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ parameters:
paths:
- wpgraphql-logging.php
- src/
excludePaths:
- src/Events/QueryEventLifecycle.php
4 changes: 4 additions & 0 deletions plugins/wpgraphql-logging/psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
<file name="deactivation.php"/>
<file name="vendor/autoload.php"/>
<directory name="src"/>
<ignoreFiles>
<file name="src/Events/QueryEventLifecycle.php"/>
</ignoreFiles>
</projectFiles>


<plugins>
<pluginClass class="PsalmWordPress\Plugin"/>
</plugins>
Expand Down
112 changes: 90 additions & 22 deletions plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -29,7 +32,7 @@ class QueryEventLifecycle {
*
* @param \WPGraphQL\Logging\Logger\LoggerService $logger
*/
protected function __construct(readonly LoggerService $logger) {
protected function __construct( readonly LoggerService $logger ) {
}

/**
Expand All @@ -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 );
Expand All @@ -67,34 +76,93 @@ 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<string, mixed> $variables The variables for the query.
* @param \GraphQL\Executor\ExecutionResult|array<int, \GraphQL\Executor\ExecutionResult> $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() );
}
}

/**
* Register actions and filters.
*/
protected function setup(): void {

// @TODO: Update POC and use pub/sub for query events.

/**
* @psalm-suppress HookNotFound
*/
Expand All @@ -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 );
}
}
Empty file.
57 changes: 0 additions & 57 deletions plugins/wpgraphql-logging/src/Hooks/PluginHooks.php

This file was deleted.

10 changes: 5 additions & 5 deletions plugins/wpgraphql-logging/src/Logger/LoggerService.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class LoggerService {
/**
* The instance of the logger based off the channel name.
*
* @var array<LoggerService>
* @var array<\WPGraphQL\Logging\Logger\LoggerService>
*/
protected static array $instances = [];

Expand Down Expand Up @@ -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 ];
}

/**
Expand Down
18 changes: 16 additions & 2 deletions plugins/wpgraphql-logging/src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Loading