Skip to content

Commit

Permalink
Merge pull request #1102: Expose LoggerChannel attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed Apr 25, 2024
2 parents a8d0c93 + 10f0e8c commit 8a05712
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 23 deletions.
23 changes: 11 additions & 12 deletions src/Bridge/Monolog/phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
stopOnError="false"
stderr="true">
stderr="true"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<testsuites>
<testsuite name="Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>src</directory>
</whitelist>
</filter>
<php>
<ini name="error_reporting" value="-1"/>
<ini name="memory_limit" value="-1"/>
</php>
<source>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
9 changes: 6 additions & 3 deletions src/Bridge/Monolog/src/Bootloader/MonologBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Spiral\Config\Patch\Append;
use Spiral\Core\Attribute\Singleton;
use Spiral\Core\Container;
use Spiral\Logger\Bootloader\LoggerBootloader;
use Spiral\Logger\LogsInterface;
use Spiral\Monolog\Config\MonologConfig;
use Spiral\Monolog\LogFactory;
Expand All @@ -27,13 +28,17 @@ final class MonologBootloader extends Bootloader
{
protected const SINGLETONS = [
LogsInterface::class => LogFactory::class,
LoggerInterface::class => Logger::class,
Logger::class => Logger::class,
];

protected const BINDINGS = [
'log.rotate' => [self::class, 'logRotate'],
];

protected const DEPENDENCIES = [
LoggerBootloader::class,
];

private const DEFAULT_FORMAT = "[%datetime%] %level_name%: %message% %context%\n";

public function __construct(
Expand Down Expand Up @@ -71,8 +76,6 @@ public function init(Container $container, FinalizerInterface $finalizer): void
'globalLevel' => Logger::DEBUG,
'handlers' => [],
]);

$container->bindInjector(Logger::class, LogFactory::class);
}

public function addHandler(string $channel, HandlerInterface $handler): void
Expand Down
9 changes: 6 additions & 3 deletions src/Bridge/Monolog/src/LogFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Spiral\Core\Container\InjectorInterface;
use Spiral\Core\FactoryInterface;
use Spiral\Logger\ListenerRegistryInterface;
use Spiral\Logger\LoggerInjector;
use Spiral\Logger\LogsInterface;
use Spiral\Monolog\Config\MonologConfig;
use Spiral\Monolog\Exception\ConfigException;
Expand All @@ -34,7 +35,7 @@ public function __construct(
$this->eventHandler = new EventHandler($listenerRegistry, $config->getEventLevel());
}

public function getLogger(string $channel = null): LoggerInterface
public function getLogger(?string $channel = null): LoggerInterface
{
$default = $this->config->getDefault();

Expand All @@ -58,9 +59,11 @@ public function getLogger(string $channel = null): LoggerInterface
);
}

public function createInjection(\ReflectionClass $class, string $context = null): LoggerInterface
/**
* @deprecated use {@see LoggerInjector} as an injector instead.
*/
public function createInjection(\ReflectionClass $class, ?string $context = null): LoggerInterface
{
// always return default logger as injection
return $this->getLogger();
}

Expand Down
31 changes: 31 additions & 0 deletions src/Bridge/Monolog/tests/FactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Spiral\Config\ConfiguratorInterface;
use Spiral\Config\LoaderInterface;
use Spiral\Core\Container;
use Spiral\Logger\Attribute\LoggerChannel;
use Spiral\Logger\ListenerRegistry;
use Spiral\Logger\LogsInterface;
use Spiral\Monolog\Bootloader\MonologBootloader;
Expand Down Expand Up @@ -74,9 +75,39 @@ public function load(string $section): array
$this->container->bind(LogFactory::class, $factory);

$this->assertSame($logger, $this->container->get(Logger::class));
$this->assertInstanceOf(LoggerInterface::class, $this->container->get(LoggerInterface::class));
$this->assertSame($logger, $this->container->get(LoggerInterface::class));
}

public function testInjectionWithAttribute(): void
{
$factory = new LogFactory(new MonologConfig([]), new ListenerRegistry(), new Container());

$this->container->bind(ConfiguratorInterface::class, new ConfigManager(
new class() implements LoaderInterface {
public function has(string $section): bool
{
return false;
}

public function load(string $section): array
{
return [];
}
}
));

$this->container->bind(FinalizerInterface::class, $finalizer = \Mockery::mock(FinalizerInterface::class));
$finalizer->shouldReceive('addFinalizer')->once();

$this->container->get(StrategyBasedBootloadManager::class)->bootload([MonologBootloader::class]);
$this->container->bind(LogFactory::class, $factory);

$this->container->invoke(function (#[LoggerChannel('foo')] LoggerInterface $logger) {
$this->assertSame('foo', $logger->getName());
});
}

