Skip to content

Commit

Permalink
MonologExtension adapted to Nette3
Browse files Browse the repository at this point in the history
  • Loading branch information
solcik authored and Milan Felix Šulc committed May 30, 2019
1 parent b2461ce commit 53e99b7
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 63 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"require": {
"php": ">=7.1.0",
"monolog/monolog": "^1.23.0",
"nette/di": "~2.4.12",
"nette/utils": "~2.5.2"
"nette/di": "~3.0.0",
"nette/utils": "~3.0.0"
},
"require-dev": {
"ninjify/qa": "^0.9.0",
Expand Down
1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ includes:

parameters:
ignoreErrors:
- '#^Call to deprecated method formatPhp\(\) of class Nette\\DI\\ContainerBuilder\.$#'
- '#^Method Contributte\\Monolog\\Tracy\\LazyTracyLogger\:\:log\(\) has parameter (\$priority|\$value) with no typehint specified\.$#'
93 changes: 39 additions & 54 deletions src/DI/MonologExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
use Contributte\Monolog\Tracy\LazyTracyLogger;
use Monolog\Handler\PsrHandler;
use Monolog\Logger;
use Nette\DI\Compiler;
use Nette\DI\CompilerExtension;
use Nette\DI\Statement;
use Nette\DI\Definitions\Statement;
use Nette\PhpGenerator\ClassType;
use Nette\Schema\Expect;
use Nette\Schema\Schema;
use Nette\Utils\Strings;
use Tracy\Bridges\Psr\PsrToTracyLoggerAdapter;
use Tracy\Bridges\Psr\TracyToPsrLoggerAdapter;
Expand All @@ -21,30 +22,29 @@
class MonologExtension extends CompilerExtension
{

/** @var mixed[] */
private $defaults = [
'channel' => [],
'hook' => [
'fromTracy' => true, // log through Tracy
'toTracy' => true, // log through Monolog
],
'holder' => [
'enabled' => false,
],
'manager' => [
'enabled' => false,
],
];

/** @var mixed [] */
private $channelDefaults = [
'handlers' => [],
'processors' => [],
];
public function getConfigSchema(): Schema
{
return Expect::structure([
'channel' => Expect::arrayOf(Expect::structure([
'handlers' => Expect::array()->required()->min(1),
'processors' => Expect::array()->default([]),
])->castTo('array'))->required()->min(1),
'hook' => Expect::structure([
'fromTracy' => Expect::bool()->default(true),
'toTracy' => Expect::bool()->default(true),
])->castTo('array'),
'holder' => Expect::structure([
'enabled' => Expect::bool()->default(false),
])->castTo('array'),
'manager' => Expect::structure([
'enabled' => Expect::bool()->default(false),
])->castTo('array'),
])->castTo('array');
}

public function loadConfiguration(): void
{
$config = $this->validateConfig($this->defaults);
$config = (array) $this->getConfig();
$builder = $this->getContainerBuilder();

if (!isset($config['channel']['default'])) {
Expand All @@ -61,16 +61,11 @@ public function loadConfiguration(): void
$tracyHandler = null;

if (class_exists(Debugger::class) && $config['hook']['toTracy'] && $builder->hasDefinition('tracy.logger')) {
$tracyAdapter = new Statement(TracyToPsrLoggerAdapter::class);
$tracyAdapter->arguments = ['@tracy.logger'];

$tracyHandler = new Statement(PsrHandler::class);
$tracyHandler->arguments = [$tracyAdapter];
$tracyAdapter = new Statement(TracyToPsrLoggerAdapter::class, ['@tracy.logger']);
$tracyHandler = new Statement(PsrHandler::class, [$tracyAdapter]);
}

foreach ($config['channel'] as $name => $channel) {
$channel = $this->validateConfig($this->channelDefaults, $channel, $this->prefix('channel.' . $name));

if (!is_string($name)) {
throw new InvalidArgumentException(sprintf('%s.channel.%s name must be a string', $this->name, (string) $name));
}
Expand All @@ -79,10 +74,6 @@ public function loadConfiguration(): void
$channel['handlers']['tracy'] = $tracyHandler;
}

if (!isset($channel['handlers']) || $channel['handlers'] === []) {
throw new InvalidStateException(sprintf('%s.channel.%s.handlers must contain at least one handler', $this->name, $name));
}

// Register handlers same way as services (setup, arguments, type etc.)
foreach ($channel['handlers'] as $handlerKey => $handlerValue) {
// Don't register handler as service, it's already registered service
Expand All @@ -91,35 +82,29 @@ public function loadConfiguration(): void
}

$handlerName = $this->prefix('logger.' . $name . '.handler.' . $handlerKey);
$handler = $builder->addDefinition($handlerName)
->setAutowired(false);

Compiler::loadDefinition($handler, $handlerValue);
$this->compiler->loadDefinitionsFromConfig([$handlerName => $handlerValue]);
$builder->getDefinition($handlerName)->setAutowired(false);
$channel['handlers'][$handlerKey] = '@' . $handlerName;
}

// Register processors same way as services (setup, arguments, type etc.)
if (isset($channel['processors'])) {
foreach ($channel['processors'] as $processorKey => $processorValue) {
// Don't register processor as service, it's already registered service
if (is_string($processorValue) && Strings::startsWith($processorValue, '@')) {
continue;
}

$processorName = $this->prefix('logger.' . $name . '.processor.' . $processorKey);
$processor = $builder->addDefinition($processorName)
->setAutowired(false);

Compiler::loadDefinition($processor, $processorValue);
$channel['processors'][$processorKey] = '@' . $processorName;
foreach ($channel['processors'] as $processorKey => $processorValue) {
// Don't register processor as service, it's already registered service
if (is_string($processorValue) && Strings::startsWith($processorValue, '@')) {
continue;
}

$processorName = $this->prefix('logger.' . $name . '.processor.' . $processorKey);
$this->compiler->loadDefinitionsFromConfig([$processorName => $processorValue]);
$builder->getDefinition($processorName)->setAutowired(false);
$channel['processors'][$processorKey] = '@' . $processorName;
}

$logger = $builder->addDefinition($this->prefix('logger.' . $name))
->setFactory(Logger::class, [
$name,
$channel['handlers'],
$channel['processors'] ?? [],
$channel['processors'],
]);

// Only default logger is autowired
Expand All @@ -144,12 +129,12 @@ public function loadConfiguration(): void
public function afterCompile(ClassType $class): void
{
$builder = $this->getContainerBuilder();
$config = $this->validateConfig($this->defaults);
$config = (array) $this->getConfig();
$initialize = $class->getMethod('initialize');

if (class_exists(Debugger::class) && $config['hook']['fromTracy'] && $builder->hasDefinition('tracy.logger')) {
$initialize->addBody('$this->getService("tracy.logger");'); // Create original Tracy\Logger service to prevent psrToTracyLazyAdapter contain itself - workaround for Tracy\ILogger service created statically
$initialize->addBody($builder->formatPhp(Debugger::class . '::setLogger(?);', [$this->prefix('@psrToTracyLazyAdapter')]));
$initialize->addBody(Debugger::class . '::setLogger($this->getService(?));', [$this->prefix('psrToTracyLazyAdapter')]);
}

if ($config['holder']['enabled']) {
Expand Down
38 changes: 32 additions & 6 deletions tests/Unit/DI/MonologExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
namespace Tests\Contributte\Monolog\Unit\DI;

use Contributte\Monolog\DI\MonologExtension;
use Contributte\Monolog\Exception\Logic\InvalidStateException;
use Contributte\Monolog\LoggerHolder;
use Contributte\Monolog\LoggerManager;
use Contributte\Monolog\Tracy\LazyTracyLogger;
use Monolog\Logger;
use Nette\DI\Compiler;
use Nette\DI\Container;
use Nette\DI\ContainerLoader;
use Nette\DI\InvalidConfigurationException;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Tracy\Bridges\Nette\TracyExtension;
Expand All @@ -19,9 +19,11 @@
class MonologExtensionTest extends TestCase
{

private const FIXTURES_DIR = __DIR__ . '/../../fixtures';

public function testRegistration(): void
{
$container = $this->createContainer(__DIR__ . '/config.neon');
$container = $this->createContainer(self::FIXTURES_DIR . '/config.neon');

// Needed for LoggerHolder and creation of original Tracy\Logger
$container->initialize();
Expand Down Expand Up @@ -54,16 +56,40 @@ public function testRegistration(): void

public function testRegistrationNoDefault(): void
{
$this->expectException(InvalidStateException::class);
$this->expectExceptionMessage('monolog.channel.default is required.');
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('The mandatory option \'monolog › channel\' is missing.');

$container = $this->createContainer(self::FIXTURES_DIR . '/config_00.neon');
}

public function testRegistrationEmptyChannels(): void
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('The option \'monolog › channel\' expects to be array in range 1.., array given.');

$container = $this->createContainer(self::FIXTURES_DIR . '/config_01.neon');
}

public function testRegistrationEmptyChannel(): void
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('The mandatory option \'monolog › channel › default › handlers\' is missing.');

$container = $this->createContainer(self::FIXTURES_DIR . '/config_02.neon');
}

public function testRegistrationEmptyHandlers(): void
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('The option \'monolog › channel › default › handlers\' expects to be array in range 1.., array given.');

$container = $this->createContainer(__DIR__ . '/empty.neon');
$container = $this->createContainer(self::FIXTURES_DIR . '/config_03.neon');
}

private function createContainer(string $configFile): Container
{
$loader = new ContainerLoader(__DIR__ . '/../../../temp/tests/' . getmypid(), true);
$class = $loader->load(function (Compiler $compiler) use ($configFile): void {
$class = $loader->load(static function (Compiler $compiler) use ($configFile): void {
$compiler->loadConfig($configFile);
$compiler->addExtension('tracy', new TracyExtension());
$compiler->addExtension('monolog', new MonologExtension());
Expand Down
Empty file removed tests/Unit/DI/empty.neon
Empty file.
2 changes: 2 additions & 0 deletions tests/Unit/DI/config.neon → tests/fixtures/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ monolog:
default:
handlers:
- Monolog\Handler\NullHandler
processors:
- Monolog\Processor\MemoryPeakUsageProcessor
foo:
handlers:
- Monolog\Handler\NullHandler
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/config_00.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
monolog:
2 changes: 2 additions & 0 deletions tests/fixtures/config_01.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
monolog:
channel:
3 changes: 3 additions & 0 deletions tests/fixtures/config_02.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
monolog:
channel:
default:
4 changes: 4 additions & 0 deletions tests/fixtures/config_03.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
monolog:
channel:
default:
handlers:

0 comments on commit 53e99b7

Please sign in to comment.