Skip to content

Commit e8dacf3

Browse files
jkobusmarkuspoerschketharropoulos
authored
feat(logger): Implement custom logger injection for Typesense client (#113)
* feat(logger): Implement custom logger injection for Typesense client * feat(logger): Implement custom logger injection for Typesense client * feat(logger): Implement custom logger injection for Typesense client * Update src/Lib/Configuration.php Co-authored-by: Markus Poerschke <markus@poerschke.nrw> * fix(test): fix error assertion on invalid logger based on 510c07d --------- Co-authored-by: Markus Poerschke <markus@poerschke.nrw> Co-authored-by: Fanis Tharropoulos <ftharropoulos@gmail.com>
1 parent 6f61049 commit e8dacf3

3 files changed

Lines changed: 249 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: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,22 @@ 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'])) {
115+
if (!$config['logger'] instanceof LoggerInterface) {
116+
throw new ConfigError('Logger must implement Psr\Log\LoggerInterface');
117+
}
118+
119+
if (isset($config['log_level'])) {
120+
throw new \InvalidArgumentException('Setting log_level is not allowed when a custom logger is provided.');
121+
}
122+
123+
$this->logger = $config['logger'];
124+
} else {
125+
$this->logLevel = $config['log_level'] ?? Logger::WARNING;
126+
$this->logger = new Logger('typesense');
127+
$this->logger->pushHandler(new StreamHandler('php://stdout', $this->logLevel));
128+
}
116129

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

0 commit comments

Comments
 (0)