public function testFinalizerShouldResetDefaultLogger()
{
$this->container->bind(ConfiguratorInterface::class, new ConfigManager(
Expand Down
8 changes: 4 additions & 4 deletions src/Bridge/Monolog/tests/LoggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Spiral\Config\ConfiguratorInterface;
use Spiral\Config\LoaderInterface;
use Spiral\Core\Container;
use Spiral\Logger\LogsInterface;
use Spiral\Monolog\Bootloader\MonologBootloader;
use Spiral\Monolog\LogFactory;

Expand All @@ -39,15 +40,14 @@ public function load(string $section): array
));

$this->container->bind(FinalizerInterface::class, $finalizer = new Finalizer());
$this->container->bind(LogFactory::class, $injector = m::mock(Container\InjectorInterface::class));

$logger = m::mock(Logger::class);
$logger->shouldReceive('reset')->once();

$injector->shouldReceive('createInjection')->once()->andReturn($logger);

$this->container->get(StrategyBasedBootloadManager::class)->bootload([MonologBootloader::class]);
$this->container->get(LoggerInterface::class);

$this->container->bind(LogsInterface::class, $factory = m::mock(LogsInterface::class));
$factory->shouldReceive('getLogger')->once()->andReturn($logger);

$finalizer->finalize();
}
Expand Down
23 changes: 23 additions & 0 deletions src/Logger/src/Attribute/LoggerChannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Spiral\Logger\Attribute;

use Psr\Log\LoggerInterface;

/**
* Used to specify the channel name for the logger when it injected as {@see LoggerInterface} on auto-wiring.
*
* Note: {@see \Spiral\Logger\LoggerInjector} should be registered in the container to support this attribute.
*/
#[\Attribute(\Attribute::TARGET_PARAMETER)]
final class LoggerChannel
{
/**
* @param non-empty-string $name
*/
public function __construct(public readonly string $name)
{
}
}
30 changes: 30 additions & 0 deletions src/Logger/src/Bootloader/LoggerBootloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Spiral\Logger\Bootloader;

use Psr\Log\LoggerInterface;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Core\Container;
use Spiral\Logger\Attribute\LoggerChannel;
use Spiral\Logger\LogFactory;
use Spiral\Logger\LoggerInjector;
use Spiral\Logger\LogsInterface;
use Spiral\Logger\NullLogger;

/**
* Register {@see LoggerInterface} injector with support for {@see LoggerChannel} attribute.
* Register default {@see LogsInterface} implementation that produces {@see NullLogger}.
*/
final class LoggerBootloader extends Bootloader
{
protected const SINGLETONS = [
LogsInterface::class => LogFactory::class,
];

public function init(Container $container): void
{
$container->bindInjector(LoggerInterface::class, LoggerInjector::class);
}
}
62 changes: 62 additions & 0 deletions src/Logger/src/LoggerInjector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Spiral\Logger;

use Psr\Log\LoggerInterface;
use Spiral\Core\Container\InjectorInterface;
use Spiral\Logger\Attribute\LoggerChannel;

/**
* Container injector for {@see LoggerInterface}.
* Supports {@see LoggerChannel} attribute.
*
* @implements InjectorInterface<LoggerInterface>
*/
final class LoggerInjector implements InjectorInterface
{
public const DEFAULT_CHANNEL = 'default';

public function __construct(
private readonly LogsInterface $factory,
) {
}

/**
* @param \ReflectionParameter|string|null $context may use extended context if possible.
*/
public function createInjection(
\ReflectionClass $class,
\ReflectionParameter|null|string $context = null,
): LoggerInterface {
$channel = \is_object($context) ? $this->extractChannelAttribute($context) : null;

if ($channel === null) {
/**
* Array of flags to check if the logger allows null argument
*
* @var array<class-string<LogsInterface>, bool> $cache
*/
static $cache = [];

$cache[$this->factory::class] = (new \ReflectionMethod($this->factory, 'getLogger'))
->getParameters()[0]->allowsNull();

$channel = $cache[$this->factory::class] ? null : self::DEFAULT_CHANNEL;
}

return $this->factory->getLogger($channel);
}

/**
* @return non-empty-string|null
*/
private function extractChannelAttribute(\ReflectionParameter $parameter): ?string
{
/** @var \ReflectionAttribute<LoggerChannel>[] $attributes */
$attributes = $parameter->getAttributes(LoggerChannel::class);

return $attributes[0]?->newInstance()->name;
}
}
2 changes: 1 addition & 1 deletion src/Logger/src/NullLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class NullLogger implements LoggerInterface

public function __construct(
callable $receptor,
private string $channel
private readonly string $channel
) {
$this->receptor = $receptor(...);
}
Expand Down
Loading

0 comments on commit 8a05712

Please sign in to comment.