Skip to content

Commit 05adecd

Browse files
committed
feat(logger): Implement custom logger injection for Typesense client
1 parent a5da1d9 commit 05adecd

3 files changed

Lines changed: 276 additions & 3 deletions

File tree

examples/custom_logger.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
include '../vendor/autoload.php';
4+
5+
use Monolog\Logger;
6+
use Monolog\Handler\StreamHandler;
7+
use Symfony\Component\HttpClient\HttplugClient;
8+
use Typesense\Client;
9+
10+
/**
11+
* Example: Using a custom logger with Typesense
12+
*
13+
* This example demonstrates how to inject your own logger instance
14+
* instead of using the default Monolog logger.
15+
*/
16+
17+
try {
18+
echo '<pre>';
19+
20+
// Create your custom logger instance
21+
$customLogger = new Logger('my-custom-logger');
22+
$customLogger->pushHandler(new StreamHandler('/tmp/typesense-custom.log', Logger::DEBUG));
23+
24+
// You can also use any other PSR-3 compatible logger
25+
// For example: Symfony's Logger, Laravel's Logger, etc.
26+
27+
echo "--------Example 1: Client with Custom Logger-------\n";
28+
// Initialize Typesense client with custom logger
29+
$client = new Client(
30+
[
31+
'api_key' => 'xyz',
32+
'nodes' => [
33+
[
34+
'host' => 'localhost',
35+
'port' => '8108',
36+
'protocol' => 'http',
37+
],
38+
],
39+
'client' => new HttplugClient(),
40+
'logger' => $customLogger, // Inject your custom logger here
41+
]
42+
);
43+
44+
// Use the client - all logs will now use your custom logger
45+
$health = $client->health->retrieve();
46+
print_r($health);
47+
echo "✓ Using custom logger - logs written to /tmp/typesense-custom.log\n";
48+
49+
echo "\n--------Example 2: Client with Default Logger-------\n";
50+
// Example without custom logger (uses default):
51+
$clientWithDefaultLogger = new Client(
52+
[
53+
'api_key' => 'xyz',
54+
'nodes' => [
55+
[
56+
'host' => 'localhost',
57+
'port' => '8108',
58+
'protocol' => 'http',
59+
],
60+
],
61+
'client' => new HttplugClient(),
62+
'log_level' => Logger::INFO, // You can customize the log level
63+
]
64+
);
65+
66+
$health2 = $clientWithDefaultLogger->health->retrieve();
67+
print_r($health2);
68+
echo "✓ Using default logger - logs written to stdout\n";
69+
70+
} catch (Exception $e) {
71+
echo $e->getMessage();
72+
}
73+

src/Lib/Configuration.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,14 @@ public function __construct(array $config)
110110
$this->numRetries = (float)($config['num_retries'] ?? 3);
111111
$this->retryIntervalSeconds = (float)($config['retry_interval_seconds'] ?? 1.0);
112112

113-
$this->logLevel = $config['log_level'] ?? Logger::WARNING;
114-
$this->logger = new Logger('typesense');
115-
$this->logger->pushHandler(new StreamHandler('php://stdout', $this->logLevel));
113+
// Allow custom logger injection
114+
if (isset($config['logger']) && $config['logger'] instanceof LoggerInterface) {
115+
$this->logger = $config['logger'];
116+
} else {
117+
$this->logLevel = $config['log_level'] ?? Logger::WARNING;
118+
$this->logger = new Logger('typesense');
119+
$this->logger->pushHandler(new StreamHandler('php://stdout', $this->logLevel));
120+
}
116121

