Skip to content

Commit eba3f3c

Browse files
authored
Merge pull request #394 from wpengine/feat-logging-additional-settings
feat(logging): Refactor query event lifecycle for modularity. Added more lifecycle options.
2 parents 02d8ecb + 8178a4f commit eba3f3c

File tree

8 files changed

+621
-314
lines changed

8 files changed

+621
-314
lines changed

plugins/wpgraphql-logging/src/Admin/.gitkeep

Whitespace-only changes.

plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,21 @@ public function get_fields(): array {
150150

151151

152152
$fields[ self::EVENT_LOG_SELECTION ] = new Select_Field(
153-
self::EVENT_LOG_SELECTION,
154-
$this->get_name(),
155-
__( 'Log Points', 'wpgraphql-logging' ),
156-
[
157-
Events::PRE_REQUEST => __( 'Pre Request', 'wpgraphql-logging' ),
158-
Events::BEFORE_GRAPHQL_EXECUTION => __( 'Before Query Execution', 'wpgraphql-logging' ),
159-
Events::BEFORE_RESPONSE_RETURNED => __( 'Before Response Returned', 'wpgraphql-logging' ),
160-
],
161-
'',
162-
__( 'Select which points in the request lifecycle to log. By default, all points are logged.', 'wpgraphql-logging' ),
163-
true
164-
);
153+
self::EVENT_LOG_SELECTION,
154+
$this->get_name(),
155+
__( 'Log Points', 'wpgraphql-logging' ),
156+
[
157+
Events::PRE_REQUEST => __( 'Pre Request', 'wpgraphql-logging' ),
158+
Events::BEFORE_GRAPHQL_EXECUTION => __( 'Before Query Execution', 'wpgraphql-logging' ),
159+
Events::BEFORE_RESPONSE_RETURNED => __( 'Before Response Returned', 'wpgraphql-logging' ),
160+
Events::REQUEST_DATA => __( 'Request Data', 'wpgraphql-logging' ),
161+
Events::REQUEST_RESULTS => __( 'Request Results', 'wpgraphql-logging' ),
162+
Events::RESPONSE_HEADERS_TO_SEND => __( 'Response Headers', 'wpgraphql-logging' ),
163+
],
164+
'',
165+
__( 'Select which points in the request lifecycle to log. By default, all points are logged.', 'wpgraphql-logging' ),
166+
true
167+
);
165168

166169
return apply_filters( 'wpgraphql_logging_basic_configuration_fields', $fields );
167170
}

plugins/wpgraphql-logging/src/Events/.gitkeep

Whitespace-only changes.

