Skip to content

Commit 915c25e

Browse files
committed
Created POC (not complete) with database handler, graphql query processor, and POC service to log request/response (not complete).
1 parent 1ac0bfa commit 915c25e

File tree

11 files changed

+572
-35
lines changed

11 files changed

+572
-35
lines changed

plugins/wpgraphql-logging/phpcs.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
<arg name="extensions" value="php"/>
2727
<arg name="severity" value="1"/>
2828
<arg name="parallel" value="20"/>
29-
<config name="testVersion" value="7.4-"/>
30-
<config name="minimum_wp_version" value="6.0"/>
29+
<config name="testVersion" value="8.1-"/>
30+
<config name="minimum_wp_version" value="6.5"/>
3131

3232
<!-- ===================================== -->
3333
<!-- Base Standards -->

plugins/wpgraphql-logging/phpstan.neon.dist

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ parameters:
2525
# Configuration
2626
level: 8
2727
phpVersion:
28-
min: 70400
28+
min: 80100
2929
max: 80400
3030
paths:
3131
- wpgraphql-logging.php
3232
- src/
33-

plugins/wpgraphql-logging/psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
88
findUnusedBaselineEntry="true"
99
findUnusedCode="false"
10+
phpVersion="8.1"
1011
>
1112
<projectFiles>
1213
<file name="wpgraphql-logging.php"/>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WPGraphQL\Logging\Events;
6+
7+
use Monolog\Level;
8+
use WPGraphQL\Logging\Logger\LoggerService;
9+
10+
/**
11+
* WPGraphQL Query Event Lifecycle -
12+
*
13+
* POC @TODO - Add pub/sub for query events.
14+
*
15+
* @package WPGraphQL\Logging
16+
*
17+
* @since 0.0.1
18+
*/
19+
class QueryEventLifecycle {
20+
/**
21+
* The single instance of the class.
22+
*
23+
* @var \WPGraphQL\Logging\Events\QueryEventLifecycle|null
24+
*/
25+
private static ?QueryEventLifecycle $instance = null;
26+
27+
/**
28+
* The logger service instance.
29+
*
30+
* @param \WPGraphQL\Logging\Logger\LoggerService $logger
31+
*/
32+
protected function __construct(readonly LoggerService $logger) {
33+
}
34+
35+
/**
36+
* Get or create the single instance of the class.
37+
*/
38+
public static function init(): QueryEventLifecycle {
39+
if ( null === self::$instance ) {
40+
// @TODO - Add filter to allow for custom logger service.
41+
$logger = LoggerService::get_instance();
42+
self::$instance = new self( $logger );
43+
self::$instance->setup();
44+
}
45+
return self::$instance;
46+
}
47+
48+
/**
49+
* Logs the pre-request event for a GraphQL query.
50+
*
51+
* @param string $query The GraphQL query.
52+
* @param mixed $variables The variables for the query.
53+
* @param string $operation_name The name of the operation.
54+
*/
55+
public function log_pre_request( $query, $variables, $operation_name ): void {
56+
57+
try {
58+
$context = [];
59+
$context = apply_filters( 'wpgraphql_logging_pre_request_context', $context, $query, $variables, $operation_name );
60+
$level = apply_filters( 'wpgraphql_logging_pre_request_level', Level::Info, $query, $variables, $operation_name );
61+
$this->logger->log( $level, 'WPGraphQL Incoming Request', $context );
62+
} catch ( \Throwable $e ) {
63+
// @TODO - Handle logging errors gracefully.
64+
error_log( 'Error in log_pre_request: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
65+
}
66+
}
67+
68+
/**
69+
* Logs the post-request event for a GraphQL query.
70+
*
71+
* @param mixed $response The response from the GraphQL request.
72+
* @param mixed $result The result of the GraphQL request.
73+
* @param string $operation_name The name of the operation.
74+
* @param string $query The GraphQL query.
75+
* @param array<string, mixed> $variables The variables for the query.
76+
*/
77+
public function log_post_request( $response, $result, string $operation_name, string $query, array $variables ): void {
78+
79+
try {
80+
$context = [];
81+
$level = Level::Info;
82+
$context = apply_filters( 'wpgraphql_logging_post_request_context', $context, $response, $result, $operation_name, $query, $variables );
83+
$level = apply_filters( 'wpgraphql_logging_post_request_level', $level, $response, $result, $operation_name, $query, $variables );
84+
$this->logger->log( $level, 'WPGraphQL Outgoing Response', $context );
85+
} catch ( \Throwable $e ) {
86+
// @TODO - Handle logging errors gracefully.
87+
error_log( 'Error in log_post_request: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
88+
}
89+
}
90+
91+
/**
92+
* Register actions and filters.
93+
*/
94+
protected function setup(): void {
95+
96+
// @TODO: Update POC and use pub/sub for query events.
97+
98+
/**
99+
* @psalm-suppress HookNotFound
100+
*/
101+
add_action( 'do_graphql_request', [ $this, 'log_pre_request' ], 10, 3 );
102+
103+
/**
104+
* @psalm-suppress HookNotFound
105+
*/
106+
add_action( 'graphql_process_http_request_response', [ $this, 'log_post_request' ], 10, 5 );
107+
}
108+
}

plugins/wpgraphql-logging/src/Hooks/PluginHooks.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66

77
use WPGraphQL\Logging\Logger\Database\DatabaseEntity;
88

9+
/**
10+
* Hooks for the WPGraphQL Logging plugin.
11+
*
12+
* @package WPGraphQL\Logging
13+
*
14+
* @since 0.0.1
15+
*/
916
class PluginHooks {
1017
/**
1118
* The single instance of the class.

plugins/wpgraphql-logging/src/Logger/Database/DatabaseEntity.php

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,68 @@ public function save(): int {
152152
return 0;
153153
}
154154

155+
/**
156+
* Gets the ID of the log entry.
157+
*/
158+
public function get_id(): ?int {
159+
return $this->id;
160+
}
161+
162+
/**
163+
* Gets the channel of the log entry.
164+
*/
165+
public function get_channel(): string {
166+
return $this->channel;
167+
}
168+
169+
/**
170+
* Gets the logging level of the log entry.
171+
*/
172+
public function get_level(): int {
173+
return $this->level;
174+
}
175+
176+
/**
177+
* Gets the name of the logging level of the log entry.
178+
*/
179+
public function get_level_name(): string {
180+
return $this->level_name;
181+
}
182+
183+
/**
184+
* Gets the message of the log entry.
185+
*/
186+
public function get_message(): string {
187+
return $this->message;
188+
}
189+
190+
/**
191+
* Gets the context of the log entry.
192+
*
193+
* @return array<string, mixed> The context of the log entry.
194+
*/
195+
public function get_context(): array {
196+
return $this->context;
197+
}
198+
199+
/**
200+
* Gets the extra data of the log entry.
201+
*
202+
* @return array<string, mixed> The extra data of the log entry.
203+
*/
204+
public function get_extra(): array {
205+
return $this->extra;
206+
}
207+
208+
/**
209+
* Gets the datetime of the log entry.
210+
*
211+
* @return string The datetime of the log entry in MySQL format.
212+
*/
213+
public function get_datetime(): string {
214+
return $this->datetime;
215+
}
216+
155217
/**
156218
* Gets the name of the logging table.
157219
*/
@@ -193,7 +255,7 @@ public static function get_schema(): string {
193255
* Creates the logging table in the database.
194256
*/
195257
public static function create_table(): void {
196-
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
258+
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; // @phpstan-ignore-line
197259
dbDelta( self::get_schema() );
198260
}
199261

@@ -250,30 +312,9 @@ private static function create_from_db_row(array $row): self {
250312
$log->level = (int) $row['level'];
251313
$log->level_name = $row['level_name'];
252314
$log->message = $row['message'];
253-
$log->context = $row['context'] ? json_decode( $row['context'], true ) : [];
254-
$log->extra = $row['extra'] ? json_decode( $row['extra'], true ) : [];
315+
$log->context = ( isset( $row['context'] ) && '' !== $row['context'] ) ? json_decode( $row['context'], true ) : [];
316+
$log->extra = ( isset( $row['extra'] ) && '' !== $row['extra'] ) ? json_decode( $row['extra'], true ) : [];
255317
$log->datetime = $row['datetime'];
256318
return $log;
257319
}
258-
259-
/**
260-
* Magic method to handle dynamic getters like get_level().
261-
*
262-
* @param string $name The name of the method called.
263-
* @param array<mixed> $arguments The arguments passed to the method.
264-
*
265-
* @throws \BadMethodCallException If the method does not exist.
266-
*
267-
* @return mixed The value of the property if it exists, otherwise throws an exception.
268-
*/
269-
public function __call(string $name, array $arguments) {
270-
if ( strpos( $name, 'get_' ) === 0 ) {
271-
$property = substr( $name, 4 );
272-
if ( property_exists( $this, $property ) ) {
273-
return $this->$property;
274-
}
275-
}
276-
$name = $this->sanitize_text_field( $name );
277-
throw new \BadMethodCallException( sprintf( 'Method %s does not exist.', esc_html( $name ) ) );
278-
}
279320
}

plugins/wpgraphql-logging/src/Logger/Handlers/WordPressDatabaseHandler.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,36 @@ class WordPressDatabaseHandler extends AbstractProcessingHandler {
2727
*/
2828
protected function write( LogRecord $record ): void {
2929
try {
30-
$name = $record->level->getName();
3130
$entity = DatabaseEntity::create(
3231
$record->channel,
3332
$record->level->value,
34-
(is_string($name) && $name !== '') ? $name : 'INFO',
33+
$this->get_record_name( $record ),
3534
$record->message,
36-
$record->context ?: [],
37-
$record->extra ?: []
35+
$record->context ?? [],
36+
$record->extra ?? []
3837
);
3938

4039
$entity->save();
4140
} catch ( Throwable $e ) {
4241
error_log( 'Error logging to WordPress database: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
4342
}
4443
}
44+
45+
/**
46+
* Gets the name of the log record.
47+
*
48+
* @param \Monolog\LogRecord $record The log record.
49+
*
50+
* @return string The name of the log record.
51+
*/
52+
protected function get_record_name( LogRecord $record ): string {
53+
54+
/**
55+
* @psalm-suppress InvalidCast
56+
*/
57+
$name = (string) $record->level->getName(); // @phpstan-ignore-line
58+
$default = 'INFO';
59+
60+
return $name ?: $default; // @phpstan-ignore-line
61+
}
4562
}

0 commit comments

Comments
 (0)