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
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,21 @@ public function get_fields(): array {


$fields[ self::EVENT_LOG_SELECTION ] = new Select_Field(
self::EVENT_LOG_SELECTION,
$this->get_name(),
__( 'Log Points', 'wpgraphql-logging' ),
[
Events::PRE_REQUEST => __( 'Pre Request', 'wpgraphql-logging' ),
Events::BEFORE_GRAPHQL_EXECUTION => __( 'Before Query Execution', 'wpgraphql-logging' ),
Events::BEFORE_RESPONSE_RETURNED => __( 'Before Response Returned', 'wpgraphql-logging' ),
],
'',
__( 'Select which points in the request lifecycle to log. By default, all points are logged.', 'wpgraphql-logging' ),
true
);
self::EVENT_LOG_SELECTION,
$this->get_name(),
__( 'Log Points', 'wpgraphql-logging' ),
[
Events::PRE_REQUEST => __( 'Pre Request', 'wpgraphql-logging' ),
Events::BEFORE_GRAPHQL_EXECUTION => __( 'Before Query Execution', 'wpgraphql-logging' ),
Events::BEFORE_RESPONSE_RETURNED => __( 'Before Response Returned', 'wpgraphql-logging' ),
Events::REQUEST_DATA => __( 'Request Data', 'wpgraphql-logging' ),
Events::REQUEST_RESULTS => __( 'Request Results', 'wpgraphql-logging' ),
Events::RESPONSE_HEADERS_TO_SEND => __( 'Response Headers', 'wpgraphql-logging' ),
],
'',
__( 'Select which points in the request lifecycle to log. By default, all points are logged.', 'wpgraphql-logging' ),
true
);

return apply_filters( 'wpgraphql_logging_basic_configuration_fields', $fields );
}
Expand Down
Empty file.
97 changes: 72 additions & 25 deletions plugins/wpgraphql-logging/src/Events/Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,75 @@
* List of available events that users can subscribe to with the EventManager.
*/
final class Events {
/**
* WPGraphQL action: do_graphql_request.
*
* Before the request is processed.
*
* @var string
*/
public const PRE_REQUEST = 'do_graphql_request';

/**
* WPGraphQL action: graphql_before_execute.
*
* @var string
*/
public const BEFORE_GRAPHQL_EXECUTION = 'graphql_before_execute';

/**
* WPGraphQL action: graphql_return_response
*
* Before the response is returned to the client.
*
* @var string
*/
public const BEFORE_RESPONSE_RETURNED = 'graphql_return_response';
}
/**
* WPGraphQL action: do_graphql_request.
*
* Before the request is processed.
*
* @var string
*/
public const PRE_REQUEST = 'do_graphql_request';

/**
* WPGraphQL action: graphql_before_execute.
*
* @var string
*/
public const BEFORE_GRAPHQL_EXECUTION = 'graphql_before_execute';

/**
* WPGraphQL action: graphql_return_response
*
* Before the response is returned to the client.
*
* @var string
*/
public const BEFORE_RESPONSE_RETURNED = 'graphql_return_response';

/**
* WPGraphQL filter: graphql_request_data.
*
* Allows the request data to be filtered. Ideal for capturing the
* full payload before processing.
*
* @var string
*/
public const REQUEST_DATA = 'graphql_request_data';

/**
* WPGraphQL filter: graphql_response_headers_to_send.
*
* Filters the headers to send in the GraphQL response.
*
* @var string
*/
public const RESPONSE_HEADERS_TO_SEND = 'graphql_response_headers_to_send';

/**
* WPGraphQL filter: graphql_request_results.
*
* Filters the final results of the GraphQL execution.
*
* @var string
*/
public const REQUEST_RESULTS = 'graphql_request_results';

/**
* WPGraphQL filter: graphql_debug_enabled.
*
* Determines if GraphQL Debug is enabled. Useful for toggling logging.
*
* @var string
*/
public const DEBUG_ENABLED = 'graphql_debug_enabled';

/**
* WPGraphQL filter: graphql_app_context_config.
*
* Filters the config for the AppContext. Useful for storing temporary
* data for the duration of a request.
*
* @var string
*/
public const APP_CONTEXT_CONFIG = 'graphql_app_context_config';
}
206 changes: 206 additions & 0 deletions plugins/wpgraphql-logging/src/Events/QueryActionLogger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<?php

declare(strict_types=1);

namespace WPGraphQL\Logging\Events;

use GraphQL\Executor\ExecutionResult;
use Monolog\Level;
use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab;
use WPGraphQL\Logging\Logger\LoggerService;
use WPGraphQL\Logging\Logger\LoggingHelper;
use WPGraphQL\Request;
use WPGraphQL\WPSchema;

