Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions examples/custom_logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

include '../vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Symfony\Component\HttpClient\HttplugClient;
use Typesense\Client;

/**
* Example: Using a custom logger with Typesense
*
* This example demonstrates how to inject your own logger instance
* instead of using the default Monolog logger.
*/

try {
echo '<pre>';

// Create your custom logger instance
$customLogger = new Logger('my-custom-logger');
$customLogger->pushHandler(new StreamHandler('/tmp/typesense-custom.log', Logger::DEBUG));

// You can also use any other PSR-3 compatible logger
// For example: Symfony's Logger, Laravel's Logger, etc.

echo "--------Example 1: Client with Custom Logger-------\n";
// Initialize Typesense client with custom logger
$client = new Client(
[
'api_key' => 'xyz',
'nodes' => [
[
'host' => 'localhost',
'port' => '8108',
'protocol' => 'http',
],
],
'client' => new HttplugClient(),
'logger' => $customLogger, // Inject your custom logger here
]
);

// Use the client - all logs will now use your custom logger
$health = $client->health->retrieve();
print_r($health);
echo "✓ Using custom logger - logs written to /tmp/typesense-custom.log\n";

echo "\n--------Example 2: Client with Default Logger-------\n";
// Example without custom logger (uses default):
$clientWithDefaultLogger = new Client(
[
'api_key' => 'xyz',
'nodes' => [
[
'host' => 'localhost',
'port' => '8108',
'protocol' => 'http',
],
],
'client' => new HttplugClient(),
'log_level' => Logger::INFO, // You can customize the log level
]
);

$health2 = $clientWithDefaultLogger->health->retrieve();
print_r($health2);
echo "✓ Using default logger - logs written to stdout\n";

} catch (Exception $e) {
echo $e->getMessage();
}

19 changes: 16 additions & 3 deletions src/Lib/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,22 @@ public function __construct(array $config)
$this->numRetries = (float)($config['num_retries'] ?? 3);
$this->retryIntervalSeconds = (float)($config['retry_interval_seconds'] ?? 1.0);

$this->logLevel = $config['log_level'] ?? Logger::WARNING;
$this->logger = new Logger('typesense');
$this->logger->pushHandler(new StreamHandler('php://stdout', $this->logLevel));
// Allow custom logger injection
if (isset($config['logger'])) {
if (!$config['logger'] instanceof LoggerInterface) {
throw new ConfigError('Logger must implement Psr\Log\LoggerInterface');
}

if (isset($config['log_level'])) {
throw new \InvalidArgumentException('Setting log_level is not allowed when a custom logger is provided.');
}

$this->logger = $config['logger'];
} else {
$this->logLevel = $config['log_level'] ?? Logger::WARNING;
$this->logger = new Logger('typesense');
$this->logger->pushHandler(new StreamHandler('php://stdout', $this->logLevel));
}

if (isset($config['client'])) {
if ($config['client'] instanceof HttpMethodsClient || $config['client'] instanceof ClientInterface) {
Expand Down
161 changes: 161 additions & 0 deletions tests/Feature/ConfigurationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

namespace Feature;

use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use PHPUnit\Framework\TestCase;
use Typesense\Exceptions\ConfigError;
use Typesense\Lib\Configuration;

class ConfigurationTest extends TestCase
{
private array $baseConfig;

protected function setUp(): void
{
$this->baseConfig = [
'api_key' => 'test_api_key',
'nodes' => [
[
'host' => 'localhost',
'port' => '8108',
'protocol' => 'http',
],
],
];
}

public function testConfigurationWithDefaultLogger(): void
{
$config = new Configuration($this->baseConfig);

$logger = $config->getLogger();

$this->assertInstanceOf(Logger::class, $logger);
}

public function testConfigurationWithCustomLogger(): void
{
// Create a custom logger
$customLogger = new Logger('custom-test-logger');
$customLogger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));

// Add custom logger to config
$configWithCustomLogger = array_merge($this->baseConfig, [
'logger' => $customLogger,
]);

$config = new Configuration($configWithCustomLogger);

$logger = $config->getLogger();

// Assert that the logger is the same instance we passed
$this->assertSame($customLogger, $logger);
}

public function testConfigurationWithCustomLogLevel(): void
{
// Add custom log level to config
$configWithLogLevel = array_merge($this->baseConfig, [
'log_level' => Logger::DEBUG,
]);

$config = new Configuration($configWithLogLevel);

$logger = $config->getLogger();
$this->assertInstanceOf(Logger::class, $logger);
}

public function testConfigurationWithCustomLoggerThrowsExceptionWhenLogLevelIsAlsoProvided(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Setting log_level is not allowed when a custom logger is provided.');

$customLogger = new Logger('custom-logger-with-level');
$customLogger->pushHandler(new StreamHandler('php://stdout', Logger::ERROR));

$configWithBoth = array_merge($this->baseConfig, [
'logger' => $customLogger,
'log_level' => Logger::DEBUG,
]);

new Configuration($configWithBoth);
}

public function testConfigurationWithInvalidLoggerThrowsException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Logger must implement Psr\Log\LoggerInterface');

// Try to pass a non-logger object (should throw exception)
$configWithInvalidLogger = array_merge($this->baseConfig, [
'logger' => 'not-a-logger-instance',
]);

new Configuration($configWithInvalidLogger);
}

public function testConfigurationThrowsErrorWhenNodesAreMissing(): void
{
$this->expectException(ConfigError::class);
$this->expectExceptionMessage('`nodes` is not defined.');

new Configuration([
'api_key' => 'test_api_key',
]);
}

public function testConfigurationThrowsErrorWhenApiKeyIsMissing(): void
{
$this->expectException(ConfigError::class);
$this->expectExceptionMessage('`api_key` is not defined.');

new Configuration([
'nodes' => [
[
'host' => 'localhost',
'port' => '8108',
'protocol' => 'http',
],
],
]);
}

public function testConfigurationWithAllOptions(): void
{
$customLogger = new Logger('full-config-logger');
$customLogger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));

$fullConfig = [
'api_key' => 'test_api_key',
'nodes' => [
[
'host' => 'localhost',
'port' => '8108',
'protocol' => 'http',
'path' => '/api',
],
],
'nearest_node' => [
'host' => 'nearest.example.com',
'port' => '443',
'protocol' => 'https',
],
'logger' => $customLogger,
'num_retries' => 5,
'retry_interval_seconds' => 2.0,
'healthcheck_interval_seconds' => 30,
'randomize_nodes' => false,
];

$config = new Configuration($fullConfig);

$this->assertSame($customLogger, $config->getLogger());
$this->assertEquals(5, $config->getNumRetries());
$this->assertEquals(2.0, $config->getRetryIntervalSeconds());
$this->assertEquals(30, $config->getHealthCheckIntervalSeconds());
$this->assertNotNull($config->getNearestNode());
}
}