LoggerChannelPass.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\DependencyInjection\Argument\BoundArgument;
  13. use Symfony\Component\DependencyInjection\ChildDefinition;
  14. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  15. use Symfony\Component\DependencyInjection\ContainerBuilder;
  16. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  17. use Symfony\Component\DependencyInjection\Reference;
  18. /**
  19. * Replaces the default logger by another one with its own channel for tagged services.
  20. *
  21. * @author Christophe Coevoet <stof@notk.org>
  22. */
  23. class LoggerChannelPass implements CompilerPassInterface
  24. {
  25. protected $channels = ['app'];
  26. /**
  27. * {@inheritDoc}
  28. */
  29. public function process(ContainerBuilder $container)
  30. {
  31. if (!$container->hasDefinition('monolog.logger')) {
  32. return;
  33. }
  34. // create channels necessary for the handlers
  35. foreach ($container->findTaggedServiceIds('monolog.logger') as $id => $tags) {
  36. foreach ($tags as $tag) {
  37. if (empty($tag['channel']) || 'app' === $tag['channel']) {
  38. continue;
  39. }
  40. $resolvedChannel = $container->getParameterBag()->resolveValue($tag['channel']);
  41. $definition = $container->getDefinition($id);
  42. $loggerId = sprintf('monolog.logger.%s', $resolvedChannel);
  43. $this->createLogger($resolvedChannel, $loggerId, $container);
  44. foreach ($definition->getArguments() as $index => $argument) {
  45. if ($argument instanceof Reference && 'logger' === (string) $argument) {
  46. $definition->replaceArgument($index, $this->changeReference($argument, $loggerId));
  47. }
  48. }
  49. $calls = $definition->getMethodCalls();
  50. foreach ($calls as $i => $call) {
  51. foreach ($call[1] as $index => $argument) {
  52. if ($argument instanceof Reference && 'logger' === (string) $argument) {
  53. $calls[$i][1][$index] = $this->changeReference($argument, $loggerId);
  54. }
  55. }
  56. }
  57. $definition->setMethodCalls($calls);
  58. if (\method_exists($definition, 'getBindings')) {
  59. $binding = new BoundArgument(new Reference($loggerId));
  60. // Mark the binding as used already, to avoid reporting it as unused if the service does not use a
  61. // logger injected through the LoggerInterface alias.
  62. $values = $binding->getValues();
  63. $values[2] = true;
  64. $binding->setValues($values);
  65. $bindings = $definition->getBindings();
  66. $bindings['Psr\Log\LoggerInterface'] = $binding;
  67. $definition->setBindings($bindings);
  68. }
  69. }
  70. }
  71. // create additional channels
  72. foreach ($container->getParameter('monolog.additional_channels') as $chan) {
  73. if ($chan === 'app') {
  74. continue;
  75. }
  76. $loggerId = sprintf('monolog.logger.%s', $chan);
  77. $this->createLogger($chan, $loggerId, $container);
  78. $container->getDefinition($loggerId)->setPublic(true);
  79. }
  80. $container->getParameterBag()->remove('monolog.additional_channels');
  81. // wire handlers to channels
  82. $handlersToChannels = $container->getParameter('monolog.handlers_to_channels');
  83. foreach ($handlersToChannels as $handler => $channels) {
  84. foreach ($this->processChannels($channels) as $channel) {
  85. try {
  86. $logger = $container->getDefinition($channel === 'app' ? 'monolog.logger' : 'monolog.logger.'.$channel);
  87. } catch (InvalidArgumentException $e) {
  88. $msg = 'Monolog configuration error: The logging channel "'.$channel.'" assigned to the "'.substr($handler, 16).'" handler does not exist.';
  89. throw new \InvalidArgumentException($msg, 0, $e);
  90. }
  91. $logger->addMethodCall('pushHandler', [new Reference($handler)]);
  92. }
  93. }
  94. }
  95. /**
  96. * @return array
  97. */
  98. public function getChannels()
  99. {
  100. return $this->channels;
  101. }
  102. /**
  103. * @param array $configuration
  104. *
  105. * @return array
  106. */
  107. protected function processChannels($configuration)
  108. {
  109. if (null === $configuration) {
  110. return $this->channels;
  111. }
  112. if ('inclusive' === $configuration['type']) {
  113. return $configuration['elements'] ?: $this->channels;
  114. }
  115. return array_diff($this->channels, $configuration['elements']);
  116. }
  117. /**
  118. * Create new logger from the monolog.logger_prototype
  119. *
  120. * @param string $channel
  121. * @param string $loggerId
  122. * @param ContainerBuilder $container
  123. */
  124. protected function createLogger($channel, $loggerId, ContainerBuilder $container)
  125. {
  126. if (!in_array($channel, $this->channels)) {
  127. $logger = new ChildDefinition('monolog.logger_prototype');
  128. $logger->replaceArgument(0, $channel);
  129. $container->setDefinition($loggerId, $logger);
  130. $this->channels[] = $channel;
  131. }
  132. // Allows only for Symfony 4.2+
  133. if (\method_exists($container, 'registerAliasForArgument')) {
  134. $parameterName = $channel . 'Logger';
  135. $container->registerAliasForArgument($loggerId, LoggerInterface::class, $parameterName);
  136. }
  137. }
  138. /**
  139. * Creates a copy of a reference and alters the service ID.
  140. *
  141. * @param Reference $reference
  142. * @param string $serviceId
  143. *
  144. * @return Reference
  145. */
  146. private function changeReference(Reference $reference, $serviceId)
  147. {
  148. if (method_exists($reference, 'isStrict')) {
  149. // Stay compatible with Symfony 2
  150. return new Reference($serviceId, $reference->getInvalidBehavior(), $reference->isStrict(false));
  151. }
  152. return new Reference($serviceId, $reference->getInvalidBehavior());
  153. }
  154. }