117122
if (isset($config['client'])) {
118123
if ($config['client'] instanceof HttpMethodsClient || $config['client'] instanceof ClientInterface) {
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php
2+
3+
namespace Feature;
4+
5+
use Monolog\Handler\StreamHandler;
6+
use Monolog\Logger;
7+
use PHPUnit\Framework\TestCase;
8+
use Psr\Log\LoggerInterface;
9+
use Typesense\Exceptions\ConfigError;
10+
use Typesense\Lib\Configuration;
11+
12+
class ConfigurationTest extends TestCase
13+
{
14+
private array $baseConfig;
15+
16+
protected function setUp(): void
17+
{
18+
$this->baseConfig = [
19+
'api_key' => 'test_api_key',
20+
'nodes' => [
21+
[
22+
'host' => 'localhost',
23+
'port' => '8108',
24+
'protocol' => 'http',
25+
],
26+
],
27+
];
28+
}
29+
30+
public function testConfigurationWithDefaultLogger(): void
31+
{
32+
$config = new Configuration($this->baseConfig);
33+
34+
$logger = $config->getLogger();
35+
36+
$this->assertInstanceOf(LoggerInterface::class, $logger);
37+
$this->assertInstanceOf(Logger::class, $logger);
38+
}
39+
40+
public function testConfigurationWithCustomLogger(): void
41+
{
42+
// Create a custom logger
43+
$customLogger = new Logger('custom-test-logger');
44+
$customLogger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
45+
46+
// Add custom logger to config
47+
$configWithCustomLogger = array_merge($this->baseConfig, [
48+
'logger' => $customLogger,
49+
]);
50+
51+
$config = new Configuration($configWithCustomLogger);
52+
53+
$logger = $config->getLogger();
54+
55+
// Assert that the logger is the same instance we passed
56+
$this->assertInstanceOf(LoggerInterface::class, $logger);
57+
$this->assertSame($customLogger, $logger);
58+
$this->assertEquals('custom-test-logger', $logger->getName());
59+
}
60+
61+
public function testConfigurationWithCustomLogLevel(): void
62+
{
63+
// Add custom log level to config
64+
$configWithLogLevel = array_merge($this->baseConfig, [
65+
'log_level' => Logger::DEBUG,
66+
]);
67+
68+
$config = new Configuration($configWithLogLevel);
69+
70+
$logger = $config->getLogger();
71+
72+
$this->assertInstanceOf(LoggerInterface::class, $logger);
73+
$this->assertInstanceOf(Logger::class, $logger);
74+
}
75+
76+
public function testConfigurationWithCustomLoggerOverridesLogLevel(): void
77+
{
78+
// Create a custom logger
79+
$customLogger = new Logger('custom-logger-with-level');
80+
$customLogger->pushHandler(new StreamHandler('php://stdout', Logger::ERROR));
81+
82+
// Add both custom logger and log level to config (logger should win)
83+
$configWithBoth = array_merge($this->baseConfig, [
84+
'logger' => $customLogger,
85+
'log_level' => Logger::DEBUG, // This should be ignored
86+
]);
87+
88+
$config = new Configuration($configWithBoth);
89+
90+
$logger = $config->getLogger();
91+
92+
// Assert that the custom logger is used, not a new one with log_level
93+
$this->assertSame($customLogger, $logger);
94+
$this->assertEquals('custom-logger-with-level', $logger->getName());
95+
}
96+
97+
public function testConfigurationWithMockLogger(): void
98+
{
99+
// Create a mock logger for testing
100+
$mockLogger = $this->createMock(LoggerInterface::class);
101+
102+
// Add mock logger to config
103+
$configWithMockLogger = array_merge($this->baseConfig, [
104+
'logger' => $mockLogger,
105+
]);
106+
107+
$config = new Configuration($configWithMockLogger);
108+
109+
$logger = $config->getLogger();
110+
111+
// Assert that the logger is the same instance we passed
112+
$this->assertInstanceOf(LoggerInterface::class, $logger);
113+
$this->assertSame($mockLogger, $logger);
114+
}
115+
116+
public function testConfigurationWithInvalidLoggerIgnored(): void
117+
{
118+
// Try to pass a non-logger object (should fall back to default)
119+
$configWithInvalidLogger = array_merge($this->baseConfig, [
120+
'logger' => 'not-a-logger-instance',
121+
]);
122+
123+
$config = new Configuration($configWithInvalidLogger);
124+
125+
$logger = $config->getLogger();
126+
127+
// Should fall back to default logger
128+
$this->assertInstanceOf(LoggerInterface::class, $logger);
129+
$this->assertInstanceOf(Logger::class, $logger);
130+
$this->assertEquals('typesense', $logger->getName());
131+
}
132+
133+
public function testConfigurationThrowsErrorWhenNodesAreMissing(): void
134+
{
135+
$this->expectException(ConfigError::class);
136+
$this->expectExceptionMessage('`nodes` is not defined.');
137+
138+
new Configuration([
139+
'api_key' => 'test_api_key',
140+
]);
141+
}
142+
143+
public function testConfigurationThrowsErrorWhenApiKeyIsMissing(): void
144+
{
145+
$this->expectException(ConfigError::class);
146+
$this->expectExceptionMessage('`api_key` is not defined.');
147+
148+
new Configuration([
149+
'nodes' => [
150+
[
151+
'host' => 'localhost',
152+
'port' => '8108',
153+
'protocol' => 'http',
154+
],
155+
],
156+
]);
157+
}
158+
159+
public function testConfigurationWithAllOptions(): void
160+
{
161+
$customLogger = new Logger('full-config-logger');
162+
$customLogger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
163+
164+
$fullConfig = [
165+
'api_key' => 'test_api_key',
166+
'nodes' => [
167+
[
168+
'host' => 'localhost',
169+
'port' => '8108',
170+
'protocol' => 'http',
171+
'path' => '/api',
172+
],
173+
],
174+
'nearest_node' => [
175+
'host' => 'nearest.example.com',
176+
'port' => '443',
177+
'protocol' => 'https',
178+
],
179+
'logger' => $customLogger,
180+
'num_retries' => 5,
181+
'retry_interval_seconds' => 2.0,
182+
'healthcheck_interval_seconds' => 30,
183+
'randomize_nodes' => false,
184+
];
185+
186+
$config = new Configuration($fullConfig);
187+
188+
$this->assertSame($customLogger, $config->getLogger());
189+
$this->assertEquals(5, $config->getNumRetries());
190+
$this->assertEquals(2.0, $config->getRetryIntervalSeconds());
191+
$this->assertEquals(30, $config->getHealthCheckIntervalSeconds());
192+
$this->assertNotNull($config->getNearestNode());
193+
}
194+
}
195+

0 commit comments

Comments
 (0)