Skip to content

Commit

Permalink
[FEATURE] Support PHP 8 style Channel attribute for logger injection
Browse files Browse the repository at this point in the history
Services are now able to control the component name that an
injected logger is created with.
This allows to group logs of related classes and is basically
a channel system as often used in monolog.

Therefore and as preparation for future monolog logging,
the attribute is named Channel.

The Channel attribute is supported for constructor argument
injection as a class and parameter specific attribute and for
LoggerAwareInterface dependency injection services as a class attribute.

This feature is only available with PHP 8.
The channel attribute will be gracefully ignored in PHP 7,
and the classic component name will be used instead.

Releases: master
Resolves: #95079
Related: #95044
Change-Id: I4b710677bc20c4200fc14b299865f4c7f95e4d0d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70849
Tested-by: Nikita Hovratov <nikita.h@live.de>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Nikita Hovratov <nikita.h@live.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
  • Loading branch information
bnf committed Sep 3, 2021
1 parent 646cd9b commit 8e0cdb4
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 2 deletions.
Expand Up @@ -21,6 +21,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use TYPO3\CMS\Core\Log\Channel;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogManager;

Expand Down Expand Up @@ -53,12 +54,37 @@ public function process(ContainerBuilder $container)
continue;
}

$channel = $id;
if ($definition->getClass()) {
$reflectionClass = $container->getReflectionClass($definition->getClass(), false);
if ($reflectionClass) {
$channel = $this->getClassChannelName($reflectionClass) ?? $channel;
}
}

$logger = new Definition(Logger::class);
$logger->setFactory([new Reference(LogManager::class), 'getLogger']);
$logger->setArguments([$id]);
$logger->setArguments([$channel]);
$logger->setShared(false);

$definition->addMethodCall('setLogger', [$logger]);
}
}

protected function getClassChannelName(\ReflectionClass $class): ?string
{
// Attribute channel definition is only supported on PHP 8 and later.
if (class_exists('\ReflectionAttribute', false)) {
$attributes = $class->getAttributes(Channel::class, \ReflectionAttribute::IS_INSTANCEOF);
foreach ($attributes as $channel) {
return $channel->newInstance()->name;
}
}

if ($class->getParentClass() !== false) {
return $this->getClassChannelName($class->getParentClass());
}

return null;
}
}
Expand Up @@ -21,6 +21,7 @@
use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use TYPO3\CMS\Core\Log\Channel;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogManager;

Expand Down Expand Up @@ -67,14 +68,46 @@ protected function processValue($value, $isRoot = false)
continue;
}

$channel = $this->getParameterChannelName($parameter) ?? $this->getClassChannelName($reflectionClass) ?? $value->getClass();

$logger = new Definition(Logger::class);
$logger->setFactory([new Reference(LogManager::class), 'getLogger']);
$logger->setArguments([$value->getClass()]);
$logger->setArguments([$channel]);
$logger->setShared(false);

$value->setArgument($name, $logger);
}

return $value;
}

protected function getParameterChannelName(\ReflectionParameter $parameter): ?string
{
// Attribute channel definition is only supported on PHP 8 and later.
if (class_exists('\ReflectionAttribute', false)) {
$attributes = $parameter->getAttributes(Channel::class, \ReflectionAttribute::IS_INSTANCEOF);
foreach ($attributes as $channel) {
return $channel->newInstance()->name;
}
}

return null;
}

protected function getClassChannelName(\ReflectionClass $class): ?string
{
// Attribute channel definition is only supported on PHP 8 and later.
if (class_exists('\ReflectionAttribute', false)) {
$attributes = $class->getAttributes(Channel::class, \ReflectionAttribute::IS_INSTANCEOF);
foreach ($attributes as $channel) {
return $channel->newInstance()->name;
}
}

if ($class->getParentClass() !== false) {
return $this->getClassChannelName($class->getParentClass());
}

return null;
}
}
34 changes: 34 additions & 0 deletions typo3/sysext/core/Classes/Log/Channel.php
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\CMS\Core\Log;

use Attribute;

/**
* Attribute for Logger channel declarations
*/
#[Attribute(Attribute::TARGET_PARAMETER|Attribute::TARGET_CLASS)]
class Channel
{
public string $name;

public function __construct(string $name)
{
$this->name = $name;
}
}
@@ -0,0 +1,83 @@
.. include:: ../../Includes.txt

============================================================================
Feature: #95079 - Support PHP 8 style Channel attribute for logger injection
============================================================================

See :issue:`95079`

Description
===========

Services are now able to control the component name that an
injected logger is created with.
This allows to group logs of related classes and is basically
a channel system as often used in monolog.

The :php:`TYPO3\CMS\Core\Log\Channel` attribute is supported for constructor
argument injection as a class and parameter specific attribute and for
:php:`LoggerAwareInterface` dependency injection services as a class attribute.

This feature is only available with PHP 8.
The channel attribute will be gracefully ignored in PHP 7,
and the classic component name will be used instead.


Registration via class attribute for :php:`LoggerInterface` injection:

.. code-block:: php
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Log\Channel;
#[Channel('security')]
class MyClass
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
// do your magic
}
}
Registration via parameter attribute for :php:`LoggerInterface` injection,
overwrites possible class attributes:

.. code-block:: php
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Log\Channel;
class MyClass
{
private LoggerInterface $logger;
public function __construct(
#[Channel('security')]
LoggerInterface $logger
) {
$this->logger = $logger;
// do your magic
}
}
Registration via class attribute for :php:`LoggerAwareInterface` services.

.. code-block:: php
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Log\Channel;
#[Channel('security')]
class MyClass implements LoggerAwareInterface
{
use LoggerAwareTrait;
}
Impact
======

It is now possible to group several classes into channels, regardless of the
PHP namespace.

.. index:: PHP-API, ext:core

0 comments on commit 8e0cdb4

Please sign in to comment.