Skip to content

Commit 8127de1

Browse files
committed
Update logger service instance to get singleton instance by channel name. Added unit tests.
1 parent 2590baa commit 8127de1

File tree

3 files changed

+162
-8
lines changed

3 files changed

+162
-8
lines changed

plugins/wpgraphql-logging/codeception.dist.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ coverage:
5151
- /vendor/*
5252
- /src/Templates/*
5353
show_only_summary: true
54-
min_coverage: 90
54+
min_coverage: 85
5555
modules:
5656
config:
5757
REST:

plugins/wpgraphql-logging/src/Logger/LoggerService.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ class LoggerService {
4242
protected readonly Logger $monolog;
4343

4444
/**
45-
* The instance of the logger.
45+
* The instance of the logger based off the channel name.
4646
*
47-
* @var \WPGraphQL\Logging\Logger\LoggerService|null
47+
* @var array<LoggerService>
4848
*/
49-
protected static ?LoggerService $instance = null;
49+
protected static array $instances = [];
5050

5151
/**
5252
* Constructor for the LoggerService and initializes the Monolog logger with the provided channel, handlers, processors, and default context.
@@ -90,16 +90,16 @@ public static function get_instance(
9090
?array $processors = null,
9191
?array $default_context = null
9292
): LoggerService {
93-
if ( null !== self::$instance ) {
94-
return self::$instance;
93+
if ( isset(self::$instances[$channel]) ) {
94+
return self::$instances[$channel];
9595
}
9696

9797
$processors = $processors ?? self::get_default_processors();
9898
$handlers = $handlers ?? self::get_default_handlers();
9999
$default_context = $default_context ?? self::get_default_context();
100100

101-
self::$instance = new self( $channel, $handlers, $processors, $default_context );
102-
return self::$instance;
101+
self::$instances[$channel] = new self( $channel, $handlers, $processors, $default_context );
102+
return self::$instances[$channel];
103103
}
104104

105105
/**
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WPGraphQL\Logging\Tests\Logger;
6+
7+
use lucatume\WPBrowser\TestCase\WPTestCase;
8+
use Monolog\Handler\TestHandler;
9+
use Monolog\Processor\ProcessorInterface;
10+
use ReflectionClass;
11+
use WPGraphQL\Logging\Logger\LoggerService;
12+
use WPGraphQL\Logging\Logger\Database\DatabaseEntity;
13+
use Monolog\LogRecord;
14+
15+
/**
16+
* Class LoggerServiceTest
17+
*
18+
* Tests for the LoggerService class.
19+
*/
20+
class LoggerServiceTest extends WPTestCase
21+
{
22+
23+
public function tearDown(): void
24+
{
25+
parent::tearDown();
26+
$this->reset_logger_instance();
27+
}
28+
29+
private function reset_logger_instance(): void
30+
{
31+
$reflection = new ReflectionClass(LoggerService::class);
32+
$instance_prop = $reflection->getProperty('instances');
33+
$instance_prop->setAccessible(true);
34+
$instance_prop->setValue(null, []);
35+
}
36+
37+
private function get_monolog_instance(LoggerService $service): \Monolog\Logger
38+
{
39+
$reflection = new ReflectionClass($service);
40+
$monolog_prop = $reflection->getProperty('monolog');
41+
$monolog_prop->setAccessible(true);
42+
return $monolog_prop->getValue($service);
43+
}
44+
45+
public function test_get_instance_returns_singleton(): void
46+
{
47+
$instance1 = LoggerService::get_instance();
48+
$instance2 = LoggerService::get_instance();
49+
50+
$this->assertSame($instance1, $instance2, 'get_instance() should always return the same object.');
51+
52+
$instance3 = LoggerService::get_instance('custom_channel');
53+
$this->assertInstanceOf(LoggerService::class, $instance3, 'Instance should be of type LoggerService.');
54+
$this->assertNotSame($instance1, $instance3, 'get_instance() should return different instances for different channels.');
55+
$this->assertEquals('custom_channel', $instance3->channel);
56+
57+
$monolog = $this->get_monolog_instance($instance3);
58+
$this->assertCount(count(LoggerService::get_default_handlers()), $monolog->getHandlers(), 'Should have the default number of handlers.');
59+
$this->assertCount(count(LoggerService::get_default_processors()), $monolog->getProcessors(), 'Should have the default number of processors.');
60+
}
61+
62+
public function test_get_instance_with_custom_configuration(): void
63+
{
64+
$custom_handler = new TestHandler();
65+
$custom_processor = new class implements ProcessorInterface {
66+
public function __invoke(LogRecord $record): LogRecord
67+
{
68+
$record = $record->with(extra: array_merge($record->extra, ['custom' => true]));
69+
return $record;
70+
}
71+
};
72+
73+
$logger_service = LoggerService::get_instance(
74+
'custom_channel',
75+
[$custom_handler],
76+
[$custom_processor]
77+
);
78+
79+
$monolog = $this->get_monolog_instance($logger_service);
80+
81+
$this->assertEquals('custom_channel', $monolog->getName());
82+
$this->assertCount(1, $monolog->getHandlers());
83+
$this->assertSame($custom_handler, $monolog->getHandlers()[0]);
84+
$this->assertCount(1, $monolog->getProcessors());
85+
}
86+
87+
/**
88+
* Provides log levels and corresponding LoggerService methods.
89+
*/
90+
public function logLevelProvider(): array
91+
{
92+
return [
93+
['debug', 'debug'],
94+
['info', 'info'],
95+
['notice', 'notice'],
96+
['warning', 'warning'],
97+
['error', 'error'],
98+
['critical', 'critical'],
99+
['alert', 'alert'],
100+
['emergency','emergency'],
101+
];
102+
}
103+
104+
/**
105+
* @dataProvider logLevelProvider
106+
*/
107+
public function test_logging_methods_write_to_handler(string $level, string $method): void
108+
{
109+
$test_handler = new TestHandler();
110+
$logger_service = LoggerService::get_instance(
111+
'log_test_channel_' . $level,
112+
[$test_handler],
113+
[],
114+
['default_key' => 'default_value']
115+
);
116+
117+
$message = "Test $level message";
118+
$context = ['foo' => 'bar'];
119+
120+
$logger_service->$method($message, $context);
121+
122+
$checkMethod = 'has' . ucfirst($level) . 'Records';
123+
$this->assertTrue($test_handler->$checkMethod(), "Handler should have $level record.");
124+
125+
$records = $test_handler->getRecords();
126+
$this->assertNotEmpty($records, 'Handler should have at least one record.');
127+
$record = $records[0];
128+
129+
$this->assertEquals($message, $record['message']);
130+
$this->assertArrayHasKey('default_key', $record['context']);
131+
$this->assertEquals('default_value', $record['context']['default_key']);
132+
$this->assertArrayHasKey('foo', $record['context']);
133+
$this->assertEquals('bar', $record['context']['foo']);
134+
}
135+
136+
public function test_log_method_accepts_arbitrary_level(): void
137+
{
138+
$test_handler = new TestHandler();
139+
$logger_service = LoggerService::get_instance(
140+
'arbitrary_level',
141+
[$test_handler],
142+
[],
143+
['default_key' => 'default_value']
144+
);
145+
146+
$logger_service->log('notice', 'Arbitrary level message', ['baz' => 'qux']);
147+
148+
$this->assertTrue($test_handler->hasNoticeRecords());
149+
$records = $test_handler->getRecords();
150+
$this->assertEquals('Arbitrary level message', $records[0]['message']);
151+
$this->assertEquals('default_value', $records[0]['context']['default_key']);
152+
$this->assertEquals('qux', $records[0]['context']['baz']);
153+
}
154+
}

0 commit comments

Comments
 (0)