/**
* Handles logging for GraphQL actions.
*
* This class is a dedicated component for listening to and logging data
* from specific WPGraphQL action hooks.
*
* @package WPGraphQL\Logging
*
* @since 0.0.1
*/
class QueryActionLogger {
use LoggingHelper;

/**
* The logger service instance.
*
* @var \WPGraphQL\Logging\Logger\LoggerService
*/
protected LoggerService $logger;

/**
* The basic configuration settings.
*
* @var array<string, string|int|bool|array<string>>
*/
protected array $config;

/**
* QueryActionLogger constructor.
*
* @param \WPGraphQL\Logging\Logger\LoggerService $logger The logger instance.
* @param array<string, mixed> $config The logging configuration.
*/
public function __construct( LoggerService $logger, array $config ) {
$this->logger = $logger;
$this->config = $config;
}

/**
* Initial Incoming Request.
*
* This method hooks into the `do_graphql_request` action.
*
* @param string $query
* @param string|null $operation_name
* @param array<string, mixed>|null $variables
*/
public function log_pre_request( string $query, ?string $operation_name, ?array $variables ): void {
try {
if ( ! $this->is_logging_enabled( $this->config ) ) {
return;
}
$selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? [];
if ( ! in_array( Events::PRE_REQUEST, $selected_events, true ) ) {
return;
}
$context = [
'query' => $query,
'variables' => $variables,
'operation_name' => $operation_name,
];
$payload = EventManager::transform( Events::PRE_REQUEST, [ 'context' => $context, 'level' => Level::Info ] );
$this->logger->log( $payload['level'], 'WPGraphQL Pre Request', $payload['context'] );
EventManager::publish( Events::PRE_REQUEST, [ 'context' => $payload['context'] ] );
} catch ( \Throwable $e ) {
$this->process_application_error( Events::PRE_REQUEST, $e );
}
}

/**
* Before Request Execution.
*
* This method hooks into the `graphql_before_execute` action.
*
* @param Request $request
*/
public function log_graphql_before_execute( Request $request ): void {
try {
if ( ! $this->is_logging_enabled( $this->config ) ) {
return;
}
$selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? [];
if ( ! in_array( Events::BEFORE_GRAPHQL_EXECUTION, $selected_events, true ) ) {
return;
}
/** @var \GraphQL\Server\OperationParams $params */
$params = $request->params;
$context = [
'query' => $params->query,
'operation_name' => $params->operation,
'variables' => $params->variables,
'params' => $params,
];
$payload = EventManager::transform( Events::BEFORE_GRAPHQL_EXECUTION, [ 'context' => $context, 'level' => Level::Info ] );
$this->logger->log( $payload['level'], 'WPGraphQL Before Query Execution', $payload['context'] );
EventManager::publish( Events::BEFORE_GRAPHQL_EXECUTION, [ 'context' => $payload['context'] ] );
} catch ( \Throwable $e ) {
$this->process_application_error( Events::BEFORE_GRAPHQL_EXECUTION, $e );
}
}

/**
* Before the GraphQL response is returned to the client.
*
* This method hooks into the `graphql_return_response` action.
*
* @param array<mixed>|\GraphQL\Executor\ExecutionResult $filtered_response
* @param array<mixed>|\GraphQL\Executor\ExecutionResult $response
* @param WPSchema $schema
* @param string|null $operation
* @param string $query
* @param array<string, mixed>|null $variables
* @param Request $request
* @param string|null $query_id
*/
public function log_before_response_returned(
array|ExecutionResult $filtered_response,
array|ExecutionResult $response,
WPSchema $schema,
?string $operation,
string $query,
?array $variables,
Request $request,
?string $query_id
): void {
try {
if ( ! $this->is_logging_enabled( $this->config ) ) {
return;
}
$selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? [];
if ( ! in_array( Events::BEFORE_RESPONSE_RETURNED, $selected_events, true ) ) {
return;
}
$context = [
'response' => $response,
'schema' => $schema,
'operation_name' => $operation,
'query' => $query,
'variables' => $variables,
'request' => $request,
'query_id' => $query_id,
];
$level = Level::Info;
$message = 'WPGraphQL Response';
$errors = $this->get_response_errors( $response );
if ( null !== $errors && count( $errors ) > 0 ) {
$context['errors'] = $errors;
$level = Level::Error;
$message = 'WPGraphQL Response with Errors';
}
$payload = EventManager::transform( Events::BEFORE_RESPONSE_RETURNED, [ 'context' => $context, 'level' => $level ] );
$this->logger->log( $payload['level'], $message, $payload['context'] );
EventManager::publish( Events::BEFORE_RESPONSE_RETURNED, [ 'context' => $payload['context'] ] );
} catch ( \Throwable $e ) {
$this->process_application_error( Events::BEFORE_RESPONSE_RETURNED, $e );
}
}

/**
* Get the context for the response.
*
* @param array<mixed>|\GraphQL\Executor\ExecutionResult $response The response.
*
* @return array<mixed>|null
*/
protected function get_response_errors( array|ExecutionResult $response ): ?array {
if ( $response instanceof ExecutionResult && [] !== $response->errors ) {
return $response->errors;
}

if ( ! is_array( $response ) ) {
return null;
}

$errors = $response['errors'] ?? null;
if ( null === $errors || [] === $errors ) {
return null;
}

return $errors;
}

/**
* Handles and logs application errors.
*
* @param string $event
* @param \Throwable $exception
*/
protected function process_application_error( string $event, \Throwable $exception ): void {
error_log( 'Error for WPGraphQL Logging - ' . $event . ': ' . $exception->getMessage() . ' in ' . $exception->getFile() . ' on line ' . $exception->getLine() ); //phpcs:ignore
}
}
Loading
Loading