├── CHANGELOG.md ├── DependencyInjection ├── Compiler │ ├── AddProcessorsPass.php │ ├── AddSwiftMailerTransportPass.php │ ├── DebugHandlerPass.php │ ├── FixEmptyLoggerPass.php │ └── LoggerChannelPass.php ├── Configuration.php └── MonologExtension.php ├── LICENSE ├── MonologBundle.php ├── README.md ├── Resources └── config │ ├── monolog.xml │ └── schema │ └── monolog-1.0.xsd ├── SwiftMailer └── MessageFactory.php └── composer.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | ## 3.10.0 (2023-11-06) 4 | 5 | * Add configuration support for SamplingHandler 6 | 7 | ## 3.9.0 (2023-11-06) 8 | 9 | * Add support for the `WithMonologChannel` attribute of Monolog 3.5.0 to autoconfigure the `monolog.logger` tag 10 | * Add support for Symfony 7 11 | * Remove support for Symfony 4 12 | * Mark classes as internal when relevant 13 | * Add support for env placeholders in the `level` option of handlers 14 | 15 | ## 3.8.0 (2022-05-10) 16 | 17 | * Deprecated ambiguous `elasticsearch` type, use `elastica` instead 18 | * Added support for Monolog 3.0 (requires symfony/monolog-bridge 6.1) 19 | * Added support for `AsMonologProcessor` to autoconfigure processors 20 | * Added support for `FallbackGroupHandler` 21 | * Added support for `ElasticsearchHandler` as `elastic_search` type 22 | * Added support for `ElasticaHandler` as `elastica` type 23 | * Added support for `TelegramBotHandler` as `telegram` 24 | * Added `fill_extra_context` flag for `sentry` handlers 25 | * Added support for configuring PsrLogMessageProcessor (`date_format` and `remove_used_context_fields`) 26 | * Fixed issue on Windows + PHP 8, workaround for https://github.com/php/php-src/issues/8315 27 | * Fixed MongoDBHandler support when no client id is provided 28 | 29 | ## 3.7.1 (2021-11-05) 30 | 31 | * Indicate compatibility with Symfony 6 32 | 33 | ## 3.7.0 (2021-03-31) 34 | 35 | * Use `ActivationStrategy` instead of `actionLevel` when available 36 | * Register resettable processors (`ResettableInterface`) for autoconfiguration (tag: `kernel.reset`) 37 | * Drop support for Symfony 3.4 38 | * Drop support for PHP < 7.1 39 | * Fix call to undefined method pushProcessor on handler that does not implement ProcessableHandlerInterface 40 | * Use "use_locking" option with rotating file handler 41 | * Add ability to specify custom Sentry hub service 42 | 43 | ## 3.6.0 (2020-10-06) 44 | 45 | * Added support for Symfony Mailer 46 | * Added support for setting log levels from parameters or environment variables 47 | 48 | ## 3.5.0 (2019-11-13) 49 | 50 | * Added support for Monolog 2.0 51 | * Added `sentry` type to use sentry 2.0 client 52 | * Added `insightops` handler 53 | * Added possibility for auto-wire monolog channel according to the type-hinted aliases, introduced in the Symfony 4.2 54 | 55 | ## 3.4.0 (2019-06-20) 56 | 57 | * Deprecate "excluded_404s" option 58 | * Flush loggers on `kernel.reset` 59 | * Register processors (`ProcessorInterface`) for autoconfiguration (tag: `monolog.processor`) 60 | * Expose configuration for the `ConsoleHandler` 61 | * Fixed psr-3 processing being applied to all handlers, only leaf ones are now processing 62 | * Fixed regression when `app` channel is defined explicitly 63 | * Fixed handlers marked as nested not being ignored properly from the stack 64 | * Added support for Redis configuration 65 | * Drop support for Symfony <3 66 | 67 | ## 3.3.1 (2018-11-04) 68 | 69 | * Fixed compatiblity with Symfony 4.2 70 | 71 | ## 3.3.0 (2018-06-04) 72 | 73 | * Fixed the autowiring of the channel logger in autoconfigured services 74 | * Added timeouts to the pushover, hipchat, slack handlers 75 | * Dropped support for PHP 5.3, 5.4, and HHVM 76 | * Added configuration for HttpCodeActivationStrategy 77 | * Deprecated "excluded_404s" option for Symfony >= 3.4 78 | 79 | ## 3.2.0 (2018-03-05) 80 | 81 | * Removed randomness from the container build 82 | * Fixed support for the `monolog.logger` tag specifying a channel in combination with Symfony 3.4+ autowiring 83 | * Fixed visibility of channels configured explicitly in the bundle config (they are now public in Symfony 4 too) 84 | * Fixed invalid service definitions 85 | 86 | ## 3.1.2 (2017-11-06) 87 | 88 | * fix invalid usage of count() 89 | 90 | ## 3.1.1 (2017-09-26) 91 | 92 | * added support for Symfony 4 93 | 94 | ## 3.1.0 (2017-03-26) 95 | 96 | * Added support for server_log handler 97 | * Allow configuring VERBOSITY_QUIET in console handlers 98 | * Fixed autowiring 99 | * Fixed slackbot handler not escaping channel names properly 100 | * Fixed slackbot handler requiring `slack_team` instead of `team` to be configured 101 | 102 | ## 3.0.3 (2017-01-10) 103 | 104 | * Fixed deprecation notices when using Symfony 3.3+ and PHP7+ 105 | 106 | ## 3.0.2 (2017-01-03) 107 | 108 | * Revert disabling DebugHandler in CLI environments 109 | * Update configuration for slack handlers for Monolog 1.22 new options 110 | * Revert the removal of the DebugHandlerPass (needed for Symfony <3.2) 111 | 112 | ## 3.0.1 (2016-11-15) 113 | 114 | * Removed obsolete code (DebugHandlerPass) 115 | 116 | ## 3.0.0 (2016-11-06) 117 | 118 | * Removed class parameters for the container configuration 119 | * Bumped minimum version of supported Symfony version to 2.7 120 | * Removed `NotFoundActivationStrategy` (the bundle now uses the class from MonologBridge) 121 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/AddProcessorsPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; 13 | 14 | use Symfony\Component\DependencyInjection\ChildDefinition; 15 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\DependencyInjection\Reference; 18 | 19 | /** 20 | * Registers processors in Monolog loggers or handlers. 21 | * 22 | * @author Christophe Coevoet 23 | * 24 | * @internal since 3.9.0 25 | */ 26 | class AddProcessorsPass implements CompilerPassInterface 27 | { 28 | public function process(ContainerBuilder $container) 29 | { 30 | if (!$container->hasDefinition('monolog.logger')) { 31 | return; 32 | } 33 | 34 | foreach ($container->findTaggedServiceIds('monolog.processor') as $id => $tags) { 35 | foreach ($tags as $tag) { 36 | if (!empty($tag['channel']) && !empty($tag['handler'])) { 37 | throw new \InvalidArgumentException(\sprintf('you cannot specify both the "handler" and "channel" attributes for the "monolog.processor" tag on service "%s"', $id)); 38 | } 39 | 40 | if (!empty($tag['handler'])) { 41 | $definition = $container->findDefinition(\sprintf('monolog.handler.%s', $tag['handler'])); 42 | $parentDef = $definition; 43 | while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { 44 | $parentDef = $container->findDefinition($parentDef->getParent()); 45 | } 46 | $class = $container->getParameterBag()->resolveValue($parentDef->getClass()); 47 | if (!method_exists($class, 'pushProcessor')) { 48 | throw new \InvalidArgumentException(\sprintf('The "%s" handler does not accept processors', $tag['handler'])); 49 | } 50 | } elseif (!empty($tag['channel'])) { 51 | if ('app' === $tag['channel']) { 52 | $definition = $container->getDefinition('monolog.logger'); 53 | } else { 54 | $definition = $container->getDefinition(\sprintf('monolog.logger.%s', $tag['channel'])); 55 | } 56 | } else { 57 | $definition = $container->getDefinition('monolog.logger_prototype'); 58 | } 59 | 60 | if (!empty($tag['method'])) { 61 | $processor = [new Reference($id), $tag['method']]; 62 | } else { 63 | // If no method is defined, fallback to use __invoke 64 | $processor = new Reference($id); 65 | } 66 | $definition->addMethodCall('pushProcessor', [$processor]); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/AddSwiftMailerTransportPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; 13 | 14 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | use Symfony\Component\DependencyInjection\Reference; 17 | 18 | /** 19 | * Sets the transport for Swiftmailer handlers depending on the existing 20 | * container definitions. 21 | * 22 | * @author Christian Flothmann 23 | * 24 | * @internal since 3.9.0 25 | */ 26 | class AddSwiftMailerTransportPass implements CompilerPassInterface 27 | { 28 | public function process(ContainerBuilder $container) 29 | { 30 | $handlers = $container->getParameter('monolog.swift_mailer.handlers'); 31 | 32 | foreach ($handlers as $id) { 33 | $definition = $container->getDefinition($id); 34 | $mailerId = (string) $definition->getArgument(0); 35 | 36 | // Try to fetch the transport for a non-default mailer first, then go with the default swiftmailer 37 | $possibleServices = [ 38 | $mailerId.'.transport.real', 39 | $mailerId.'.transport', 40 | 'swiftmailer.transport.real', 41 | 'swiftmailer.transport', 42 | ]; 43 | 44 | foreach ($possibleServices as $serviceId) { 45 | if ($container->hasAlias($serviceId) || $container->hasDefinition($serviceId)) { 46 | $definition->addMethodCall( 47 | 'setTransport', 48 | [new Reference($serviceId)] 49 | ); 50 | 51 | break; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/DebugHandlerPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; 13 | 14 | use Monolog\Logger; 15 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\DependencyInjection\Definition; 18 | use Symfony\Component\DependencyInjection\Reference; 19 | 20 | /** 21 | * Adds the DebugHandler when the profiler is enabled and kernel.debug is true. 22 | * 23 | * @author Christophe Coevoet 24 | * @author Jordi Boggiano 25 | * 26 | * @deprecated since version 2.12, to be removed in 4.0. Use AddDebugLogProcessorPass in FrameworkBundle instead. 27 | */ 28 | class DebugHandlerPass implements CompilerPassInterface 29 | { 30 | private $channelPass; 31 | 32 | public function __construct(LoggerChannelPass $channelPass) 33 | { 34 | @trigger_error('The '.__CLASS__.' class is deprecated since version 2.12 and will be removed in 4.0. Use AddDebugLogProcessorPass in FrameworkBundle instead.', \E_USER_DEPRECATED); 35 | 36 | $this->channelPass = $channelPass; 37 | } 38 | 39 | public function process(ContainerBuilder $container) 40 | { 41 | if (!$container->hasDefinition('profiler')) { 42 | return; 43 | } 44 | 45 | if (!$container->getParameter('kernel.debug')) { 46 | return; 47 | } 48 | 49 | $debugHandler = new Definition('Symfony\Bridge\Monolog\Handler\DebugHandler', [Logger::DEBUG, true]); 50 | $container->setDefinition('monolog.handler.debug', $debugHandler); 51 | 52 | foreach ($this->channelPass->getChannels() as $channel) { 53 | $container 54 | ->getDefinition('app' === $channel ? 'monolog.logger' : 'monolog.logger.'.$channel) 55 | ->addMethodCall('pushHandler', [new Reference('monolog.handler.debug')]); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/FixEmptyLoggerPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; 13 | 14 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | use Symfony\Component\DependencyInjection\Reference; 17 | 18 | /** 19 | * Fixes loggers with no handlers (by registering a "null" one). 20 | * 21 | * Monolog 1.x adds a default handler logging on STDERR when a logger has 22 | * no registered handlers. This is NOT what what we want in Symfony, so in such 23 | * cases, we add a "null" handler to avoid the issue. 24 | * 25 | * Note that Monolog 2.x does not register a default handler anymore, so this pass can 26 | * be removed when MonologBundle minimum version of Monolog is bumped to 2.0. 27 | * 28 | * @author Fabien Potencier 29 | * 30 | * @see https://github.com/Seldaek/monolog/commit/ad37b7b2d11f300cbace9f5e84f855d329519e28 31 | * 32 | * @internal since 3.9.0 33 | */ 34 | class FixEmptyLoggerPass implements CompilerPassInterface 35 | { 36 | private $channelPass; 37 | 38 | public function __construct(LoggerChannelPass $channelPass) 39 | { 40 | $this->channelPass = $channelPass; 41 | } 42 | 43 | public function process(ContainerBuilder $container) 44 | { 45 | $container->register('monolog.handler.null_internal', 'Monolog\Handler\NullHandler'); 46 | foreach ($this->channelPass->getChannels() as $channel) { 47 | $def = $container->getDefinition('app' === $channel ? 'monolog.logger' : 'monolog.logger.'.$channel); 48 | foreach ($def->getMethodCalls() as $method) { 49 | if ('pushHandler' === $method[0]) { 50 | continue 2; 51 | } 52 | } 53 | 54 | $def->addMethodCall('pushHandler', [new Reference('monolog.handler.null_internal')]); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/LoggerChannelPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; 13 | 14 | use Psr\Log\LoggerInterface; 15 | use Symfony\Component\DependencyInjection\Argument\BoundArgument; 16 | use Symfony\Component\DependencyInjection\ChildDefinition; 17 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; 20 | use Symfony\Component\DependencyInjection\Reference; 21 | 22 | /** 23 | * Replaces the default logger by another one with its own channel for tagged services. 24 | * 25 | * @author Christophe Coevoet 26 | * 27 | * @internal since 3.9.0 28 | */ 29 | class LoggerChannelPass implements CompilerPassInterface 30 | { 31 | protected $channels = ['app']; 32 | 33 | public function process(ContainerBuilder $container) 34 | { 35 | if (!$container->hasDefinition('monolog.logger')) { 36 | return; 37 | } 38 | 39 | // create channels necessary for the handlers 40 | foreach ($container->findTaggedServiceIds('monolog.logger') as $id => $tags) { 41 | foreach ($tags as $tag) { 42 | if (empty($tag['channel']) || 'app' === $tag['channel']) { 43 | continue; 44 | } 45 | 46 | $resolvedChannel = $container->getParameterBag()->resolveValue($tag['channel']); 47 | 48 | $definition = $container->getDefinition($id); 49 | $loggerId = \sprintf('monolog.logger.%s', $resolvedChannel); 50 | $this->createLogger($resolvedChannel, $loggerId, $container); 51 | 52 | foreach ($definition->getArguments() as $index => $argument) { 53 | if ($argument instanceof Reference && 'logger' === (string) $argument) { 54 | $definition->replaceArgument($index, $this->changeReference($argument, $loggerId)); 55 | } 56 | } 57 | 58 | $calls = $definition->getMethodCalls(); 59 | foreach ($calls as $i => $call) { 60 | foreach ($call[1] as $index => $argument) { 61 | if ($argument instanceof Reference && 'logger' === (string) $argument) { 62 | $calls[$i][1][$index] = $this->changeReference($argument, $loggerId); 63 | } 64 | } 65 | } 66 | $definition->setMethodCalls($calls); 67 | 68 | $binding = new BoundArgument(new Reference($loggerId)); 69 | 70 | // Mark the binding as used already, to avoid reporting it as unused if the service does not use a 71 | // logger injected through the LoggerInterface alias. 72 | $values = $binding->getValues(); 73 | $values[2] = true; 74 | $binding->setValues($values); 75 | 76 | $bindings = $definition->getBindings(); 77 | $bindings['Psr\Log\LoggerInterface'] = $binding; 78 | $definition->setBindings($bindings); 79 | } 80 | } 81 | 82 | // create additional channels 83 | foreach ($container->getParameter('monolog.additional_channels') as $chan) { 84 | if ('app' === $chan) { 85 | continue; 86 | } 87 | $loggerId = \sprintf('monolog.logger.%s', $chan); 88 | $this->createLogger($chan, $loggerId, $container); 89 | $container->getDefinition($loggerId)->setPublic(true); 90 | } 91 | $container->getParameterBag()->remove('monolog.additional_channels'); 92 | 93 | // wire handlers to channels 94 | $handlersToChannels = $container->getParameter('monolog.handlers_to_channels'); 95 | foreach ($handlersToChannels as $handler => $channels) { 96 | foreach ($this->processChannels($channels) as $channel) { 97 | try { 98 | $logger = $container->getDefinition('app' === $channel ? 'monolog.logger' : 'monolog.logger.'.$channel); 99 | } catch (InvalidArgumentException $e) { 100 | $msg = 'Monolog configuration error: The logging channel "'.$channel.'" assigned to the "'.substr($handler, 16).'" handler does not exist.'; 101 | throw new \InvalidArgumentException($msg, 0, $e); 102 | } 103 | $logger->addMethodCall('pushHandler', [new Reference($handler)]); 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * @return array 110 | */ 111 | public function getChannels() 112 | { 113 | return $this->channels; 114 | } 115 | 116 | /** 117 | * @return array 118 | */ 119 | protected function processChannels(?array $configuration) 120 | { 121 | if (null === $configuration) { 122 | return $this->channels; 123 | } 124 | 125 | if ('inclusive' === $configuration['type']) { 126 | return $configuration['elements'] ?: $this->channels; 127 | } 128 | 129 | return array_diff($this->channels, $configuration['elements']); 130 | } 131 | 132 | /** 133 | * Create new logger from the monolog.logger_prototype. 134 | * 135 | * @return void 136 | */ 137 | protected function createLogger(string $channel, string $loggerId, ContainerBuilder $container) 138 | { 139 | if (!\in_array($channel, $this->channels)) { 140 | $logger = new ChildDefinition('monolog.logger_prototype'); 141 | $logger->replaceArgument(0, $channel); 142 | $container->setDefinition($loggerId, $logger); 143 | $this->channels[] = $channel; 144 | } 145 | 146 | $parameterName = $channel.'Logger'; 147 | 148 | $container->registerAliasForArgument($loggerId, LoggerInterface::class, $parameterName); 149 | } 150 | 151 | /** 152 | * Creates a copy of a reference and alters the service ID. 153 | */ 154 | private function changeReference(Reference $reference, string $serviceId): Reference 155 | { 156 | return new Reference($serviceId, $reference->getInvalidBehavior()); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\DependencyInjection; 13 | 14 | use Monolog\Logger; 15 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 17 | use Symfony\Component\Config\Definition\ConfigurationInterface; 18 | use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; 19 | 20 | /** 21 | * This class contains the configuration information for the bundle. 22 | * 23 | * This information is solely responsible for how the different configuration 24 | * sections are normalized, and merged. 25 | * 26 | * Possible handler types and related configurations (brackets indicate optional params): 27 | * 28 | * - service: 29 | * - id 30 | * 31 | * - stream: 32 | * - path: string 33 | * - [level]: level name or int value, defaults to DEBUG 34 | * - [bubble]: bool, defaults to true 35 | * - [file_permission]: int|null, defaults to null (0644) 36 | * - [use_locking]: bool, defaults to false 37 | * 38 | * - console: 39 | * - [verbosity_levels]: level => verbosity configuration 40 | * - [level]: level name or int value, defaults to DEBUG 41 | * - [bubble]: bool, defaults to true 42 | * - [console_formatter_options]: array 43 | * 44 | * - firephp: 45 | * - [level]: level name or int value, defaults to DEBUG 46 | * - [bubble]: bool, defaults to true 47 | * 48 | * - browser_console: 49 | * - [level]: level name or int value, defaults to DEBUG 50 | * - [bubble]: bool, defaults to true 51 | * 52 | * - gelf: 53 | * - publisher: {id: ...} or {hostname: ..., port: ..., chunk_size: ...} 54 | * - [level]: level name or int value, defaults to DEBUG 55 | * - [bubble]: bool, defaults to true 56 | * 57 | * - chromephp: 58 | * - [level]: level name or int value, defaults to DEBUG 59 | * - [bubble]: bool, defaults to true 60 | * 61 | * - rotating_file: 62 | * - path: string 63 | * - [max_files]: files to keep, defaults to zero (infinite) 64 | * - [level]: level name or int value, defaults to DEBUG 65 | * - [bubble]: bool, defaults to true 66 | * - [file_permission]: string|null, defaults to null 67 | * - [use_locking]: bool, defaults to false 68 | * - [filename_format]: string, defaults to '{filename}-{date}' 69 | * - [date_format]: string, defaults to 'Y-m-d' 70 | * 71 | * - mongo: 72 | * - mongo: 73 | * - id: optional if host is given 74 | * - host: database host name, optional if id is given 75 | * - [port]: defaults to 27017 76 | * - [user]: database user name 77 | * - pass: mandatory only if user is present 78 | * - [database]: defaults to monolog 79 | * - [collection]: defaults to logs 80 | * - [level]: level name or int value, defaults to DEBUG 81 | * - [bubble]: bool, defaults to true 82 | * 83 | * - elastic_search: 84 | * - elasticsearch: 85 | * - id: optional if host is given 86 | * - host: elastic search host name, with scheme (e.g. "https://127.0.0.1:9200") 87 | * - [user]: elastic search user name 88 | * - [password]: elastic search user password 89 | * - [index]: index name, defaults to monolog 90 | * - [document_type]: document_type, defaults to logs 91 | * - [level]: level name or int value, defaults to DEBUG 92 | * - [bubble]: bool, defaults to true 93 | * 94 | * - elastica: 95 | * - elasticsearch: 96 | * - id: optional if host is given 97 | * - host: elastic search host name. Do not prepend with http(s):// 98 | * - [port]: defaults to 9200 99 | * - [transport]: transport protocol (http by default) 100 | * - [user]: elastic search user name 101 | * - [password]: elastic search user password 102 | * - [index]: index name, defaults to monolog 103 | * - [document_type]: document_type, defaults to logs 104 | * - [level]: level name or int value, defaults to DEBUG 105 | * - [bubble]: bool, defaults to true 106 | * 107 | * - redis: 108 | * - redis: 109 | * - id: optional if host is given 110 | * - host: 127.0.0.1 111 | * - password: null 112 | * - port: 6379 113 | * - database: 0 114 | * - key_name: monolog_redis 115 | * 116 | * - predis: 117 | * - redis: 118 | * - id: optional if host is given 119 | * - host: tcp://10.0.0.1:6379 120 | * - key_name: monolog_redis 121 | * 122 | * - fingers_crossed: 123 | * - handler: the wrapped handler's name 124 | * - [action_level|activation_strategy]: minimum level or service id to activate the handler, defaults to WARNING 125 | * - [excluded_404s]: if set, the strategy will be changed to one that excludes 404s coming from URLs matching any of those patterns 126 | * - [excluded_http_codes]: if set, the strategy will be changed to one that excludes specific HTTP codes (requires Symfony Monolog bridge 4.1+) 127 | * - [buffer_size]: defaults to 0 (unlimited) 128 | * - [stop_buffering]: bool to disable buffering once the handler has been activated, defaults to true 129 | * - [passthru_level]: level name or int value for messages to always flush, disabled by default 130 | * - [bubble]: bool, defaults to true 131 | * 132 | * - filter: 133 | * - handler: the wrapped handler's name 134 | * - [accepted_levels]: list of levels to accept 135 | * - [min_level]: minimum level to accept (only used if accepted_levels not specified) 136 | * - [max_level]: maximum level to accept (only used if accepted_levels not specified) 137 | * - [bubble]: bool, defaults to true 138 | * 139 | * - buffer: 140 | * - handler: the wrapped handler's name 141 | * - [buffer_size]: defaults to 0 (unlimited) 142 | * - [level]: level name or int value, defaults to DEBUG 143 | * - [bubble]: bool, defaults to true 144 | * - [flush_on_overflow]: bool, defaults to false 145 | * 146 | * - deduplication: 147 | * - handler: the wrapped handler's name 148 | * - [store]: The file/path where the deduplication log should be kept, defaults to %kernel.cache_dir%/monolog_dedup_* 149 | * - [deduplication_level]: The minimum logging level for log records to be looked at for deduplication purposes, defaults to ERROR 150 | * - [time]: The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through, defaults to 60 151 | * - [bubble]: bool, defaults to true 152 | * 153 | * - group: 154 | * - members: the wrapped handlers by name 155 | * - [bubble]: bool, defaults to true 156 | * 157 | * - whatfailuregroup: 158 | * - members: the wrapped handlers by name 159 | * - [bubble]: bool, defaults to true 160 | * 161 | * - fallbackgroup 162 | * - members: the wrapped handlers by name 163 | * - [bubble]: bool, defaults to true 164 | * 165 | * - syslog: 166 | * - ident: string 167 | * - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER 168 | * - [logopts]: defaults to LOG_PID 169 | * - [level]: level name or int value, defaults to DEBUG 170 | * - [bubble]: bool, defaults to true 171 | * 172 | * - syslogudp: 173 | * - host: syslogd host name 174 | * - [port]: defaults to 514 175 | * - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER 176 | * - [logopts]: defaults to LOG_PID 177 | * - [level]: level name or int value, defaults to DEBUG 178 | * - [bubble]: bool, defaults to true 179 | * - [ident]: string, defaults to 180 | * 181 | * - swift_mailer: 182 | * - from_email: optional if email_prototype is given 183 | * - to_email: optional if email_prototype is given 184 | * - subject: optional if email_prototype is given 185 | * - [email_prototype]: service id of a message, defaults to a default message with the three fields above 186 | * - [content_type]: optional if email_prototype is given, defaults to text/plain 187 | * - [mailer]: mailer service, defaults to mailer 188 | * - [level]: level name or int value, defaults to DEBUG 189 | * - [bubble]: bool, defaults to true 190 | * - [lazy]: use service lazy loading, bool, defaults to true 191 | * 192 | * - native_mailer: 193 | * - from_email: string 194 | * - to_email: string 195 | * - subject: string 196 | * - [level]: level name or int value, defaults to DEBUG 197 | * - [bubble]: bool, defaults to true 198 | * - [headers]: optional array containing additional headers: ['Foo: Bar', '...'] 199 | * 200 | * - symfony_mailer: 201 | * - from_email: optional if email_prototype is given 202 | * - to_email: optional if email_prototype is given 203 | * - subject: optional if email_prototype is given 204 | * - [email_prototype]: service id of a message, defaults to a default message with the three fields above 205 | * - [mailer]: mailer service id, defaults to mailer.mailer 206 | * - [level]: level name or int value, defaults to DEBUG 207 | * - [bubble]: bool, defaults to true 208 | * 209 | * - socket: 210 | * - connection_string: string 211 | * - [timeout]: float 212 | * - [connection_timeout]: float 213 | * - [persistent]: bool 214 | * - [level]: level name or int value, defaults to DEBUG 215 | * - [bubble]: bool, defaults to true 216 | * 217 | * - pushover: 218 | * - token: pushover api token 219 | * - user: user id or array of ids 220 | * - [title]: optional title for messages, defaults to the server hostname 221 | * - [level]: level name or int value, defaults to DEBUG 222 | * - [bubble]: bool, defaults to true 223 | * - [timeout]: float 224 | * - [connection_timeout]: float 225 | * 226 | * - raven / sentry: 227 | * - dsn: connection string 228 | * - client_id: Raven client custom service id (optional) 229 | * - [release]: release number of the application that will be attached to logs, defaults to null 230 | * - [level]: level name or int value, defaults to DEBUG 231 | * - [bubble]: bool, defaults to true 232 | * - [auto_log_stacks]: bool, defaults to false 233 | * - [environment]: string, default to null (no env specified) 234 | * 235 | * - sentry: 236 | * - hub_id: Sentry hub custom service id (optional) 237 | * - [fill_extra_context]: bool, defaults to false 238 | * 239 | * - newrelic: 240 | * - [level]: level name or int value, defaults to DEBUG 241 | * - [bubble]: bool, defaults to true 242 | * - [app_name]: new relic app name, default null 243 | * 244 | * - hipchat: 245 | * - token: hipchat api token 246 | * - room: room id or name 247 | * - [notify]: defaults to false 248 | * - [nickname]: defaults to Monolog 249 | * - [level]: level name or int value, defaults to DEBUG 250 | * - [bubble]: bool, defaults to true 251 | * - [use_ssl]: bool, defaults to true 252 | * - [message_format]: text or html, defaults to text 253 | * - [host]: defaults to "api.hipchat.com" 254 | * - [api_version]: defaults to "v1" 255 | * - [timeout]: float 256 | * - [connection_timeout]: float 257 | * 258 | * - slack: 259 | * - token: slack api token 260 | * - channel: channel name (with starting #) 261 | * - [bot_name]: defaults to Monolog 262 | * - [icon_emoji]: defaults to null 263 | * - [use_attachment]: bool, defaults to true 264 | * - [use_short_attachment]: bool, defaults to false 265 | * - [include_extra]: bool, defaults to false 266 | * - [level]: level name or int value, defaults to DEBUG 267 | * - [bubble]: bool, defaults to true 268 | * - [timeout]: float 269 | * - [connection_timeout]: float 270 | * 271 | * - slackwebhook: 272 | * - webhook_url: slack webhook URL 273 | * - channel: channel name (with starting #) 274 | * - [bot_name]: defaults to Monolog 275 | * - [icon_emoji]: defaults to null 276 | * - [use_attachment]: bool, defaults to true 277 | * - [use_short_attachment]: bool, defaults to false 278 | * - [include_extra]: bool, defaults to false 279 | * - [level]: level name or int value, defaults to DEBUG 280 | * - [bubble]: bool, defaults to true 281 | * 282 | * - slackbot: 283 | * - team: slack team slug 284 | * - token: slackbot token 285 | * - channel: channel name (with starting #) 286 | * - [level]: level name or int value, defaults to DEBUG 287 | * - [bubble]: bool, defaults to true 288 | * 289 | * - cube: 290 | * - url: http/udp url to the cube server 291 | * - [level]: level name or int value, defaults to DEBUG 292 | * - [bubble]: bool, defaults to true 293 | * 294 | * - amqp: 295 | * - exchange: service id of an AMQPExchange 296 | * - [exchange_name]: string, defaults to log 297 | * - [level]: level name or int value, defaults to DEBUG 298 | * - [bubble]: bool, defaults to true 299 | * 300 | * - error_log: 301 | * - [message_type]: int 0 or 4, defaults to 0 302 | * - [level]: level name or int value, defaults to DEBUG 303 | * - [bubble]: bool, defaults to true 304 | * 305 | * - null: 306 | * - [level]: level name or int value, defaults to DEBUG 307 | * - [bubble]: bool, defaults to true 308 | * 309 | * - test: 310 | * - [level]: level name or int value, defaults to DEBUG 311 | * - [bubble]: bool, defaults to true 312 | * 313 | * - debug: 314 | * - [level]: level name or int value, defaults to DEBUG 315 | * - [bubble]: bool, defaults to true 316 | * 317 | * - loggly: 318 | * - token: loggly api token 319 | * - [level]: level name or int value, defaults to DEBUG 320 | * - [bubble]: bool, defaults to true 321 | * - [tags]: tag names 322 | * 323 | * - logentries: 324 | * - token: logentries api token 325 | * - [use_ssl]: whether or not SSL encryption should be used, defaults to true 326 | * - [level]: level name or int value, defaults to DEBUG 327 | * - [bubble]: bool, defaults to true 328 | * - [timeout]: float 329 | * - [connection_timeout]: float 330 | * 331 | * - insightops: 332 | * - token: Log token supplied by InsightOps 333 | * - region: Region where InsightOps account is hosted. Could be 'us' or 'eu'. Defaults to 'us' 334 | * - [use_ssl]: whether or not SSL encryption should be used, defaults to true 335 | * - [level]: level name or int value, defaults to DEBUG 336 | * - [bubble]: bool, defaults to true 337 | * 338 | * - flowdock: 339 | * - token: flowdock api token 340 | * - source: human readable identifier of the application 341 | * - from_email: email address of the message sender 342 | * - [level]: level name or int value, defaults to DEBUG 343 | * - [bubble]: bool, defaults to true 344 | * 345 | * - rollbar: 346 | * - id: RollbarNotifier service (mandatory if token is not provided) 347 | * - token: rollbar api token (skip if you provide a RollbarNotifier service id) 348 | * - [config]: config values from https://github.com/rollbar/rollbar-php#configuration-reference 349 | * - [level]: level name or int value, defaults to DEBUG 350 | * - [bubble]: bool, defaults to true 351 | * 352 | * - server_log: 353 | * - host: server log host. ex: 127.0.0.1:9911 354 | * - [level]: level name or int value, defaults to DEBUG 355 | * - [bubble]: bool, defaults to true 356 | * 357 | * - telegram: 358 | * - token: Telegram bot access token provided by BotFather 359 | * - channel: Telegram channel name 360 | * - [level]: level name or int value, defaults to DEBUG 361 | * - [bubble]: bool, defaults to true 362 | * - [parse_mode]: optional the kind of formatting that is used for the message 363 | * - [disable_webpage_preview]: bool, defaults to false, disables link previews for links in the message 364 | * - [disable_notification]: bool, defaults to false, sends the message silently. Users will receive a notification with no sound 365 | * - [split_long_messages]: bool, defaults to false, split messages longer than 4096 bytes into multiple messages 366 | * - [delay_between_messages]: bool, defaults to false, adds a 1sec delay/sleep between sending split messages 367 | * 368 | * - sampling: 369 | * - handler: the wrapped handler's name 370 | * - factor: the sampling factor (e.g. 10 means every ~10th record is sampled) 371 | * 372 | * All handlers can also be marked with `nested: true` to make sure they are never added explicitly to the stack 373 | * 374 | * @author Jordi Boggiano 375 | * @author Christophe Coevoet 376 | * 377 | * @final since 3.9.0 378 | */ 379 | class Configuration implements ConfigurationInterface 380 | { 381 | /** 382 | * Generates the configuration tree builder. 383 | */ 384 | public function getConfigTreeBuilder(): TreeBuilder 385 | { 386 | $treeBuilder = new TreeBuilder('monolog'); 387 | $rootNode = $treeBuilder->getRootNode(); 388 | 389 | $handlers = $rootNode 390 | ->fixXmlConfig('channel') 391 | ->fixXmlConfig('handler') 392 | ->children() 393 | ->scalarNode('use_microseconds')->defaultTrue()->end() 394 | ->arrayNode('channels') 395 | ->canBeUnset() 396 | ->prototype('scalar')->end() 397 | ->end() 398 | ->arrayNode('handlers'); 399 | 400 | $handlers 401 | ->canBeUnset() 402 | ->useAttributeAsKey('name') 403 | ->validate() 404 | ->ifTrue(function ($v) { return isset($v['debug']); }) 405 | ->thenInvalid('The "debug" name cannot be used as it is reserved for the handler of the profiler') 406 | ->end() 407 | ->example([ 408 | 'syslog' => [ 409 | 'type' => 'stream', 410 | 'path' => '/var/log/symfony.log', 411 | 'level' => 'ERROR', 412 | 'bubble' => 'false', 413 | 'formatter' => 'my_formatter', 414 | ], 415 | 'main' => [ 416 | 'type' => 'fingers_crossed', 417 | 'action_level' => 'WARNING', 418 | 'buffer_size' => 30, 419 | 'handler' => 'custom', 420 | ], 421 | 'custom' => [ 422 | 'type' => 'service', 423 | 'id' => 'my_handler', 424 | ], 425 | ]); 426 | 427 | $handlerNode = $handlers 428 | ->prototype('array') 429 | ->fixXmlConfig('member') 430 | ->fixXmlConfig('excluded_404') 431 | ->fixXmlConfig('excluded_http_code') 432 | ->fixXmlConfig('tag') 433 | ->fixXmlConfig('accepted_level') 434 | ->fixXmlConfig('header') 435 | ->canBeUnset(); 436 | 437 | $handlerNode 438 | ->children() 439 | ->scalarNode('type') 440 | ->isRequired() 441 | ->treatNullLike('null') 442 | ->beforeNormalization() 443 | ->always() 444 | ->then(function ($v) { return strtolower($v); }) 445 | ->end() 446 | ->end() 447 | ->scalarNode('id')->end() // service & rollbar 448 | ->scalarNode('priority')->defaultValue(0)->end() 449 | ->scalarNode('level')->defaultValue('DEBUG')->end() 450 | ->booleanNode('bubble')->defaultTrue()->end() 451 | ->scalarNode('app_name')->defaultNull()->end() 452 | ->booleanNode('fill_extra_context')->defaultFalse()->end() // sentry 453 | ->booleanNode('include_stacktraces')->defaultFalse()->end() 454 | ->arrayNode('process_psr_3_messages') 455 | ->addDefaultsIfNotSet() 456 | ->beforeNormalization() 457 | ->ifTrue(static function ($v) { return !\is_array($v); }) 458 | ->then(static function ($v) { return ['enabled' => $v]; }) 459 | ->end() 460 | ->children() 461 | ->booleanNode('enabled')->defaultNull()->end() 462 | ->scalarNode('date_format')->end() 463 | ->booleanNode('remove_used_context_fields')->end() 464 | ->end() 465 | ->end() 466 | ->scalarNode('path')->defaultValue('%kernel.logs_dir%/%kernel.environment%.log')->end() // stream and rotating 467 | ->scalarNode('file_permission') // stream and rotating 468 | ->defaultNull() 469 | ->beforeNormalization() 470 | ->ifString() 471 | ->then(function ($v) { 472 | if ('0' === substr($v, 0, 1)) { 473 | return octdec($v); 474 | } 475 | 476 | return (int) $v; 477 | }) 478 | ->end() 479 | ->end() 480 | ->booleanNode('use_locking')->defaultFalse()->end() // stream and rotating 481 | ->scalarNode('filename_format')->defaultValue('{filename}-{date}')->end() // rotating 482 | ->scalarNode('date_format')->defaultValue('Y-m-d')->end() // rotating 483 | ->scalarNode('ident')->defaultFalse()->end() // syslog and syslogudp 484 | ->scalarNode('logopts')->defaultValue(\LOG_PID)->end() // syslog 485 | ->scalarNode('facility')->defaultValue('user')->end() // syslog 486 | ->scalarNode('max_files')->defaultValue(0)->end() // rotating 487 | ->scalarNode('action_level')->defaultValue('WARNING')->end() // fingers_crossed 488 | ->scalarNode('activation_strategy')->defaultNull()->end() // fingers_crossed 489 | ->booleanNode('stop_buffering')->defaultTrue()->end()// fingers_crossed 490 | ->scalarNode('passthru_level')->defaultNull()->end() // fingers_crossed 491 | ->arrayNode('excluded_404s') // fingers_crossed 492 | ->canBeUnset() 493 | ->prototype('scalar')->end() 494 | ->end() 495 | ->arrayNode('excluded_http_codes') // fingers_crossed 496 | ->canBeUnset() 497 | ->beforeNormalization() 498 | ->always(function ($values) { 499 | return array_map(function ($value) { 500 | /* 501 | * Allows YAML: 502 | * excluded_http_codes: [403, 404, { 400: ['^/foo', '^/bar'] }] 503 | * 504 | * and XML: 505 | * 506 | * ^/foo 507 | * ^/bar 508 | * 509 | * 510 | */ 511 | 512 | if (\is_array($value)) { 513 | return isset($value['code']) ? $value : ['code' => key($value), 'urls' => current($value)]; 514 | } 515 | 516 | return ['code' => $value, 'urls' => []]; 517 | }, $values); 518 | }) 519 | ->end() 520 | ->prototype('array') 521 | ->children() 522 | ->scalarNode('code')->end() 523 | ->arrayNode('urls') 524 | ->prototype('scalar')->end() 525 | ->end() 526 | ->end() 527 | ->end() 528 | ->end() 529 | ->arrayNode('accepted_levels') // filter 530 | ->canBeUnset() 531 | ->prototype('scalar')->end() 532 | ->end() 533 | ->scalarNode('min_level')->defaultValue('DEBUG')->end() // filter 534 | ->scalarNode('max_level')->defaultValue('EMERGENCY')->end() // filter 535 | ->scalarNode('buffer_size')->defaultValue(0)->end() // fingers_crossed and buffer 536 | ->booleanNode('flush_on_overflow')->defaultFalse()->end() // buffer 537 | ->scalarNode('handler')->end() // fingers_crossed and buffer 538 | ->scalarNode('url')->end() // cube 539 | ->scalarNode('exchange')->end() // amqp 540 | ->scalarNode('exchange_name')->defaultValue('log')->end() // amqp 541 | ->scalarNode('room')->end() // hipchat 542 | ->scalarNode('message_format')->defaultValue('text')->end() // hipchat 543 | ->scalarNode('api_version')->defaultNull()->end() // hipchat 544 | ->scalarNode('channel')->defaultNull()->end() // slack & slackwebhook & slackbot & telegram 545 | ->scalarNode('bot_name')->defaultValue('Monolog')->end() // slack & slackwebhook 546 | ->scalarNode('use_attachment')->defaultTrue()->end() // slack & slackwebhook 547 | ->scalarNode('use_short_attachment')->defaultFalse()->end() // slack & slackwebhook 548 | ->scalarNode('include_extra')->defaultFalse()->end() // slack & slackwebhook 549 | ->scalarNode('icon_emoji')->defaultNull()->end() // slack & slackwebhook 550 | ->scalarNode('webhook_url')->end() // slackwebhook 551 | ->scalarNode('team')->end() // slackbot 552 | ->scalarNode('notify')->defaultFalse()->end() // hipchat 553 | ->scalarNode('nickname')->defaultValue('Monolog')->end() // hipchat 554 | ->scalarNode('token')->end() // pushover & hipchat & loggly & logentries & flowdock & rollbar & slack & slackbot & insightops & telegram 555 | ->scalarNode('region')->end() // insightops 556 | ->scalarNode('source')->end() // flowdock 557 | ->booleanNode('use_ssl')->defaultTrue()->end() // logentries & hipchat & insightops 558 | ->variableNode('user') // pushover 559 | ->validate() 560 | ->ifTrue(function ($v) { 561 | return !\is_string($v) && !\is_array($v); 562 | }) 563 | ->thenInvalid('User must be a string or an array.') 564 | ->end() 565 | ->end() 566 | ->scalarNode('title')->defaultNull()->end() // pushover 567 | ->scalarNode('host')->defaultNull()->end() // syslogudp & hipchat 568 | ->scalarNode('port')->defaultValue(514)->end() // syslogudp 569 | ->arrayNode('config') 570 | ->canBeUnset() 571 | ->prototype('scalar')->end() 572 | ->end() // rollbar 573 | ->arrayNode('members') // group, whatfailuregroup, fallbackgroup 574 | ->canBeUnset() 575 | ->performNoDeepMerging() 576 | ->prototype('scalar')->end() 577 | ->end() 578 | ->scalarNode('connection_string')->end() // socket_handler 579 | ->scalarNode('timeout')->end() // socket_handler, logentries, pushover, hipchat & slack 580 | ->scalarNode('time')->defaultValue(60)->end() // deduplication 581 | ->scalarNode('deduplication_level')->defaultValue(Logger::ERROR)->end() // deduplication 582 | ->scalarNode('store')->defaultNull()->end() // deduplication 583 | ->scalarNode('connection_timeout')->end() // socket_handler, logentries, pushover, hipchat & slack 584 | ->booleanNode('persistent')->end() // socket_handler 585 | ->scalarNode('dsn')->end() // raven_handler, sentry_handler 586 | ->scalarNode('hub_id')->defaultNull()->end() // sentry_handler 587 | ->scalarNode('client_id')->defaultNull()->end() // raven_handler, sentry_handler 588 | ->scalarNode('auto_log_stacks')->defaultFalse()->end() // raven_handler 589 | ->scalarNode('release')->defaultNull()->end() // raven_handler, sentry_handler 590 | ->scalarNode('environment')->defaultNull()->end() // raven_handler, sentry_handler 591 | ->scalarNode('message_type')->defaultValue(0)->end() // error_log 592 | ->scalarNode('parse_mode')->defaultNull()->end() // telegram 593 | ->booleanNode('disable_webpage_preview')->defaultNull()->end() // telegram 594 | ->booleanNode('disable_notification')->defaultNull()->end() // telegram 595 | ->booleanNode('split_long_messages')->defaultFalse()->end() // telegram 596 | ->booleanNode('delay_between_messages')->defaultFalse()->end() // telegram 597 | ->integerNode('factor')->defaultValue(1)->min(1)->end() // sampling 598 | ->arrayNode('tags') // loggly 599 | ->beforeNormalization() 600 | ->ifString() 601 | ->then(function ($v) { return explode(',', $v); }) 602 | ->end() 603 | ->beforeNormalization() 604 | ->ifArray() 605 | ->then(function ($v) { return array_filter(array_map('trim', $v)); }) 606 | ->end() 607 | ->prototype('scalar')->end() 608 | ->end() 609 | // console 610 | ->variableNode('console_formater_options') 611 | ->setDeprecated('symfony/monolog-bundle', 3.7, '"%path%.%node%" is deprecated, use "%path%.console_formatter_options" instead.') 612 | ->validate() 613 | ->ifTrue(function ($v) { 614 | return !\is_array($v); 615 | }) 616 | ->thenInvalid('The console_formater_options must be an array.') 617 | ->end() 618 | ->end() 619 | ->variableNode('console_formatter_options') 620 | ->defaultValue([]) 621 | ->validate() 622 | ->ifTrue(static function ($v) { return !\is_array($v); }) 623 | ->thenInvalid('The console_formatter_options must be an array.') 624 | ->end() 625 | ->end() 626 | ->scalarNode('formatter')->end() 627 | ->booleanNode('nested')->defaultFalse()->end() 628 | ->end(); 629 | 630 | $this->addGelfSection($handlerNode); 631 | $this->addMongoSection($handlerNode); 632 | $this->addElasticsearchSection($handlerNode); 633 | $this->addRedisSection($handlerNode); 634 | $this->addPredisSection($handlerNode); 635 | $this->addMailerSection($handlerNode); 636 | $this->addVerbosityLevelSection($handlerNode); 637 | $this->addChannelsSection($handlerNode); 638 | 639 | $handlerNode 640 | ->beforeNormalization() 641 | ->always(static function ($v) { 642 | if (empty($v['console_formatter_options']) && !empty($v['console_formater_options'])) { 643 | $v['console_formatter_options'] = $v['console_formater_options']; 644 | } 645 | 646 | return $v; 647 | }) 648 | ->end() 649 | ->validate() 650 | ->always(static function ($v) { 651 | unset($v['console_formater_options']); 652 | 653 | return $v; 654 | }) 655 | ->end() 656 | ->validate() 657 | ->ifTrue(function ($v) { return 'service' === $v['type'] && !empty($v['formatter']); }) 658 | ->thenInvalid('Service handlers can not have a formatter configured in the bundle, you must reconfigure the service itself instead') 659 | ->end() 660 | ->validate() 661 | ->ifTrue(function ($v) { return ('fingers_crossed' === $v['type'] || 'buffer' === $v['type'] || 'filter' === $v['type'] || 'sampling' === $v['type']) && empty($v['handler']); }) 662 | ->thenInvalid('The handler has to be specified to use a FingersCrossedHandler or BufferHandler or FilterHandler or SamplingHandler') 663 | ->end() 664 | ->validate() 665 | ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_404s']) && !empty($v['activation_strategy']); }) 666 | ->thenInvalid('You can not use excluded_404s together with a custom activation_strategy in a FingersCrossedHandler') 667 | ->end() 668 | ->validate() 669 | ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['activation_strategy']); }) 670 | ->thenInvalid('You can not use excluded_http_codes together with a custom activation_strategy in a FingersCrossedHandler') 671 | ->end() 672 | ->validate() 673 | ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['excluded_404s']); }) 674 | ->thenInvalid('You can not use excluded_http_codes together with excluded_404s in a FingersCrossedHandler') 675 | ->end() 676 | ->validate() 677 | ->ifTrue(function ($v) { return 'fingers_crossed' !== $v['type'] && (!empty($v['excluded_http_codes']) || !empty($v['excluded_404s'])); }) 678 | ->thenInvalid('You can only use excluded_http_codes/excluded_404s with a FingersCrossedHandler definition') 679 | ->end() 680 | ->validate() 681 | ->ifTrue(function ($v) { return 'filter' === $v['type'] && 'DEBUG' !== $v['min_level'] && !empty($v['accepted_levels']); }) 682 | ->thenInvalid('You can not use min_level together with accepted_levels in a FilterHandler') 683 | ->end() 684 | ->validate() 685 | ->ifTrue(function ($v) { return 'filter' === $v['type'] && 'EMERGENCY' !== $v['max_level'] && !empty($v['accepted_levels']); }) 686 | ->thenInvalid('You can not use max_level together with accepted_levels in a FilterHandler') 687 | ->end() 688 | ->validate() 689 | ->ifTrue(function ($v) { return 'rollbar' === $v['type'] && !empty($v['id']) && !empty($v['token']); }) 690 | ->thenInvalid('You can not use both an id and a token in a RollbarHandler') 691 | ->end() 692 | ->validate() 693 | ->ifTrue(function ($v) { return 'rollbar' === $v['type'] && empty($v['id']) && empty($v['token']); }) 694 | ->thenInvalid('The id or the token has to be specified to use a RollbarHandler') 695 | ->end() 696 | ->validate() 697 | ->ifTrue(function ($v) { return 'telegram' === $v['type'] && (empty($v['token']) || empty($v['channel'])); }) 698 | ->thenInvalid('The token and channel have to be specified to use a TelegramBotHandler') 699 | ->end() 700 | ->validate() 701 | ->ifTrue(function ($v) { return 'service' === $v['type'] && !isset($v['id']); }) 702 | ->thenInvalid('The id has to be specified to use a service as handler') 703 | ->end() 704 | ->validate() 705 | ->ifTrue(function ($v) { return 'syslogudp' === $v['type'] && !isset($v['host']); }) 706 | ->thenInvalid('The host has to be specified to use a syslogudp as handler') 707 | ->end() 708 | ->validate() 709 | ->ifTrue(function ($v) { return 'socket' === $v['type'] && !isset($v['connection_string']); }) 710 | ->thenInvalid('The connection_string has to be specified to use a SocketHandler') 711 | ->end() 712 | ->validate() 713 | ->ifTrue(function ($v) { return 'pushover' === $v['type'] && (empty($v['token']) || empty($v['user'])); }) 714 | ->thenInvalid('The token and user have to be specified to use a PushoverHandler') 715 | ->end() 716 | ->validate() 717 | ->ifTrue(function ($v) { return 'raven' === $v['type'] && !\array_key_exists('dsn', $v) && null === $v['client_id']; }) 718 | ->thenInvalid('The DSN has to be specified to use a RavenHandler') 719 | ->end() 720 | ->validate() 721 | ->ifTrue(function ($v) { return 'sentry' === $v['type'] && !\array_key_exists('dsn', $v) && null === $v['hub_id'] && null === $v['client_id']; }) 722 | ->thenInvalid('The DSN has to be specified to use Sentry\'s handler') 723 | ->end() 724 | ->validate() 725 | ->ifTrue(function ($v) { return 'sentry' === $v['type'] && null !== $v['hub_id'] && null !== $v['client_id']; }) 726 | ->thenInvalid('You can not use both a hub_id and a client_id in a Sentry handler') 727 | ->end() 728 | ->validate() 729 | ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && (empty($v['token']) || empty($v['room'])); }) 730 | ->thenInvalid('The token and room have to be specified to use a HipChatHandler') 731 | ->end() 732 | ->validate() 733 | ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && !\in_array($v['message_format'], ['text', 'html']); }) 734 | ->thenInvalid('The message_format has to be "text" or "html" in a HipChatHandler') 735 | ->end() 736 | ->validate() 737 | ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && null !== $v['api_version'] && !\in_array($v['api_version'], ['v1', 'v2'], true); }) 738 | ->thenInvalid('The api_version has to be "v1" or "v2" in a HipChatHandler') 739 | ->end() 740 | ->validate() 741 | ->ifTrue(function ($v) { return 'slack' === $v['type'] && (empty($v['token']) || empty($v['channel'])); }) 742 | ->thenInvalid('The token and channel have to be specified to use a SlackHandler') 743 | ->end() 744 | ->validate() 745 | ->ifTrue(function ($v) { return 'slackwebhook' === $v['type'] && (empty($v['webhook_url'])); }) 746 | ->thenInvalid('The webhook_url have to be specified to use a SlackWebhookHandler') 747 | ->end() 748 | ->validate() 749 | ->ifTrue(function ($v) { return 'slackbot' === $v['type'] && (empty($v['team']) || empty($v['token']) || empty($v['channel'])); }) 750 | ->thenInvalid('The team, token and channel have to be specified to use a SlackbotHandler') 751 | ->end() 752 | ->validate() 753 | ->ifTrue(function ($v) { return 'cube' === $v['type'] && empty($v['url']); }) 754 | ->thenInvalid('The url has to be specified to use a CubeHandler') 755 | ->end() 756 | ->validate() 757 | ->ifTrue(function ($v) { return 'amqp' === $v['type'] && empty($v['exchange']); }) 758 | ->thenInvalid('The exchange has to be specified to use a AmqpHandler') 759 | ->end() 760 | ->validate() 761 | ->ifTrue(function ($v) { return 'loggly' === $v['type'] && empty($v['token']); }) 762 | ->thenInvalid('The token has to be specified to use a LogglyHandler') 763 | ->end() 764 | ->validate() 765 | ->ifTrue(function ($v) { return 'loggly' === $v['type'] && !empty($v['tags']); }) 766 | ->then(function ($v) { 767 | $invalidTags = preg_grep('/^[a-z0-9][a-z0-9\.\-_]*$/i', $v['tags'], \PREG_GREP_INVERT); 768 | if (!empty($invalidTags)) { 769 | throw new InvalidConfigurationException(\sprintf('The following Loggly tags are invalid: %s.', implode(', ', $invalidTags))); 770 | } 771 | 772 | return $v; 773 | }) 774 | ->end() 775 | ->validate() 776 | ->ifTrue(function ($v) { return 'logentries' === $v['type'] && empty($v['token']); }) 777 | ->thenInvalid('The token has to be specified to use a LogEntriesHandler') 778 | ->end() 779 | ->validate() 780 | ->ifTrue(function ($v) { return 'insightops' === $v['type'] && empty($v['token']); }) 781 | ->thenInvalid('The token has to be specified to use a InsightOpsHandler') 782 | ->end() 783 | ->validate() 784 | ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['token']); }) 785 | ->thenInvalid('The token has to be specified to use a FlowdockHandler') 786 | ->end() 787 | ->validate() 788 | ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['from_email']); }) 789 | ->thenInvalid('The from_email has to be specified to use a FlowdockHandler') 790 | ->end() 791 | ->validate() 792 | ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['source']); }) 793 | ->thenInvalid('The source has to be specified to use a FlowdockHandler') 794 | ->end() 795 | ->validate() 796 | ->ifTrue(function ($v) { return 'server_log' === $v['type'] && empty($v['host']); }) 797 | ->thenInvalid('The host has to be specified to use a ServerLogHandler') 798 | ->end() 799 | ; 800 | 801 | return $treeBuilder; 802 | } 803 | 804 | private function addGelfSection(ArrayNodeDefinition $handerNode) 805 | { 806 | $handerNode 807 | ->children() 808 | ->arrayNode('publisher') 809 | ->canBeUnset() 810 | ->beforeNormalization() 811 | ->ifString() 812 | ->then(function ($v) { return ['id' => $v]; }) 813 | ->end() 814 | ->children() 815 | ->scalarNode('id')->end() 816 | ->scalarNode('hostname')->end() 817 | ->scalarNode('port')->defaultValue(12201)->end() 818 | ->scalarNode('chunk_size')->defaultValue(1420)->end() 819 | ->end() 820 | ->validate() 821 | ->ifTrue(function ($v) { 822 | return !isset($v['id']) && !isset($v['hostname']); 823 | }) 824 | ->thenInvalid('What must be set is either the hostname or the id.') 825 | ->end() 826 | ->end() 827 | ->end() 828 | ->validate() 829 | ->ifTrue(function ($v) { return 'gelf' === $v['type'] && !isset($v['publisher']); }) 830 | ->thenInvalid('The publisher has to be specified to use a GelfHandler') 831 | ->end() 832 | ; 833 | } 834 | 835 | private function addMongoSection(ArrayNodeDefinition $handerNode) 836 | { 837 | $handerNode 838 | ->children() 839 | ->arrayNode('mongo') 840 | ->canBeUnset() 841 | ->beforeNormalization() 842 | ->ifString() 843 | ->then(function ($v) { return ['id' => $v]; }) 844 | ->end() 845 | ->children() 846 | ->scalarNode('id')->end() 847 | ->scalarNode('host')->end() 848 | ->scalarNode('port')->defaultValue(27017)->end() 849 | ->scalarNode('user')->end() 850 | ->scalarNode('pass')->end() 851 | ->scalarNode('database')->defaultValue('monolog')->end() 852 | ->scalarNode('collection')->defaultValue('logs')->end() 853 | ->end() 854 | ->validate() 855 | ->ifTrue(function ($v) { 856 | return !isset($v['id']) && !isset($v['host']); 857 | }) 858 | ->thenInvalid('What must be set is either the host or the id.') 859 | ->end() 860 | ->validate() 861 | ->ifTrue(function ($v) { 862 | return isset($v['user']) && !isset($v['pass']); 863 | }) 864 | ->thenInvalid('If you set user, you must provide a password.') 865 | ->end() 866 | ->end() 867 | ->end() 868 | ->validate() 869 | ->ifTrue(function ($v) { return 'mongo' === $v['type'] && !isset($v['mongo']); }) 870 | ->thenInvalid('The mongo configuration has to be specified to use a MongoHandler') 871 | ->end() 872 | ; 873 | } 874 | 875 | private function addElasticsearchSection(ArrayNodeDefinition $handerNode) 876 | { 877 | $handerNode 878 | ->children() 879 | ->arrayNode('elasticsearch') 880 | ->canBeUnset() 881 | ->beforeNormalization() 882 | ->ifString() 883 | ->then(function ($v) { return ['id' => $v]; }) 884 | ->end() 885 | ->children() 886 | ->scalarNode('id')->end() 887 | ->scalarNode('host')->end() 888 | ->scalarNode('port')->defaultValue(9200)->end() 889 | ->scalarNode('transport')->defaultValue('Http')->end() 890 | ->scalarNode('user')->defaultNull()->end() 891 | ->scalarNode('password')->defaultNull()->end() 892 | ->end() 893 | ->validate() 894 | ->ifTrue(function ($v) { 895 | return !isset($v['id']) && !isset($v['host']); 896 | }) 897 | ->thenInvalid('What must be set is either the host or the id.') 898 | ->end() 899 | ->end() 900 | ->scalarNode('index')->defaultValue('monolog')->end() // elasticsearch & elastic_search & elastica 901 | ->scalarNode('document_type')->defaultValue('logs')->end() // elasticsearch & elastic_search & elastica 902 | ->scalarNode('ignore_error')->defaultValue(false)->end() // elasticsearch & elastic_search & elastica 903 | ->end() 904 | ; 905 | } 906 | 907 | private function addRedisSection(ArrayNodeDefinition $handerNode) 908 | { 909 | $handerNode 910 | ->children() 911 | ->arrayNode('redis') 912 | ->canBeUnset() 913 | ->beforeNormalization() 914 | ->ifString() 915 | ->then(function ($v) { return ['id' => $v]; }) 916 | ->end() 917 | ->children() 918 | ->scalarNode('id')->end() 919 | ->scalarNode('host')->end() 920 | ->scalarNode('password')->defaultNull()->end() 921 | ->scalarNode('port')->defaultValue(6379)->end() 922 | ->scalarNode('database')->defaultValue(0)->end() 923 | ->scalarNode('key_name')->defaultValue('monolog_redis')->end() 924 | ->end() 925 | ->validate() 926 | ->ifTrue(function ($v) { 927 | return !isset($v['id']) && !isset($v['host']); 928 | }) 929 | ->thenInvalid('What must be set is either the host or the service id of the Redis client.') 930 | ->end() 931 | ->end() 932 | ->end() 933 | ->validate() 934 | ->ifTrue(function ($v) { return 'redis' === $v['type'] && empty($v['redis']); }) 935 | ->thenInvalid('The host has to be specified to use a RedisLogHandler') 936 | ->end() 937 | ; 938 | } 939 | 940 | private function addPredisSection(ArrayNodeDefinition $handerNode) 941 | { 942 | $handerNode 943 | ->children() 944 | ->arrayNode('predis') 945 | ->canBeUnset() 946 | ->beforeNormalization() 947 | ->ifString() 948 | ->then(function ($v) { return ['id' => $v]; }) 949 | ->end() 950 | ->children() 951 | ->scalarNode('id')->end() 952 | ->scalarNode('host')->end() 953 | ->end() 954 | ->validate() 955 | ->ifTrue(function ($v) { 956 | return !isset($v['id']) && !isset($v['host']); 957 | }) 958 | ->thenInvalid('What must be set is either the host or the service id of the Predis client.') 959 | ->end() 960 | ->end() 961 | ->end() 962 | ->validate() 963 | ->ifTrue(function ($v) { return 'predis' === $v['type'] && empty($v['redis']); }) 964 | ->thenInvalid('The host has to be specified to use a RedisLogHandler') 965 | ->end() 966 | ; 967 | } 968 | 969 | private function addMailerSection(ArrayNodeDefinition $handerNode) 970 | { 971 | $handerNode 972 | ->children() 973 | ->scalarNode('from_email')->end() // swift_mailer, native_mailer, symfony_mailer and flowdock 974 | ->arrayNode('to_email') // swift_mailer, native_mailer and symfony_mailer 975 | ->prototype('scalar')->end() 976 | ->beforeNormalization() 977 | ->ifString() 978 | ->then(function ($v) { return [$v]; }) 979 | ->end() 980 | ->end() 981 | ->scalarNode('subject')->end() // swift_mailer, native_mailer and symfony_mailer 982 | ->scalarNode('content_type')->defaultNull()->end() // swift_mailer and symfony_mailer 983 | ->arrayNode('headers') // native_mailer 984 | ->canBeUnset() 985 | ->scalarPrototype()->end() 986 | ->end() 987 | ->scalarNode('mailer')->defaultNull()->end() // swift_mailer and symfony_mailer 988 | ->arrayNode('email_prototype') // swift_mailer and symfony_mailer 989 | ->canBeUnset() 990 | ->beforeNormalization() 991 | ->ifString() 992 | ->then(function ($v) { return ['id' => $v]; }) 993 | ->end() 994 | ->children() 995 | ->scalarNode('id')->isRequired()->end() 996 | ->scalarNode('method')->defaultNull()->end() 997 | ->end() 998 | ->end() 999 | ->booleanNode('lazy')->defaultValue(true)->end() // swift_mailer 1000 | ->end() 1001 | ->validate() 1002 | ->ifTrue(function ($v) { return 'swift_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); }) 1003 | ->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use a SwiftMailerHandler') 1004 | ->end() 1005 | ->validate() 1006 | ->ifTrue(function ($v) { return 'native_mailer' === $v['type'] && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); }) 1007 | ->thenInvalid('The sender, recipient and subject have to be specified to use a NativeMailerHandler') 1008 | ->end() 1009 | ->validate() 1010 | ->ifTrue(function ($v) { return 'symfony_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); }) 1011 | ->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use the Symfony MailerHandler') 1012 | ->end() 1013 | ; 1014 | } 1015 | 1016 | private function addVerbosityLevelSection(ArrayNodeDefinition $handerNode) 1017 | { 1018 | $handerNode 1019 | ->children() 1020 | ->arrayNode('verbosity_levels') // console 1021 | ->beforeNormalization() 1022 | ->ifArray() 1023 | ->then(function ($v) { 1024 | $map = []; 1025 | $verbosities = ['VERBOSITY_QUIET', 'VERBOSITY_NORMAL', 'VERBOSITY_VERBOSE', 'VERBOSITY_VERY_VERBOSE', 'VERBOSITY_DEBUG']; 1026 | // allow numeric indexed array with ascendning verbosity and lowercase names of the constants 1027 | foreach ($v as $verbosity => $level) { 1028 | if (\is_int($verbosity) && isset($verbosities[$verbosity])) { 1029 | $map[$verbosities[$verbosity]] = strtoupper($level); 1030 | } else { 1031 | $map[strtoupper($verbosity)] = strtoupper($level); 1032 | } 1033 | } 1034 | 1035 | return $map; 1036 | }) 1037 | ->end() 1038 | ->children() 1039 | ->scalarNode('VERBOSITY_QUIET')->defaultValue('ERROR')->end() 1040 | ->scalarNode('VERBOSITY_NORMAL')->defaultValue('WARNING')->end() 1041 | ->scalarNode('VERBOSITY_VERBOSE')->defaultValue('NOTICE')->end() 1042 | ->scalarNode('VERBOSITY_VERY_VERBOSE')->defaultValue('INFO')->end() 1043 | ->scalarNode('VERBOSITY_DEBUG')->defaultValue('DEBUG')->end() 1044 | ->end() 1045 | ->validate() 1046 | ->always(function ($v) { 1047 | $map = []; 1048 | foreach ($v as $verbosity => $level) { 1049 | $verbosityConstant = 'Symfony\Component\Console\Output\OutputInterface::'.$verbosity; 1050 | 1051 | if (!\defined($verbosityConstant)) { 1052 | throw new InvalidConfigurationException(\sprintf('The configured verbosity "%s" is invalid as it is not defined in Symfony\Component\Console\Output\OutputInterface.', $verbosity)); 1053 | } 1054 | 1055 | try { 1056 | if (Logger::API === 3) { 1057 | $level = Logger::toMonologLevel($level)->value; 1058 | } else { 1059 | $level = Logger::toMonologLevel(is_numeric($level) ? (int) $level : $level); 1060 | } 1061 | } catch (\Psr\Log\InvalidArgumentException $e) { 1062 | throw new InvalidConfigurationException(\sprintf('The configured minimum log level "%s" for verbosity "%s" is invalid as it is not defined in Monolog\Logger.', $level, $verbosity)); 1063 | } 1064 | 1065 | $map[\constant($verbosityConstant)] = $level; 1066 | } 1067 | 1068 | return $map; 1069 | }) 1070 | ->end() 1071 | ->end() 1072 | ->end() 1073 | ; 1074 | } 1075 | 1076 | private function addChannelsSection(ArrayNodeDefinition $handerNode) 1077 | { 1078 | $handerNode 1079 | ->children() 1080 | ->arrayNode('channels') 1081 | ->fixXmlConfig('channel', 'elements') 1082 | ->canBeUnset() 1083 | ->beforeNormalization() 1084 | ->ifString() 1085 | ->then(function ($v) { return ['elements' => [$v]]; }) 1086 | ->end() 1087 | ->beforeNormalization() 1088 | ->ifTrue(function ($v) { return \is_array($v) && is_numeric(key($v)); }) 1089 | ->then(function ($v) { return ['elements' => $v]; }) 1090 | ->end() 1091 | ->validate() 1092 | ->ifTrue(function ($v) { return empty($v); }) 1093 | ->thenUnset() 1094 | ->end() 1095 | ->validate() 1096 | ->always(function ($v) { 1097 | $isExclusive = null; 1098 | if (isset($v['type'])) { 1099 | $isExclusive = 'exclusive' === $v['type']; 1100 | } 1101 | 1102 | $elements = []; 1103 | foreach ($v['elements'] as $element) { 1104 | if (0 === strpos($element, '!')) { 1105 | if (false === $isExclusive) { 1106 | throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list.'); 1107 | } 1108 | $elements[] = substr($element, 1); 1109 | $isExclusive = true; 1110 | } else { 1111 | if (true === $isExclusive) { 1112 | throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list'); 1113 | } 1114 | $elements[] = $element; 1115 | $isExclusive = false; 1116 | } 1117 | } 1118 | 1119 | if (!\count($elements)) { 1120 | return null; 1121 | } 1122 | 1123 | // de-duplicating $elements here in case the handlers are redefined, see https://github.com/symfony/monolog-bundle/issues/433 1124 | return ['type' => $isExclusive ? 'exclusive' : 'inclusive', 'elements' => array_unique($elements)]; 1125 | }) 1126 | ->end() 1127 | ->children() 1128 | ->scalarNode('type') 1129 | ->validate() 1130 | ->ifNotInArray(['inclusive', 'exclusive']) 1131 | ->thenInvalid('The type of channels has to be inclusive or exclusive') 1132 | ->end() 1133 | ->end() 1134 | ->arrayNode('elements') 1135 | ->prototype('scalar')->end() 1136 | ->end() 1137 | ->end() 1138 | ->end() 1139 | ->end() 1140 | ; 1141 | } 1142 | } 1143 | -------------------------------------------------------------------------------- /DependencyInjection/MonologExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\DependencyInjection; 13 | 14 | use Monolog\Attribute\AsMonologProcessor; 15 | use Monolog\Attribute\WithMonologChannel; 16 | use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; 17 | use Monolog\Handler\HandlerInterface; 18 | use Monolog\Logger; 19 | use Monolog\Processor\ProcessorInterface; 20 | use Monolog\Processor\PsrLogMessageProcessor; 21 | use Monolog\ResettableInterface; 22 | use Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy; 23 | use Symfony\Bridge\Monolog\Logger as LegacyLogger; 24 | use Symfony\Bridge\Monolog\Processor\SwitchUserTokenProcessor; 25 | use Symfony\Bridge\Monolog\Processor\TokenProcessor; 26 | use Symfony\Bridge\Monolog\Processor\WebProcessor; 27 | use Symfony\Component\Config\FileLocator; 28 | use Symfony\Component\DependencyInjection\Argument\BoundArgument; 29 | use Symfony\Component\DependencyInjection\ChildDefinition; 30 | use Symfony\Component\DependencyInjection\ContainerBuilder; 31 | use Symfony\Component\DependencyInjection\Definition; 32 | use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; 33 | use Symfony\Component\DependencyInjection\Reference; 34 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 35 | use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; 36 | use Symfony\Contracts\HttpClient\HttpClientInterface; 37 | 38 | /** 39 | * MonologExtension is an extension for the Monolog library. 40 | * 41 | * @author Jordi Boggiano 42 | * @author Christophe Coevoet 43 | * 44 | * @final since 3.9.0 45 | */ 46 | class MonologExtension extends Extension 47 | { 48 | private $nestedHandlers = []; 49 | 50 | private $swiftMailerHandlers = []; 51 | 52 | /** 53 | * Loads the Monolog configuration. 54 | * 55 | * @param array $configs An array of configuration settings 56 | * @param ContainerBuilder $container A ContainerBuilder instance 57 | */ 58 | public function load(array $configs, ContainerBuilder $container) 59 | { 60 | $configuration = $this->getConfiguration($configs, $container); 61 | $config = $this->processConfiguration($configuration, $configs); 62 | 63 | if (isset($config['handlers'])) { 64 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 65 | $loader->load('monolog.xml'); 66 | 67 | if (!class_exists(DebugLoggerConfigurator::class)) { 68 | $container->getDefinition('monolog.logger_prototype')->setClass(LegacyLogger::class); 69 | } 70 | 71 | $container->setParameter('monolog.use_microseconds', $config['use_microseconds']); 72 | 73 | $handlers = []; 74 | 75 | foreach ($config['handlers'] as $name => $handler) { 76 | $handlers[$handler['priority']][] = [ 77 | 'id' => $this->buildHandler($container, $name, $handler), 78 | 'channels' => empty($handler['channels']) ? null : $handler['channels'], 79 | ]; 80 | } 81 | 82 | $container->setParameter( 83 | 'monolog.swift_mailer.handlers', 84 | $this->swiftMailerHandlers 85 | ); 86 | 87 | ksort($handlers); 88 | $sortedHandlers = []; 89 | foreach ($handlers as $priorityHandlers) { 90 | foreach (array_reverse($priorityHandlers) as $handler) { 91 | $sortedHandlers[] = $handler; 92 | } 93 | } 94 | 95 | $handlersToChannels = []; 96 | foreach ($sortedHandlers as $handler) { 97 | if (!\in_array($handler['id'], $this->nestedHandlers)) { 98 | $handlersToChannels[$handler['id']] = $handler['channels']; 99 | } 100 | } 101 | $container->setParameter('monolog.handlers_to_channels', $handlersToChannels); 102 | } 103 | 104 | $container->setParameter('monolog.additional_channels', $config['channels'] ?? []); 105 | 106 | if (interface_exists(ProcessorInterface::class)) { 107 | $container->registerForAutoconfiguration(ProcessorInterface::class) 108 | ->addTag('monolog.processor'); 109 | } else { 110 | $container->registerForAutoconfiguration(WebProcessor::class) 111 | ->addTag('monolog.processor'); 112 | } 113 | if (interface_exists(ResettableInterface::class)) { 114 | $container->registerForAutoconfiguration(ResettableInterface::class) 115 | ->addTag('kernel.reset', ['method' => 'reset']); 116 | } 117 | $container->registerForAutoconfiguration(TokenProcessor::class) 118 | ->addTag('monolog.processor'); 119 | if (interface_exists(HttpClientInterface::class)) { 120 | $handlerAutoconfiguration = $container->registerForAutoconfiguration(HandlerInterface::class); 121 | $handlerAutoconfiguration->setBindings($handlerAutoconfiguration->getBindings() + [ 122 | HttpClientInterface::class => new BoundArgument(new Reference('monolog.http_client'), false), 123 | ]); 124 | } 125 | 126 | if (80000 <= \PHP_VERSION_ID) { 127 | $container->registerAttributeForAutoconfiguration(AsMonologProcessor::class, static function (ChildDefinition $definition, AsMonologProcessor $attribute, \Reflector $reflector): void { 128 | $tagAttributes = get_object_vars($attribute); 129 | if ($reflector instanceof \ReflectionMethod) { 130 | if (isset($tagAttributes['method'])) { 131 | throw new \LogicException(\sprintf('AsMonologProcessor attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); 132 | } 133 | 134 | $tagAttributes['method'] = $reflector->getName(); 135 | } 136 | 137 | $definition->addTag('monolog.processor', $tagAttributes); 138 | }); 139 | $container->registerAttributeForAutoconfiguration(WithMonologChannel::class, static function (ChildDefinition $definition, WithMonologChannel $attribute): void { 140 | $definition->addTag('monolog.logger', ['channel' => $attribute->channel]); 141 | }); 142 | } 143 | } 144 | 145 | /** 146 | * Returns the base path for the XSD files. 147 | * 148 | * @return string The XSD base path 149 | */ 150 | public function getXsdValidationBasePath() 151 | { 152 | return __DIR__.'/../Resources/config/schema'; 153 | } 154 | 155 | public function getNamespace() 156 | { 157 | return 'http://symfony.com/schema/dic/monolog'; 158 | } 159 | 160 | private function buildHandler(ContainerBuilder $container, $name, array $handler) 161 | { 162 | $handlerId = $this->getHandlerId($name); 163 | if ('service' === $handler['type']) { 164 | $container->setAlias($handlerId, $handler['id']); 165 | 166 | if (!empty($handler['nested']) && true === $handler['nested']) { 167 | $this->markNestedHandler($handlerId); 168 | } 169 | 170 | return $handlerId; 171 | } 172 | 173 | $handlerClass = $this->getHandlerClassByType($handler['type']); 174 | $definition = new Definition($handlerClass); 175 | 176 | if ($handler['include_stacktraces']) { 177 | $definition->setConfigurator(['Symfony\\Bundle\\MonologBundle\\MonologBundle', 'includeStacktraces']); 178 | } 179 | 180 | if (null === $handler['process_psr_3_messages']['enabled']) { 181 | $handler['process_psr_3_messages']['enabled'] = !isset($handler['handler']) && !$handler['members']; 182 | } 183 | 184 | if ($handler['process_psr_3_messages']['enabled'] && method_exists($handlerClass, 'pushProcessor')) { 185 | $processorId = $this->buildPsrLogMessageProcessor($container, $handler['process_psr_3_messages']); 186 | $definition->addMethodCall('pushProcessor', [new Reference($processorId)]); 187 | } 188 | 189 | switch ($handler['type']) { 190 | case 'stream': 191 | $definition->setArguments([ 192 | $handler['path'], 193 | $handler['level'], 194 | $handler['bubble'], 195 | $handler['file_permission'], 196 | $handler['use_locking'], 197 | ]); 198 | break; 199 | 200 | case 'console': 201 | $definition->setArguments([ 202 | null, 203 | $handler['bubble'], 204 | $handler['verbosity_levels'] ?? [], 205 | $handler['console_formatter_options'], 206 | ]); 207 | $definition->addTag('kernel.event_subscriber'); 208 | break; 209 | 210 | case 'chromephp': 211 | case 'firephp': 212 | $definition->setArguments([ 213 | $handler['level'], 214 | $handler['bubble'], 215 | ]); 216 | $definition->addTag('kernel.event_listener', ['event' => 'kernel.response', 'method' => 'onKernelResponse']); 217 | break; 218 | 219 | case 'gelf': 220 | if (isset($handler['publisher']['id'])) { 221 | $publisher = new Reference($handler['publisher']['id']); 222 | } elseif (class_exists('Gelf\Transport\UdpTransport')) { 223 | $transport = new Definition("Gelf\Transport\UdpTransport", [ 224 | $handler['publisher']['hostname'], 225 | $handler['publisher']['port'], 226 | $handler['publisher']['chunk_size'], 227 | ]); 228 | $transport->setPublic(false); 229 | 230 | $publisher = new Definition('Gelf\Publisher', []); 231 | $publisher->addMethodCall('addTransport', [$transport]); 232 | $publisher->setPublic(false); 233 | } elseif (class_exists('Gelf\MessagePublisher')) { 234 | $publisher = new Definition('Gelf\MessagePublisher', [ 235 | $handler['publisher']['hostname'], 236 | $handler['publisher']['port'], 237 | $handler['publisher']['chunk_size'], 238 | ]); 239 | 240 | $publisher->setPublic(false); 241 | } else { 242 | throw new \RuntimeException('The gelf handler requires the graylog2/gelf-php package to be installed'); 243 | } 244 | 245 | $definition->setArguments([ 246 | $publisher, 247 | $handler['level'], 248 | $handler['bubble'], 249 | ]); 250 | break; 251 | 252 | case 'mongo': 253 | if (isset($handler['mongo']['id'])) { 254 | $client = new Reference($handler['mongo']['id']); 255 | } else { 256 | $server = 'mongodb://'; 257 | 258 | if (isset($handler['mongo']['user'])) { 259 | $server .= $handler['mongo']['user'].':'.$handler['mongo']['pass'].'@'; 260 | } 261 | 262 | $server .= $handler['mongo']['host'].':'.$handler['mongo']['port']; 263 | 264 | $client = new Definition('MongoDB\Client', [ 265 | $server, 266 | ]); 267 | 268 | $client->setPublic(false); 269 | } 270 | 271 | $definition->setArguments([ 272 | $client, 273 | $handler['mongo']['database'], 274 | $handler['mongo']['collection'], 275 | $handler['level'], 276 | $handler['bubble'], 277 | ]); 278 | break; 279 | 280 | case 'elasticsearch': 281 | @trigger_error('The "elasticsearch" handler type is deprecated in MonologBundle since version 3.8.0, use the "elastica" type instead, or switch to the official Elastic client using the "elastic_search" type.', \E_USER_DEPRECATED); 282 | // no break 283 | 284 | case 'elastica': 285 | case 'elastic_search': 286 | if (isset($handler['elasticsearch']['id'])) { 287 | $client = new Reference($handler['elasticsearch']['id']); 288 | } else { 289 | if ('elastic_search' === $handler['type']) { 290 | // v8 has a new Elastic\ prefix 291 | $client = new Definition(class_exists('Elastic\Elasticsearch\Client') ? 'Elastic\Elasticsearch\Client' : 'Elasticsearch\Client'); 292 | $factory = class_exists('Elastic\Elasticsearch\ClientBuilder') ? 'Elastic\Elasticsearch\ClientBuilder' : 'Elasticsearch\ClientBuilder'; 293 | $client->setFactory([$factory, 'fromConfig']); 294 | $clientArguments = [ 295 | 'host' => $handler['elasticsearch']['host'], 296 | ]; 297 | 298 | if (isset($handler['elasticsearch']['user'], $handler['elasticsearch']['password'])) { 299 | $clientArguments['basicAuthentication'] = [$handler['elasticsearch']['user'], $handler['elasticsearch']['password']]; 300 | } 301 | } else { 302 | $client = new Definition('Elastica\Client'); 303 | 304 | $clientArguments = [ 305 | 'host' => $handler['elasticsearch']['host'], 306 | 'port' => $handler['elasticsearch']['port'], 307 | 'transport' => $handler['elasticsearch']['transport'], 308 | ]; 309 | 310 | if (isset($handler['elasticsearch']['user'], $handler['elasticsearch']['password'])) { 311 | $clientArguments['headers'] = [ 312 | 'Authorization' => 'Basic '.base64_encode($handler['elasticsearch']['user'].':'.$handler['elasticsearch']['password']), 313 | ]; 314 | } 315 | } 316 | 317 | $client->setArguments([ 318 | $clientArguments, 319 | ]); 320 | 321 | $client->setPublic(false); 322 | } 323 | 324 | // elastica handler definition 325 | $definition->setArguments([ 326 | $client, 327 | [ 328 | 'index' => $handler['index'], 329 | 'type' => $handler['document_type'], 330 | 'ignore_error' => $handler['ignore_error'], 331 | ], 332 | $handler['level'], 333 | $handler['bubble'], 334 | ]); 335 | break; 336 | 337 | case 'telegram': 338 | if (!class_exists('Monolog\Handler\TelegramBotHandler')) { 339 | throw new \RuntimeException('The TelegramBotHandler is not available. Please update "monolog/monolog" to 2.2.0'); 340 | } 341 | 342 | $definition->setArguments([ 343 | $handler['token'], 344 | $handler['channel'], 345 | $handler['level'], 346 | $handler['bubble'], 347 | $handler['parse_mode'], 348 | $handler['disable_webpage_preview'], 349 | $handler['disable_notification'], 350 | $handler['split_long_messages'], 351 | $handler['delay_between_messages'], 352 | ]); 353 | break; 354 | 355 | case 'redis': 356 | case 'predis': 357 | if (isset($handler['redis']['id'])) { 358 | $clientId = $handler['redis']['id']; 359 | } elseif ('redis' === $handler['type']) { 360 | if (!class_exists(\Redis::class)) { 361 | throw new \RuntimeException('The \Redis class is not available.'); 362 | } 363 | 364 | $client = new Definition(\Redis::class); 365 | $client->addMethodCall('connect', [$handler['redis']['host'], $handler['redis']['port']]); 366 | $client->addMethodCall('auth', [$handler['redis']['password']]); 367 | $client->addMethodCall('select', [$handler['redis']['database']]); 368 | $client->setPublic(false); 369 | $clientId = uniqid('monolog.redis.client.', true); 370 | $container->setDefinition($clientId, $client); 371 | } else { 372 | if (!class_exists(\Predis\Client::class)) { 373 | throw new \RuntimeException('The \Predis\Client class is not available.'); 374 | } 375 | 376 | $client = new Definition(\Predis\Client::class); 377 | $client->setArguments([ 378 | $handler['redis']['host'], 379 | ]); 380 | $client->setPublic(false); 381 | 382 | $clientId = uniqid('monolog.predis.client.', true); 383 | $container->setDefinition($clientId, $client); 384 | } 385 | $definition->setArguments([ 386 | new Reference($clientId), 387 | $handler['redis']['key_name'], 388 | $handler['level'], 389 | $handler['bubble'], 390 | ]); 391 | break; 392 | 393 | case 'rotating_file': 394 | $definition->setArguments([ 395 | $handler['path'], 396 | $handler['max_files'], 397 | $handler['level'], 398 | $handler['bubble'], 399 | $handler['file_permission'], 400 | $handler['use_locking'], 401 | ]); 402 | $definition->addMethodCall('setFilenameFormat', [ 403 | $handler['filename_format'], 404 | $handler['date_format'], 405 | ]); 406 | break; 407 | 408 | case 'fingers_crossed': 409 | $nestedHandlerId = $this->getHandlerId($handler['handler']); 410 | $this->markNestedHandler($nestedHandlerId); 411 | 412 | $activation = $handler['action_level']; 413 | if (class_exists(SwitchUserTokenProcessor::class)) { 414 | $activation = new Definition(ErrorLevelActivationStrategy::class, [$activation]); 415 | } 416 | 417 | if (isset($handler['activation_strategy'])) { 418 | $activation = new Reference($handler['activation_strategy']); 419 | } elseif (!empty($handler['excluded_404s'])) { 420 | if (class_exists(HttpCodeActivationStrategy::class)) { 421 | @trigger_error('The "excluded_404s" option is deprecated in MonologBundle since version 3.4.0, you should rely on the "excluded_http_codes" option instead.', \E_USER_DEPRECATED); 422 | } 423 | $activationDef = new Definition('Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy', [ 424 | new Reference('request_stack'), 425 | $handler['excluded_404s'], 426 | $activation, 427 | ]); 428 | $container->setDefinition($handlerId.'.not_found_strategy', $activationDef); 429 | $activation = new Reference($handlerId.'.not_found_strategy'); 430 | } elseif (!empty($handler['excluded_http_codes'])) { 431 | $activationDef = new Definition('Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy', [ 432 | new Reference('request_stack'), 433 | $handler['excluded_http_codes'], 434 | $activation, 435 | ]); 436 | $container->setDefinition($handlerId.'.http_code_strategy', $activationDef); 437 | $activation = new Reference($handlerId.'.http_code_strategy'); 438 | } 439 | 440 | $definition->setArguments([ 441 | new Reference($nestedHandlerId), 442 | $activation, 443 | $handler['buffer_size'], 444 | $handler['bubble'], 445 | $handler['stop_buffering'], 446 | $handler['passthru_level'], 447 | ]); 448 | break; 449 | 450 | case 'filter': 451 | $nestedHandlerId = $this->getHandlerId($handler['handler']); 452 | $this->markNestedHandler($nestedHandlerId); 453 | $minLevelOrList = !empty($handler['accepted_levels']) ? $handler['accepted_levels'] : $handler['min_level']; 454 | 455 | $definition->setArguments([ 456 | new Reference($nestedHandlerId), 457 | $minLevelOrList, 458 | $handler['max_level'], 459 | $handler['bubble'], 460 | ]); 461 | break; 462 | 463 | case 'buffer': 464 | $nestedHandlerId = $this->getHandlerId($handler['handler']); 465 | $this->markNestedHandler($nestedHandlerId); 466 | 467 | $definition->setArguments([ 468 | new Reference($nestedHandlerId), 469 | $handler['buffer_size'], 470 | $handler['level'], 471 | $handler['bubble'], 472 | $handler['flush_on_overflow'], 473 | ]); 474 | break; 475 | 476 | case 'deduplication': 477 | $nestedHandlerId = $this->getHandlerId($handler['handler']); 478 | $this->markNestedHandler($nestedHandlerId); 479 | $defaultStore = '%kernel.cache_dir%/monolog_dedup_'.sha1($handlerId); 480 | 481 | $definition->setArguments([ 482 | new Reference($nestedHandlerId), 483 | $handler['store'] ?? $defaultStore, 484 | $handler['deduplication_level'], 485 | $handler['time'], 486 | $handler['bubble'], 487 | ]); 488 | break; 489 | 490 | case 'group': 491 | case 'whatfailuregroup': 492 | case 'fallbackgroup': 493 | $references = []; 494 | foreach ($handler['members'] as $nestedHandler) { 495 | $nestedHandlerId = $this->getHandlerId($nestedHandler); 496 | $this->markNestedHandler($nestedHandlerId); 497 | $references[] = new Reference($nestedHandlerId); 498 | } 499 | 500 | $definition->setArguments([ 501 | $references, 502 | $handler['bubble'], 503 | ]); 504 | break; 505 | 506 | case 'syslog': 507 | $definition->setArguments([ 508 | $handler['ident'], 509 | $handler['facility'], 510 | $handler['level'], 511 | $handler['bubble'], 512 | $handler['logopts'], 513 | ]); 514 | break; 515 | 516 | case 'syslogudp': 517 | $definition->setArguments([ 518 | $handler['host'], 519 | $handler['port'], 520 | $handler['facility'], 521 | $handler['level'], 522 | $handler['bubble'], 523 | ]); 524 | if ($handler['ident']) { 525 | $definition->addArgument($handler['ident']); 526 | } 527 | break; 528 | 529 | case 'swift_mailer': 530 | $mailer = $handler['mailer'] ?: 'mailer'; 531 | if (isset($handler['email_prototype'])) { 532 | if (!empty($handler['email_prototype']['method'])) { 533 | $prototype = [new Reference($handler['email_prototype']['id']), $handler['email_prototype']['method']]; 534 | } else { 535 | $prototype = new Reference($handler['email_prototype']['id']); 536 | } 537 | } else { 538 | $messageFactory = new Definition('Symfony\Bundle\MonologBundle\SwiftMailer\MessageFactory'); 539 | $messageFactory->setLazy(true); 540 | $messageFactory->setPublic(false); 541 | $messageFactory->setArguments([ 542 | new Reference($mailer), 543 | $handler['from_email'], 544 | $handler['to_email'], 545 | $handler['subject'], 546 | $handler['content_type'], 547 | ]); 548 | 549 | $messageFactoryId = \sprintf('%s.mail_message_factory', $handlerId); 550 | $container->setDefinition($messageFactoryId, $messageFactory); 551 | // set the prototype as a callable 552 | $prototype = [new Reference($messageFactoryId), 'createMessage']; 553 | } 554 | $definition->setArguments([ 555 | new Reference($mailer), 556 | $prototype, 557 | $handler['level'], 558 | $handler['bubble'], 559 | ]); 560 | 561 | $this->swiftMailerHandlers[] = $handlerId; 562 | $definition->addTag('kernel.event_listener', ['event' => 'kernel.terminate', 'method' => 'onKernelTerminate']); 563 | $definition->addTag('kernel.event_listener', ['event' => 'console.terminate', 'method' => 'onCliTerminate']); 564 | break; 565 | 566 | case 'native_mailer': 567 | $definition->setArguments([ 568 | $handler['to_email'], 569 | $handler['subject'], 570 | $handler['from_email'], 571 | $handler['level'], 572 | $handler['bubble'], 573 | ]); 574 | if (!empty($handler['headers'])) { 575 | $definition->addMethodCall('addHeader', [$handler['headers']]); 576 | } 577 | break; 578 | 579 | case 'symfony_mailer': 580 | $mailer = $handler['mailer'] ?: 'mailer.mailer'; 581 | if (isset($handler['email_prototype'])) { 582 | if (!empty($handler['email_prototype']['method'])) { 583 | $prototype = [new Reference($handler['email_prototype']['id']), $handler['email_prototype']['method']]; 584 | } else { 585 | $prototype = new Reference($handler['email_prototype']['id']); 586 | } 587 | } else { 588 | $prototype = (new Definition('Symfony\Component\Mime\Email')) 589 | ->setPublic(false) 590 | ->addMethodCall('from', [$handler['from_email']]) 591 | ->addMethodCall('to', $handler['to_email']) 592 | ->addMethodCall('subject', [$handler['subject']]); 593 | } 594 | $definition->setArguments([ 595 | new Reference($mailer), 596 | $prototype, 597 | $handler['level'], 598 | $handler['bubble'], 599 | ]); 600 | break; 601 | 602 | case 'socket': 603 | $definition->setArguments([ 604 | $handler['connection_string'], 605 | $handler['level'], 606 | $handler['bubble'], 607 | ]); 608 | if (isset($handler['timeout'])) { 609 | $definition->addMethodCall('setTimeout', [$handler['timeout']]); 610 | } 611 | if (isset($handler['connection_timeout'])) { 612 | $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); 613 | } 614 | if (isset($handler['persistent'])) { 615 | $definition->addMethodCall('setPersistent', [$handler['persistent']]); 616 | } 617 | break; 618 | 619 | case 'pushover': 620 | $definition->setArguments([ 621 | $handler['token'], 622 | $handler['user'], 623 | $handler['title'], 624 | $handler['level'], 625 | $handler['bubble'], 626 | ]); 627 | if (isset($handler['timeout'])) { 628 | $definition->addMethodCall('setTimeout', [$handler['timeout']]); 629 | } 630 | if (isset($handler['connection_timeout'])) { 631 | $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); 632 | } 633 | break; 634 | 635 | case 'hipchat': 636 | $definition->setArguments([ 637 | $handler['token'], 638 | $handler['room'], 639 | $handler['nickname'], 640 | $handler['notify'], 641 | $handler['level'], 642 | $handler['bubble'], 643 | $handler['use_ssl'], 644 | $handler['message_format'], 645 | !empty($handler['host']) ? $handler['host'] : 'api.hipchat.com', 646 | !empty($handler['api_version']) ? $handler['api_version'] : 'v1', 647 | ]); 648 | if (isset($handler['timeout'])) { 649 | $definition->addMethodCall('setTimeout', [$handler['timeout']]); 650 | } 651 | if (isset($handler['connection_timeout'])) { 652 | $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); 653 | } 654 | break; 655 | 656 | case 'slack': 657 | $definition->setArguments([ 658 | $handler['token'], 659 | $handler['channel'], 660 | $handler['bot_name'], 661 | $handler['use_attachment'], 662 | $handler['icon_emoji'], 663 | $handler['level'], 664 | $handler['bubble'], 665 | $handler['use_short_attachment'], 666 | $handler['include_extra'], 667 | ]); 668 | if (isset($handler['timeout'])) { 669 | $definition->addMethodCall('setTimeout', [$handler['timeout']]); 670 | } 671 | if (isset($handler['connection_timeout'])) { 672 | $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); 673 | } 674 | break; 675 | 676 | case 'slackwebhook': 677 | $definition->setArguments([ 678 | $handler['webhook_url'], 679 | $handler['channel'], 680 | $handler['bot_name'], 681 | $handler['use_attachment'], 682 | $handler['icon_emoji'], 683 | $handler['use_short_attachment'], 684 | $handler['include_extra'], 685 | $handler['level'], 686 | $handler['bubble'], 687 | ]); 688 | break; 689 | 690 | case 'slackbot': 691 | $definition->setArguments([ 692 | $handler['team'], 693 | $handler['token'], 694 | urlencode($handler['channel']), 695 | $handler['level'], 696 | $handler['bubble'], 697 | ]); 698 | break; 699 | 700 | case 'cube': 701 | $definition->setArguments([ 702 | $handler['url'], 703 | $handler['level'], 704 | $handler['bubble'], 705 | ]); 706 | break; 707 | 708 | case 'amqp': 709 | $definition->setArguments([ 710 | new Reference($handler['exchange']), 711 | $handler['exchange_name'], 712 | $handler['level'], 713 | $handler['bubble'], 714 | ]); 715 | break; 716 | 717 | case 'error_log': 718 | $definition->setArguments([ 719 | $handler['message_type'], 720 | $handler['level'], 721 | $handler['bubble'], 722 | ]); 723 | break; 724 | 725 | case 'sentry': 726 | if (null !== $handler['hub_id']) { 727 | $hub = new Reference($handler['hub_id']); 728 | } else { 729 | if (null !== $handler['client_id']) { 730 | $clientId = $handler['client_id']; 731 | } else { 732 | $options = new Definition( 733 | 'Sentry\\Options', 734 | [['dsn' => $handler['dsn']]] 735 | ); 736 | 737 | if (!empty($handler['environment'])) { 738 | $options->addMethodCall('setEnvironment', [$handler['environment']]); 739 | } 740 | 741 | if (!empty($handler['release'])) { 742 | $options->addMethodCall('setRelease', [$handler['release']]); 743 | } 744 | 745 | $builder = new Definition('Sentry\\ClientBuilder', [$options]); 746 | 747 | $client = new Definition('Sentry\\Client'); 748 | $client->setFactory([$builder, 'getClient']); 749 | 750 | $clientId = 'monolog.sentry.client.'.sha1($handler['dsn']); 751 | $container->setDefinition($clientId, $client); 752 | 753 | if (!$container->hasAlias('Sentry\\ClientInterface')) { 754 | $container->setAlias('Sentry\\ClientInterface', $clientId); 755 | } 756 | } 757 | 758 | $hub = new Definition( 759 | 'Sentry\\State\\Hub', 760 | [new Reference($clientId)] 761 | ); 762 | $container->setDefinition(\sprintf('monolog.handler.%s.hub', $name), $hub); 763 | 764 | // can't set the hub to the current hub, getting into a recursion otherwise... 765 | // $hub->addMethodCall('setCurrent', array($hub)); 766 | } 767 | 768 | $definition->setArguments([ 769 | $hub, 770 | $handler['level'], 771 | $handler['bubble'], 772 | $handler['fill_extra_context'], 773 | ]); 774 | break; 775 | 776 | case 'raven': 777 | if (null !== $handler['client_id']) { 778 | $clientId = $handler['client_id']; 779 | } else { 780 | $client = new Definition('Raven_Client', [ 781 | $handler['dsn'], 782 | [ 783 | 'auto_log_stacks' => $handler['auto_log_stacks'], 784 | 'environment' => $handler['environment'], 785 | ], 786 | ]); 787 | $client->setPublic(false); 788 | $clientId = 'monolog.raven.client.'.sha1($handler['dsn']); 789 | $container->setDefinition($clientId, $client); 790 | } 791 | $definition->setArguments([ 792 | new Reference($clientId), 793 | $handler['level'], 794 | $handler['bubble'], 795 | ]); 796 | if (!empty($handler['release'])) { 797 | $definition->addMethodCall('setRelease', [$handler['release']]); 798 | } 799 | break; 800 | 801 | case 'loggly': 802 | $definition->setArguments([ 803 | $handler['token'], 804 | $handler['level'], 805 | $handler['bubble'], 806 | ]); 807 | if (!empty($handler['tags'])) { 808 | $definition->addMethodCall('setTag', [implode(',', $handler['tags'])]); 809 | } 810 | break; 811 | 812 | case 'logentries': 813 | $definition->setArguments([ 814 | $handler['token'], 815 | $handler['use_ssl'], 816 | $handler['level'], 817 | $handler['bubble'], 818 | ]); 819 | if (isset($handler['timeout'])) { 820 | $definition->addMethodCall('setTimeout', [$handler['timeout']]); 821 | } 822 | if (isset($handler['connection_timeout'])) { 823 | $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); 824 | } 825 | break; 826 | 827 | case 'insightops': 828 | $definition->setArguments([ 829 | $handler['token'], 830 | $handler['region'] ?: 'us', 831 | $handler['use_ssl'], 832 | $handler['level'], 833 | $handler['bubble'], 834 | ]); 835 | break; 836 | 837 | case 'flowdock': 838 | $definition->setArguments([ 839 | $handler['token'], 840 | $handler['level'], 841 | $handler['bubble'], 842 | ]); 843 | 844 | if (empty($handler['formatter'])) { 845 | $formatter = new Definition("Monolog\Formatter\FlowdockFormatter", [ 846 | $handler['source'], 847 | $handler['from_email'], 848 | ]); 849 | $formatterId = 'monolog.flowdock.formatter.'.sha1($handler['source'].'|'.$handler['from_email']); 850 | $formatter->setPublic(false); 851 | $container->setDefinition($formatterId, $formatter); 852 | 853 | $definition->addMethodCall('setFormatter', [new Reference($formatterId)]); 854 | } 855 | break; 856 | 857 | case 'rollbar': 858 | if (!empty($handler['id'])) { 859 | $rollbarId = $handler['id']; 860 | } else { 861 | $config = $handler['config'] ?: []; 862 | $config['access_token'] = $handler['token']; 863 | $rollbar = new Definition('RollbarNotifier', [ 864 | $config, 865 | ]); 866 | $rollbarId = 'monolog.rollbar.notifier.'.sha1(json_encode($config)); 867 | $rollbar->setPublic(false); 868 | $container->setDefinition($rollbarId, $rollbar); 869 | } 870 | 871 | $definition->setArguments([ 872 | new Reference($rollbarId), 873 | $handler['level'], 874 | $handler['bubble'], 875 | ]); 876 | break; 877 | case 'newrelic': 878 | $definition->setArguments([ 879 | $handler['level'], 880 | $handler['bubble'], 881 | $handler['app_name'], 882 | ]); 883 | break; 884 | case 'server_log': 885 | $definition->setArguments([ 886 | $handler['host'], 887 | $handler['level'], 888 | $handler['bubble'], 889 | ]); 890 | break; 891 | case 'sampling': 892 | $nestedHandlerId = $this->getHandlerId($handler['handler']); 893 | $this->markNestedHandler($nestedHandlerId); 894 | 895 | $definition->setArguments([ 896 | new Reference($nestedHandlerId), 897 | $handler['factor'], 898 | ]); 899 | break; 900 | 901 | // Handlers using the constructor of AbstractHandler without adding their own arguments 902 | case 'browser_console': 903 | case 'test': 904 | case 'null': 905 | case 'noop': 906 | case 'debug': 907 | $definition->setArguments([ 908 | $handler['level'], 909 | $handler['bubble'], 910 | ]); 911 | break; 912 | 913 | default: 914 | $nullWarning = ''; 915 | if ('' == $handler['type']) { 916 | $nullWarning = ', if you meant to define a null handler in a yaml config, make sure you quote "null" so it does not get converted to a php null'; 917 | } 918 | 919 | throw new \InvalidArgumentException(\sprintf('Invalid handler type "%s" given for handler "%s"'.$nullWarning, $handler['type'], $name)); 920 | } 921 | 922 | if (!empty($handler['nested']) && true === $handler['nested']) { 923 | $this->markNestedHandler($handlerId); 924 | } 925 | 926 | if (!empty($handler['formatter'])) { 927 | $definition->addMethodCall('setFormatter', [new Reference($handler['formatter'])]); 928 | } 929 | 930 | if (!\in_array($handlerId, $this->nestedHandlers) && is_subclass_of($handlerClass, ResettableInterface::class)) { 931 | $definition->addTag('kernel.reset', ['method' => 'reset']); 932 | } 933 | 934 | $container->setDefinition($handlerId, $definition); 935 | 936 | return $handlerId; 937 | } 938 | 939 | private function markNestedHandler($nestedHandlerId) 940 | { 941 | if (\in_array($nestedHandlerId, $this->nestedHandlers)) { 942 | return; 943 | } 944 | 945 | $this->nestedHandlers[] = $nestedHandlerId; 946 | } 947 | 948 | private function getHandlerId($name) 949 | { 950 | return \sprintf('monolog.handler.%s', $name); 951 | } 952 | 953 | private function getHandlerClassByType($handlerType) 954 | { 955 | $typeToClassMapping = [ 956 | 'stream' => 'Monolog\Handler\StreamHandler', 957 | 'console' => 'Symfony\Bridge\Monolog\Handler\ConsoleHandler', 958 | 'group' => 'Monolog\Handler\GroupHandler', 959 | 'buffer' => 'Monolog\Handler\BufferHandler', 960 | 'deduplication' => 'Monolog\Handler\DeduplicationHandler', 961 | 'rotating_file' => 'Monolog\Handler\RotatingFileHandler', 962 | 'syslog' => 'Monolog\Handler\SyslogHandler', 963 | 'syslogudp' => 'Monolog\Handler\SyslogUdpHandler', 964 | 'null' => 'Monolog\Handler\NullHandler', 965 | 'test' => 'Monolog\Handler\TestHandler', 966 | 'gelf' => 'Monolog\Handler\GelfHandler', 967 | 'rollbar' => 'Monolog\Handler\RollbarHandler', 968 | 'flowdock' => 'Monolog\Handler\FlowdockHandler', 969 | 'browser_console' => 'Monolog\Handler\BrowserConsoleHandler', 970 | 'firephp' => 'Symfony\Bridge\Monolog\Handler\FirePHPHandler', 971 | 'chromephp' => 'Symfony\Bridge\Monolog\Handler\ChromePhpHandler', 972 | 'debug' => 'Symfony\Bridge\Monolog\Handler\DebugHandler', 973 | 'swift_mailer' => 'Symfony\Bridge\Monolog\Handler\SwiftMailerHandler', 974 | 'native_mailer' => 'Monolog\Handler\NativeMailerHandler', 975 | 'symfony_mailer' => 'Symfony\Bridge\Monolog\Handler\MailerHandler', 976 | 'socket' => 'Monolog\Handler\SocketHandler', 977 | 'pushover' => 'Monolog\Handler\PushoverHandler', 978 | 'raven' => 'Monolog\Handler\RavenHandler', 979 | 'sentry' => 'Sentry\Monolog\Handler', 980 | 'newrelic' => 'Monolog\Handler\NewRelicHandler', 981 | 'hipchat' => 'Monolog\Handler\HipChatHandler', 982 | 'slack' => 'Monolog\Handler\SlackHandler', 983 | 'slackwebhook' => 'Monolog\Handler\SlackWebhookHandler', 984 | 'slackbot' => 'Monolog\Handler\SlackbotHandler', 985 | 'cube' => 'Monolog\Handler\CubeHandler', 986 | 'amqp' => 'Monolog\Handler\AmqpHandler', 987 | 'error_log' => 'Monolog\Handler\ErrorLogHandler', 988 | 'loggly' => 'Monolog\Handler\LogglyHandler', 989 | 'logentries' => 'Monolog\Handler\LogEntriesHandler', 990 | 'whatfailuregroup' => 'Monolog\Handler\WhatFailureGroupHandler', 991 | 'fingers_crossed' => 'Monolog\Handler\FingersCrossedHandler', 992 | 'filter' => 'Monolog\Handler\FilterHandler', 993 | 'mongo' => 'Monolog\Handler\MongoDBHandler', 994 | 'elasticsearch' => 'Monolog\Handler\ElasticSearchHandler', 995 | 'telegram' => 'Monolog\Handler\TelegramBotHandler', 996 | 'server_log' => 'Symfony\Bridge\Monolog\Handler\ServerLogHandler', 997 | 'redis' => 'Monolog\Handler\RedisHandler', 998 | 'predis' => 'Monolog\Handler\RedisHandler', 999 | 'insightops' => 'Monolog\Handler\InsightOpsHandler', 1000 | 'sampling' => 'Monolog\Handler\SamplingHandler', 1001 | ]; 1002 | 1003 | $v2HandlerTypesAdded = [ 1004 | 'elastica' => 'Monolog\Handler\ElasticaHandler', 1005 | 'elasticsearch' => 'Monolog\Handler\ElasticaHandler', 1006 | 'elastic_search' => 'Monolog\Handler\ElasticsearchHandler', 1007 | 'fallbackgroup' => 'Monolog\Handler\FallbackGroupHandler', 1008 | 'noop' => 'Monolog\Handler\NoopHandler', 1009 | ]; 1010 | 1011 | $v2HandlerTypesRemoved = [ 1012 | 'hipchat', 1013 | 'raven', 1014 | 'slackbot', 1015 | ]; 1016 | 1017 | $v3HandlerTypesRemoved = [ 1018 | 'swift_mailer', 1019 | ]; 1020 | 1021 | if (Logger::API >= 2) { 1022 | $typeToClassMapping = array_merge($typeToClassMapping, $v2HandlerTypesAdded); 1023 | foreach ($v2HandlerTypesRemoved as $v2HandlerTypeRemoved) { 1024 | unset($typeToClassMapping[$v2HandlerTypeRemoved]); 1025 | } 1026 | } 1027 | 1028 | if (Logger::API >= 3) { 1029 | foreach ($v3HandlerTypesRemoved as $v3HandlerTypeRemoved) { 1030 | unset($typeToClassMapping[$v3HandlerTypeRemoved]); 1031 | } 1032 | } 1033 | 1034 | if (!isset($typeToClassMapping[$handlerType])) { 1035 | if (Logger::API === 1 && \array_key_exists($handlerType, $v2HandlerTypesAdded)) { 1036 | throw new \InvalidArgumentException(\sprintf('"%s" was added in Monolog v2, please upgrade if you wish to use it.', $handlerType)); 1037 | } 1038 | 1039 | if (Logger::API >= 2 && \array_key_exists($handlerType, $v2HandlerTypesRemoved)) { 1040 | throw new \InvalidArgumentException(\sprintf('"%s" was removed in Monolog v2.', $handlerType)); 1041 | } 1042 | 1043 | if (Logger::API >= 3 && \array_key_exists($handlerType, $v3HandlerTypesRemoved)) { 1044 | throw new \InvalidArgumentException(\sprintf('"%s" was removed in Monolog v3.', $handlerType)); 1045 | } 1046 | 1047 | throw new \InvalidArgumentException(\sprintf('There is no handler class defined for handler "%s".', $handlerType)); 1048 | } 1049 | 1050 | return $typeToClassMapping[$handlerType]; 1051 | } 1052 | 1053 | private function buildPsrLogMessageProcessor(ContainerBuilder $container, array $processorOptions): string 1054 | { 1055 | static $hasConstructorArguments; 1056 | 1057 | if (!isset($hasConstructorArguments)) { 1058 | $reflectionConstructor = (new \ReflectionClass(PsrLogMessageProcessor::class))->getConstructor(); 1059 | $hasConstructorArguments = null !== $reflectionConstructor && $reflectionConstructor->getNumberOfParameters() > 0; 1060 | unset($reflectionConstructor); 1061 | } 1062 | 1063 | $processorId = 'monolog.processor.psr_log_message'; 1064 | $processorArguments = []; 1065 | 1066 | unset($processorOptions['enabled']); 1067 | 1068 | if (!empty($processorOptions)) { 1069 | if (!$hasConstructorArguments) { 1070 | throw new \RuntimeException('Monolog 1.26 or higher is required for the "date_format" and "remove_used_context_fields" options to be used.'); 1071 | } 1072 | $processorArguments = [ 1073 | $processorOptions['date_format'] ?? null, 1074 | $processorOptions['remove_used_context_fields'] ?? false, 1075 | ]; 1076 | $processorId .= '.'.ContainerBuilder::hash($processorArguments); 1077 | } 1078 | 1079 | if (!$container->hasDefinition($processorId)) { 1080 | $processor = new Definition(PsrLogMessageProcessor::class); 1081 | $processor->setPublic(false); 1082 | $processor->setArguments($processorArguments); 1083 | $container->setDefinition($processorId, $processor); 1084 | } 1085 | 1086 | return $processorId; 1087 | } 1088 | } 1089 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2019 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MonologBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle; 13 | 14 | use Monolog\Formatter\JsonFormatter; 15 | use Monolog\Formatter\LineFormatter; 16 | use Monolog\Handler\HandlerInterface; 17 | use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddProcessorsPass; 18 | use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddSwiftMailerTransportPass; 19 | use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\FixEmptyLoggerPass; 20 | use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass; 21 | use Symfony\Component\DependencyInjection\ContainerBuilder; 22 | use Symfony\Component\HttpKernel\Bundle\Bundle; 23 | 24 | /** 25 | * @author Jordi Boggiano 26 | * 27 | * @final since 3.9.0 28 | */ 29 | class MonologBundle extends Bundle 30 | { 31 | /** 32 | * @return void 33 | */ 34 | public function build(ContainerBuilder $container) 35 | { 36 | parent::build($container); 37 | 38 | $container->addCompilerPass($channelPass = new LoggerChannelPass()); 39 | $container->addCompilerPass(new FixEmptyLoggerPass($channelPass)); 40 | $container->addCompilerPass(new AddProcessorsPass()); 41 | $container->addCompilerPass(new AddSwiftMailerTransportPass()); 42 | } 43 | 44 | /** 45 | * @internal 46 | * 47 | * @return void 48 | */ 49 | public static function includeStacktraces(HandlerInterface $handler) 50 | { 51 | $formatter = $handler->getFormatter(); 52 | if ($formatter instanceof LineFormatter || $formatter instanceof JsonFormatter) { 53 | $formatter->includeStacktraces(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MonologBundle 2 | ============= 3 | 4 | The `MonologBundle` provides integration of the [Monolog](https://github.com/Seldaek/monolog) 5 | library into the Symfony framework. 6 | 7 | More information in the official [documentation](http://symfony.com/doc/current/cookbook/logging/index.html). 8 | 9 | License 10 | ======= 11 | 12 | This bundle is released under the [MIT license](LICENSE) 13 | -------------------------------------------------------------------------------- /Resources/config/monolog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | app 10 | 11 | %monolog.use_microseconds% 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | app 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Resources/config/schema/monolog-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /SwiftMailer/MessageFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MonologBundle\SwiftMailer; 13 | 14 | /** 15 | * Helps create Swift_Message objects, lazily. 16 | * 17 | * @author Ryan Weaver 18 | */ 19 | class MessageFactory 20 | { 21 | private $mailer; 22 | 23 | private $fromEmail; 24 | 25 | private $toEmail; 26 | 27 | private $subject; 28 | 29 | private $contentType; 30 | 31 | public function __construct(\Swift_Mailer $mailer, $fromEmail, $toEmail, $subject, $contentType = null) 32 | { 33 | $this->mailer = $mailer; 34 | $this->fromEmail = $fromEmail; 35 | $this->toEmail = $toEmail; 36 | $this->subject = $subject; 37 | $this->contentType = $contentType; 38 | } 39 | 40 | /** 41 | * Creates a Swift_Message template that will be used to send the log message. 42 | * 43 | * @param string $content formatted email body to be sent 44 | * @param array $records Log records that formed the content 45 | * 46 | * @return \Swift_Message 47 | */ 48 | public function createMessage($content, array $records) 49 | { 50 | /** @var \Swift_Message $message */ 51 | $message = $this->mailer->createMessage(); 52 | $message->setTo($this->toEmail); 53 | $message->setFrom($this->fromEmail); 54 | $message->setSubject($this->subject); 55 | 56 | if ($this->contentType) { 57 | $message->setContentType($this->contentType); 58 | } 59 | 60 | return $message; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/monolog-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Symfony MonologBundle", 5 | "keywords": ["log", "logging"], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Fabien Potencier", 11 | "email": "fabien@symfony.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.2.5", 20 | "symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0", 21 | "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", 22 | "symfony/config": "^5.4 || ^6.0 || ^7.0", 23 | "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", 24 | "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0" 25 | }, 26 | "require-dev": { 27 | "symfony/yaml": "^5.4 || ^6.0 || ^7.0", 28 | "symfony/console": "^5.4 || ^6.0 || ^7.0", 29 | "symfony/phpunit-bridge": "^7.1" 30 | }, 31 | "autoload": { 32 | "psr-4": { "Symfony\\Bundle\\MonologBundle\\": "" }, 33 | "exclude-from-classmap": [ 34 | "/Tests/" 35 | ] 36 | }, 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "3.x-dev" 40 | } 41 | } 42 | } 43 | --------------------------------------------------------------------------------