plugins/wpgraphql-logging/src/Events/Events.php

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,75 @@
88
* List of available events that users can subscribe to with the EventManager.
99
*/
1010
final class Events {
11-
/**
12-
* WPGraphQL action: do_graphql_request.
13-
*
14-
* Before the request is processed.
15-
*
16-
* @var string
17-
*/
18-
public const PRE_REQUEST = 'do_graphql_request';
19-
20-
/**
21-
* WPGraphQL action: graphql_before_execute.
22-
*
23-
* @var string
24-
*/
25-
public const BEFORE_GRAPHQL_EXECUTION = 'graphql_before_execute';
26-
27-
/**
28-
* WPGraphQL action: graphql_return_response
29-
*
30-
* Before the response is returned to the client.
31-
*
32-
* @var string
33-
*/
34-
public const BEFORE_RESPONSE_RETURNED = 'graphql_return_response';
35-
}
11+
/**
12+
* WPGraphQL action: do_graphql_request.
13+
*
14+
* Before the request is processed.
15+
*
16+
* @var string
17+
*/
18+
public const PRE_REQUEST = 'do_graphql_request';
19+
20+
/**
21+
* WPGraphQL action: graphql_before_execute.
22+
*
23+
* @var string
24+
*/
25+
public const BEFORE_GRAPHQL_EXECUTION = 'graphql_before_execute';
26+
27+
/**
28+
* WPGraphQL action: graphql_return_response
29+
*
30+
* Before the response is returned to the client.
31+
*
32+
* @var string
33+
*/
34+
public const BEFORE_RESPONSE_RETURNED = 'graphql_return_response';
35+
36+
/**
37+
* WPGraphQL filter: graphql_request_data.
38+
*
39+
* Allows the request data to be filtered. Ideal for capturing the
40+
* full payload before processing.
41+
*
42+
* @var string
43+
*/
44+
public const REQUEST_DATA = 'graphql_request_data';
45+
46+
/**
47+
* WPGraphQL filter: graphql_response_headers_to_send.
48+
*
49+
* Filters the headers to send in the GraphQL response.
50+
*
51+
* @var string
52+
*/
53+
public const RESPONSE_HEADERS_TO_SEND = 'graphql_response_headers_to_send';
54+
55+
/**
56+
* WPGraphQL filter: graphql_request_results.
57+
*
58+
* Filters the final results of the GraphQL execution.
59+
*
60+
* @var string
61+
*/
62+
public const REQUEST_RESULTS = 'graphql_request_results';
63+
64+
/**
65+
* WPGraphQL filter: graphql_debug_enabled.
66+
*
67+
* Determines if GraphQL Debug is enabled. Useful for toggling logging.
68+
*
69+
* @var string
70+
*/
71+
public const DEBUG_ENABLED = 'graphql_debug_enabled';
72+
73+
/**
74+
* WPGraphQL filter: graphql_app_context_config.
75+
*
76+
* Filters the config for the AppContext. Useful for storing temporary
77+
* data for the duration of a request.
78+
*
79+
* @var string
80+
*/
81+
public const APP_CONTEXT_CONFIG = 'graphql_app_context_config';
82+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WPGraphQL\Logging\Events;
6+
7+
use GraphQL\Executor\ExecutionResult;
8+
use Monolog\Level;
9+
use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab;
10+
use WPGraphQL\Logging\Logger\LoggerService;
11+
use WPGraphQL\Logging\Logger\LoggingHelper;
12+
use WPGraphQL\Request;
13+
use WPGraphQL\WPSchema;
14+
15+
/**
16+
* Handles logging for GraphQL actions.
17+
*
18+
* This class is a dedicated component for listening to and logging data
19+
* from specific WPGraphQL action hooks.
20+
*
21+
* @package WPGraphQL\Logging
22+
*
23+
* @since 0.0.1
24+
*/
25+
class QueryActionLogger {
26+
use LoggingHelper;
27+
28+
/**
29+
* The logger service instance.
30+
*
31+
* @var \WPGraphQL\Logging\Logger\LoggerService
32+
*/
33+
protected LoggerService $logger;
34+
35+
/**
36+
* The basic configuration settings.
37+
*
38+
* @var array<string, string|int|bool|array<string>>
39+
*/
40+
protected array $config;
41+
42+
/**
43+
* QueryActionLogger constructor.
44+
*
45+
* @param \WPGraphQL\Logging\Logger\LoggerService $logger The logger instance.
46+
* @param array<string, mixed> $config The logging configuration.
47+
*/
48+
public function __construct( LoggerService $logger, array $config ) {
49+
$this->logger = $logger;
50+
$this->config = $config;
51+
}
52+
53+
/**
54+
* Initial Incoming Request.
55+
*
56+
* This method hooks into the `do_graphql_request` action.
57+
*
58+
* @param string $query
59+
* @param string|null $operation_name
60+
* @param array<string, mixed>|null $variables
61+
*/
62+
public function log_pre_request( string $query, ?string $operation_name, ?array $variables ): void {
63+
try {
64+
if ( ! $this->is_logging_enabled( $this->config ) ) {
65+
return;
66+
}
67+
$selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? [];
68+
if ( ! in_array( Events::PRE_REQUEST, $selected_events, true ) ) {
69+
return;
70+
}
71+
$context = [
72+
'query' => $query,
73+
'variables' => $variables,
74+
'operation_name' => $operation_name,
75+
];
76+
$payload = EventManager::transform( Events::PRE_REQUEST, [ 'context' => $context, 'level' => Level::Info ] );
77+
$this->logger->log( $payload['level'], 'WPGraphQL Pre Request', $payload['context'] );
78+
EventManager::publish( Events::PRE_REQUEST, [ 'context' => $payload['context'] ] );
79+
} catch ( \Throwable $e ) {
80+
$this->process_application_error( Events::PRE_REQUEST, $e );
81+
}
82+
}
83+
84+
/**
85+
* Before Request Execution.
86+
*
87+
* This method hooks into the `graphql_before_execute` action.
88+
*
89+
* @param Request $request
90+
*/
91+
public function log_graphql_before_execute( Request $request ): void {
92+
try {
93+
if ( ! $this->is_logging_enabled( $this->config ) ) {
94+
return;
95+
}
96+
$selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? [];
97+
if ( ! in_array( Events::BEFORE_GRAPHQL_EXECUTION, $selected_events, true ) ) {
98+
return;
99+
}
100+
/** @var \GraphQL\Server\OperationParams $params */
101+
$params = $request->params;
102+
$context = [
103+
'query' => $params->query,
104+
'operation_name' => $params->operation,
105+
'variables' => $params->variables,
106+
'params' => $params,
107+
];
108+
$payload = EventManager::transform( Events::BEFORE_GRAPHQL_EXECUTION, [ 'context' => $context, 'level' => Level::Info ] );
109+
$this->logger->log( $payload['level'], 'WPGraphQL Before Query Execution', $payload['context'] );
110+
EventManager::publish( Events::BEFORE_GRAPHQL_EXECUTION, [ 'context' => $payload['context'] ] );
111+
} catch ( \Throwable $e ) {
112+
$this->process_application_error( Events::BEFORE_GRAPHQL_EXECUTION, $e );
113+
}
114+
}
115+
116+
/**
117+
* Before the GraphQL response is returned to the client.
118+
*
119+
* This method hooks into the `graphql_return_response` action.
120+
*
121+
* @param array<mixed>|\GraphQL\Executor\ExecutionResult $filtered_response
122+
* @param array<mixed>|\GraphQL\Executor\ExecutionResult $response
123+
* @param WPSchema $schema
124+
* @param string|null $operation
125+
* @param string $query
126+
* @param array<string, mixed>|null $variables
127+
* @param Request $request
128+
* @param string|null $query_id
129+
*/
130+
public function log_before_response_returned(
131+
array|ExecutionResult $filtered_response,
132+
array|ExecutionResult $response,
133+
WPSchema $schema,
134+
?string $operation,
135+
string $query,
136+
?array $variables,
137+
Request $request,
138+
?string $query_id
139+
): void {
140+
try {
141+
if ( ! $this->is_logging_enabled( $this->config ) ) {
142+
return;
143+
}
144+
$selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? [];
145+
if ( ! in_array( Events::BEFORE_RESPONSE_RETURNED, $selected_events, true ) ) {
146+
return;
147+
}
148+
$context = [
149+
'response' => $response,
150+
'schema' => $schema,
151+
'operation_name' => $operation,
152+
'query' => $query,
153+
'variables' => $variables,
154+
'request' => $request,
155+
'query_id' => $query_id,
156+
];
157+
$level = Level::Info;
158+
$message = 'WPGraphQL Response';
159+
$errors = $this->get_response_errors( $response );
160+
if ( null !== $errors && count( $errors ) > 0 ) {
161+
$context['errors'] = $errors;
162+
$level = Level::Error;
163+
$message = 'WPGraphQL Response with Errors';
164+
}
165+
$payload = EventManager::transform( Events::BEFORE_RESPONSE_RETURNED, [ 'context' => $context, 'level' => $level ] );
166+
$this->logger->log( $payload['level'], $message, $payload['context'] );
167+
EventManager::publish( Events::BEFORE_RESPONSE_RETURNED, [ 'context' => $payload['context'] ] );
168+
} catch ( \Throwable $e ) {
169+
$this->process_application_error( Events::BEFORE_RESPONSE_RETURNED, $e );
170+
}
171+
}
172+
173+
/**
174+
* Get the context for the response.
175+
*
176+
* @param array<mixed>|\GraphQL\Executor\ExecutionResult $response The response.
177+
*
178+
* @return array<mixed>|null
179+
*/
180+
protected function get_response_errors( array|ExecutionResult $response ): ?array {
181+
if ( $response instanceof ExecutionResult && [] !== $response->errors ) {
182+
return $response->errors;
183+
}
184+
185+
if ( ! is_array( $response ) ) {
186+
return null;
187+
}
188+
189+
$errors = $response['errors'] ?? null;
190+
if ( null === $errors || [] === $errors ) {
191+
return null;
192+
}
193+
194+
return $errors;
195+
}
196+
197+
/**
198+
* Handles and logs application errors.
199+
*
200+
* @param string $event
201+
* @param \Throwable $exception
202+
*/
203+
protected function process_application_error( string $event, \Throwable $exception ): void {
204+
error_log( 'Error for WPGraphQL Logging - ' . $event . ': ' . $exception->getMessage() . ' in ' . $exception->getFile() . ' on line ' . $exception->getLine() ); //phpcs:ignore
205+
}
206+
}

0 commit comments

Comments
 (0)