Skip to content

Commit 47b3771

Browse files
committed
Added docs and test for log store service.
1 parent 4f7fb64 commit 47b3771

File tree

4 files changed

+257
-0
lines changed

4 files changed

+257
-0
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# How to Replace the Log Storage Service
2+
3+
The WPGraphQL Logging plugin provides a robust database logging solution out of the box. However, for advanced use cases or integration with external logging systems, you can replace the default storage mechanism with your own custom implementation.
4+
5+
This is made possible by the `wpgraphql_logging_log_store_service` filter.
6+
7+
## Requirements
8+
9+
Your custom log service class must implement the `\WPGraphQL\Logging\Logger\Api\LogServiceInterface`. This ensures that your custom service has all the methods the plugin expects to interact with.
10+
11+
## Example: Logging to a File
12+
13+
Here is an example of how you could replace the default database logger with a simple file-based logger.
14+
15+
**1. Create your custom Log Service class**
16+
17+
First, create a class that implements `LogServiceInterface`. This is a simplified example that would log to a file in the `wp-content/uploads` directory.
18+
19+
```php
20+
<?php
21+
// In your theme's functions.php or a custom plugin
22+
23+
use WPGraphQL\Logging\Logger\Api\LogServiceInterface;
24+
use WPGraphQL\Logging\Logger\Api\LogEntityInterface;
25+
26+
class MyFileLogService implements LogServiceInterface {
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
public function create_log_entity( string $channel, int $level, string $level_name, string $message, array $context = [], array $extra = [] ): ?LogEntityInterface {
32+
$log_file = WP_CONTENT_DIR . '/uploads/wpgraphql-logs.log';
33+
$log_entry = sprintf(
34+
"[%s] %s.%s: %s %s %s\n",
35+
gmdate( 'Y-m-d H:i:s' ),
36+
$channel,
37+
$level_name,
38+
$message,
39+
wp_json_encode( $context ),
40+
wp_json_encode( $extra )
41+
);
42+
43+
file_put_contents( $log_file, $log_entry, FILE_APPEND );
44+
45+
// Return null as we are not creating a database entity.
46+
return null;
47+
}
48+
49+
public function find_entity_by_id(int $id): ?LogEntityInterface
50+
{
51+
return null;
52+
}
53+
54+
public function find_entities_by_where(array $args = []): array
55+
{
56+
return [];
57+
}
58+
59+
public function delete_entity_by_id(int $id): bool
60+
{
61+
return true;
62+
}
63+
64+
public function delete_entities_older_than(DateTime $date): bool
65+
{
66+
return true;
67+
}
68+
69+
public function delete_all_entities(): bool
70+
{
71+
return true;
72+
}
73+
74+
public function count_entities_by_where(array $args = []): int
75+
{
76+
return 0;
77+
}
78+
79+
public function activate(): void
80+
{
81+
}
82+
83+
public function deactivate(): void
84+
{
85+
}
86+
}
87+
```
88+
89+
**2. Hook into the filter**
90+
91+
Next, use the `wpgraphql_logging_log_store_service` filter to return an instance of your new class. It's best to do this early, for example on the `plugins_loaded` hook.
92+
93+
```php
94+
<?php
95+
96+
add_action( 'plugins_loaded', function() {
97+
add_filter( 'wpgraphql_logging_log_store_service', function( $log_service ) {
98+
// If another plugin hasn't already replaced the service,
99+
// replace it with our custom file logger.
100+
if ( null === $log_service ) {
101+
$log_service = new MyFileLogService();
102+
}
103+
return $log_service;
104+
} );
105+
}, 10, 0 );
106+
```
107+
108+
With this in place, all logs from WPGraphQL Logging will be routed through your `MyFileLogService` and saved to a file instead of the database.

plugins/wpgraphql-logging/docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ define( 'WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN', true );
181181
## How to Guides
182182

183183
### Admin
184+
- [How to replace the database log service](how-to/logger_replace_log_store_service.md)
184185
- [How to add a new Settings tab to WPGraphQL Logging](how-to/admin_add_new_tab.md)
185186
- [How to add a new field to an existing tab and query it](how-to/admin_add_fields.md)
186187
- [How to add a new column to the Logs admin grid](how-to/admin_add_view_column.md)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WPGraphQL\Logging\Unit\Logger\Store;
6+
7+
use WPGraphQL\Logging\Logger\Api\LogEntityInterface;
8+
use WPGraphQL\Logging\Logger\Api\LogServiceInterface;
9+
use DateTime;
10+
11+
/**
12+
* A stub implementation of LogServiceInterface for testing purposes.
13+
*/
14+
class CustomLogServiceStub implements LogServiceInterface
15+
{
16+
public function create_log_entity(string $channel, int $level, string $level_name, string $message, array $context = [], array $extra = []): ?LogEntityInterface
17+
{
18+
return null;
19+
}
20+
21+
public function find_entity_by_id(int $id): ?LogEntityInterface
22+
{
23+
return null;
24+
}
25+
26+
public function find_entities_by_where(array $args = []): array
27+
{
28+
return [];
29+
}
30+
31+
public function delete_entity_by_id(int $id): bool
32+
{
33+
return true;
34+
}
35+
36+
public function delete_entities_older_than(DateTime $date): bool
37+
{
38+
return true;
39+
}
40+
41+
public function delete_all_entities(): bool
42+
{
43+
return true;
44+
}
45+
46+
public function count_entities_by_where(array $args = []): int
47+
{
48+
return 0;
49+
}
50+
51+
public function activate(): void
52+
{
53+
}
54+
55+
public function deactivate(): void
56+
{
57+
}
58+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WPGraphQL\Logging\Unit\Logger\Store;
6+
7+
use WPGraphQL\Logging\Logger\Database\WordPressDatabaseLogService;
8+
use WPGraphQL\Logging\Logger\Store\LogStoreService;
9+
use lucatume\WPBrowser\TestCase\WPTestCase;
10+
11+
require_once __DIR__ . '/CustomLogServiceStub.php';
12+
13+
class LogStoreServiceTest extends WPTestCase
14+
{
15+
/**
16+
* Resets the singleton instance in LogStoreService using reflection.
17+
*/
18+
protected function resetLogStoreService()
19+
{
20+
$reflection = new \ReflectionClass(LogStoreService::class);
21+
$instanceProperty = $reflection->getProperty('instance');
22+
$instanceProperty->setAccessible(true);
23+
$instanceProperty->setValue(null, null);
24+
$instanceProperty->setAccessible(false);
25+
}
26+
27+
public function testGetLogServiceReturnsDefaultService()
28+
{
29+
$this->resetLogStoreService();
30+
remove_all_filters('wpgraphql_logging_log_store_service');
31+
32+
$service = LogStoreService::get_log_service();
33+
$this->assertInstanceOf(WordPressDatabaseLogService::class, $service);
34+
}
35+
36+
public function testGetLogServiceCanBeReplacedByFilter()
37+
{
38+
$this->resetLogStoreService();
39+
remove_all_filters('wpgraphql_logging_log_store_service');
40+
41+
$customService = new CustomLogServiceStub();
42+
43+
add_filter('wpgraphql_logging_log_store_service', function ($service) use ($customService) {
44+
return $customService;
45+
});
46+
47+
$service = LogStoreService::get_log_service();
48+
49+
$this->assertInstanceOf(CustomLogServiceStub::class, $service);
50+
$this->assertSame($customService, $service);
51+
52+
// It should return the same instance on subsequent calls
53+
$service2 = LogStoreService::get_log_service();
54+
$this->assertSame($customService, $service2);
55+
56+
remove_all_filters('wpgraphql_logging_log_store_service');
57+
}
58+
59+
public function testFilterReturningNullUsesDefaultService()
60+
{
61+
$this->resetLogStoreService();
62+
remove_all_filters('wpgraphql_logging_log_store_service');
63+
64+
add_filter('wpgraphql_logging_log_store_service', function ($service) {
65+
// A filter that does nothing or returns null
66+
return null;
67+
});
68+
69+
$service = LogStoreService::get_log_service();
70+
$this->assertInstanceOf(WordPressDatabaseLogService::class, $service);
71+
72+
remove_all_filters('wpgraphql_logging_log_store_service');
73+
}
74+
75+
public function testFilterReturningInvalidObjectUsesDefaultService()
76+
{
77+
$this->resetLogStoreService();
78+
remove_all_filters('wpgraphql_logging_log_store_service');
79+
80+
add_filter('wpgraphql_logging_log_store_service', function ($service) {
81+
// Return something that does not implement the interface
82+
return new \stdClass();
83+
});
84+
85+
$service = LogStoreService::get_log_service();
86+
$this->assertInstanceOf(WordPressDatabaseLogService::class, $service);
87+
88+
remove_all_filters('wpgraphql_logging_log_store_service');
89+
}
90+
}

0 commit comments

Comments
 (0)