1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Bundle\MonologBundle\DependencyInjection;
13
14use Monolog\Logger;
15use Monolog\Processor\ProcessorInterface;
16use Monolog\ResettableInterface;
17use Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy;
18use Symfony\Bridge\Monolog\Processor\TokenProcessor;
19use Symfony\Bridge\Monolog\Processor\WebProcessor;
20use Symfony\Bundle\FullStack;
21use Symfony\Component\HttpKernel\DependencyInjection\Extension;
22use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
23use Symfony\Component\DependencyInjection\ContainerBuilder;
24use Symfony\Component\Config\FileLocator;
25use Symfony\Component\DependencyInjection\Definition;
26use Symfony\Component\DependencyInjection\Reference;
27use Symfony\Component\HttpKernel\Kernel;
28
29/**
30 * MonologExtension is an extension for the Monolog library.
31 *
32 * @author Jordi Boggiano <j.boggiano@seld.be>
33 * @author Christophe Coevoet <stof@notk.org>
34 */
35class MonologExtension extends Extension
36{
37    private $nestedHandlers = [];
38
39    private $swiftMailerHandlers = [];
40
41    private function levelToMonologConst($level)
42    {
43        return is_int($level) ? $level : constant('Monolog\Logger::'.strtoupper($level));
44    }
45
46    /**
47     * Loads the Monolog configuration.
48     *
49     * @param array            $configs   An array of configuration settings
50     * @param ContainerBuilder $container A ContainerBuilder instance
51     */
52    public function load(array $configs, ContainerBuilder $container)
53    {
54        if (class_exists(FullStack::class) && Kernel::MAJOR_VERSION < 5 && Logger::API >= 2) {
55            throw new \RuntimeException('Symfony 5 is required for Monolog 2 support. Please downgrade Monolog to version 1.');
56        }
57
58        $configuration = $this->getConfiguration($configs, $container);
59        $config = $this->processConfiguration($configuration, $configs);
60
61
62        if (isset($config['handlers'])) {
63            $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
64            $loader->load('monolog.xml');
65
66            $container->setParameter('monolog.use_microseconds', $config['use_microseconds']);
67
68            $handlers = [];
69
70            foreach ($config['handlers'] as $name => $handler) {
71                $handlers[$handler['priority']][] = [
72                    'id' => $this->buildHandler($container, $name, $handler),
73                    'channels' => empty($handler['channels']) ? null : $handler['channels'],
74                ];
75            }
76
77            $container->setParameter(
78                'monolog.swift_mailer.handlers',
79                $this->swiftMailerHandlers
80            );
81
82            ksort($handlers);
83            $sortedHandlers = [];
84            foreach ($handlers as $priorityHandlers) {
85                foreach (array_reverse($priorityHandlers) as $handler) {
86                    $sortedHandlers[] = $handler;
87                }
88            }
89
90            $handlersToChannels = [];
91            foreach ($sortedHandlers as $handler) {
92                if (!in_array($handler['id'], $this->nestedHandlers)) {
93                    $handlersToChannels[$handler['id']] = $handler['channels'];
94                }
95            }
96            $container->setParameter('monolog.handlers_to_channels', $handlersToChannels);
97
98            if (PHP_VERSION_ID < 70000) {
99                $this->addClassesToCompile([
100                    'Monolog\\Formatter\\FormatterInterface',
101                    'Monolog\\Formatter\\LineFormatter',
102                    'Monolog\\Handler\\HandlerInterface',
103                    'Monolog\\Handler\\AbstractHandler',
104                    'Monolog\\Handler\\AbstractProcessingHandler',
105                    'Monolog\\Handler\\StreamHandler',
106                    'Monolog\\Handler\\FingersCrossedHandler',
107                    'Monolog\\Handler\\FilterHandler',
108                    'Monolog\\Handler\\TestHandler',
109                    'Monolog\\Logger',
110                    'Symfony\\Bridge\\Monolog\\Logger',
111                    'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface',
112                    'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy',
113                ]);
114            }
115        }
116
117        $container->setParameter('monolog.additional_channels', isset($config['channels']) ? $config['channels'] : []);
118
119        if (method_exists($container, 'registerForAutoconfiguration')) {
120            if (interface_exists(ProcessorInterface::class)) {
121                $container->registerForAutoconfiguration(ProcessorInterface::class)
122                    ->addTag('monolog.processor');
123            } else {
124                $container->registerForAutoconfiguration(WebProcessor::class)
125                    ->addTag('monolog.processor');
126            }
127            $container->registerForAutoconfiguration(TokenProcessor::class)
128                ->addTag('monolog.processor');
129        }
130    }
131
132    /**
133     * Returns the base path for the XSD files.
134     *
135     * @return string The XSD base path
136     */
137    public function getXsdValidationBasePath()
138    {
139        return __DIR__.'/../Resources/config/schema';
140    }
141
142    public function getNamespace()
143    {
144        return 'http://symfony.com/schema/dic/monolog';
145    }
146
147    private function buildHandler(ContainerBuilder $container, $name, array $handler)
148    {
149        $handlerId = $this->getHandlerId($name);
150        if ('service' === $handler['type']) {
151            $container->setAlias($handlerId, $handler['id']);
152
153            if (!empty($handler['nested']) && true === $handler['nested']) {
154                $this->markNestedHandler($handlerId);
155            }
156
157            return $handlerId;
158        }
159
160        $handlerClass = $this->getHandlerClassByType($handler['type']);
161        $definition = new Definition($handlerClass);
162
163        $handler['level'] = $this->levelToMonologConst($handler['level']);
164
165        if ($handler['include_stacktraces']) {
166            $definition->setConfigurator(['Symfony\\Bundle\\MonologBundle\\MonologBundle', 'includeStacktraces']);
167        }
168
169        if (null === $handler['process_psr_3_messages']) {
170            $handler['process_psr_3_messages'] = !isset($handler['handler']) && !$handler['members'];
171        }
172
173        if ($handler['process_psr_3_messages']) {
174            $processorId = 'monolog.processor.psr_log_message';
175            if (!$container->hasDefinition($processorId)) {
176                $processor = new Definition('Monolog\\Processor\\PsrLogMessageProcessor');
177                $processor->setPublic(false);
178                $container->setDefinition($processorId, $processor);
179            }
180
181            $definition->addMethodCall('pushProcessor', [new Reference($processorId)]);
182        }
183
184        switch ($handler['type']) {
185        case 'stream':
186            $definition->setArguments([
187                $handler['path'],
188                $handler['level'],
189                $handler['bubble'],
190                $handler['file_permission'],
191                $handler['use_locking'],
192            ]);
193            break;
194
195        case 'console':
196            $definition->setArguments([
197                null,
198                $handler['bubble'],
199                isset($handler['verbosity_levels']) ? $handler['verbosity_levels'] : [],
200                $handler['console_formater_options']
201            ]);
202            $definition->addTag('kernel.event_subscriber');
203            break;
204
205        case 'firephp':
206            $definition->setArguments([
207                $handler['level'],
208                $handler['bubble'],
209            ]);
210            $definition->addTag('kernel.event_listener', ['event' => 'kernel.response', 'method' => 'onKernelResponse']);
211            break;
212
213        case 'gelf':
214            if (isset($handler['publisher']['id'])) {
215                $publisher = new Reference($handler['publisher']['id']);
216            } elseif (class_exists('Gelf\Transport\UdpTransport')) {
217                $transport = new Definition("Gelf\Transport\UdpTransport", [
218                    $handler['publisher']['hostname'],
219                    $handler['publisher']['port'],
220                    $handler['publisher']['chunk_size'],
221                ]);
222                $transport->setPublic(false);
223
224                $publisher = new Definition('Gelf\Publisher', []);
225                $publisher->addMethodCall('addTransport', [$transport]);
226                $publisher->setPublic(false);
227            } elseif (class_exists('Gelf\MessagePublisher')) {
228                $publisher = new Definition('Gelf\MessagePublisher', [
229                    $handler['publisher']['hostname'],
230                    $handler['publisher']['port'],
231                    $handler['publisher']['chunk_size'],
232                ]);
233
234                $publisher->setPublic(false);
235            } else {
236                throw new \RuntimeException('The gelf handler requires the graylog2/gelf-php package to be installed');
237            }
238
239            $definition->setArguments([
240                $publisher,
241                $handler['level'],
242                $handler['bubble'],
243            ]);
244            break;
245
246        case 'mongo':
247            if (isset($handler['mongo']['id'])) {
248                $client = new Reference($handler['mongo']['id']);
249            } else {
250                $server = 'mongodb://';
251
252                if (isset($handler['mongo']['user'])) {
253                    $server .= $handler['mongo']['user'].':'.$handler['mongo']['pass'].'@';
254                }
255
256                $server .= $handler['mongo']['host'].':'.$handler['mongo']['port'];
257
258                $client = new Definition('MongoClient', [
259                    $server,
260                ]);
261
262                $client->setPublic(false);
263            }
264
265            $definition->setArguments([
266                $client,
267                $handler['mongo']['database'],
268                $handler['mongo']['collection'],
269                $handler['level'],
270                $handler['bubble'],
271            ]);
272            break;
273
274        case 'elasticsearch':
275            if (isset($handler['elasticsearch']['id'])) {
276                $elasticaClient = new Reference($handler['elasticsearch']['id']);
277            } else {
278                // elastica client new definition
279                $elasticaClient = new Definition('Elastica\Client');
280                $elasticaClientArguments = [
281                    'host' => $handler['elasticsearch']['host'],
282                    'port' => $handler['elasticsearch']['port'],
283                    'transport' => $handler['elasticsearch']['transport'],
284                ];
285
286                if (isset($handler['elasticsearch']['user']) && isset($handler['elasticsearch']['password'])) {
287                    $elasticaClientArguments = array_merge(
288                        $elasticaClientArguments,
289                        [
290                            'headers' => [
291                                'Authorization' => 'Basic ' . base64_encode($handler['elasticsearch']['user'] . ':' . $handler['elasticsearch']['password'])
292                            ]
293                        ]
294                    );
295                }
296
297                $elasticaClient->setArguments([
298                    $elasticaClientArguments
299                ]);
300
301                $elasticaClient->setPublic(false);
302            }
303
304            // elastica handler definition
305            $definition->setArguments([
306                $elasticaClient,
307                [
308                    'index' => $handler['index'],
309                    'type' => $handler['document_type'],
310                    'ignore_error' => $handler['ignore_error']
311                ],
312                $handler['level'],
313                $handler['bubble'],
314            ]);
315            break;
316        case 'redis':
317        case 'predis':
318            if (isset($handler['redis']['id'])) {
319                $clientId = $handler['redis']['id'];
320            } elseif ('redis' === $handler['type']) {
321                if (!class_exists(\Redis::class)) {
322                    throw new \RuntimeException('The \Redis class is not available.');
323                }
324
325                $client = new Definition(\Redis::class);
326                $client->addMethodCall('connect', [$handler['redis']['host'], $handler['redis']['port']]);
327                $client->addMethodCall('auth', [$handler['redis']['password']]);
328                $client->addMethodCall('select', [$handler['redis']['database']]);
329                $client->setPublic(false);
330                $clientId = uniqid('monolog.redis.client.', true);
331                $container->setDefinition($clientId, $client);
332            } else {
333                if (!class_exists(\Predis\Client::class)) {
334                    throw new \RuntimeException('The \Predis\Client class is not available.');
335                }
336
337                $client = new Definition(\Predis\Client::class);
338                $client->setArguments([
339                    $handler['redis']['host'],
340                ]);
341                $client->setPublic(false);
342
343                $clientId = uniqid('monolog.predis.client.', true);
344                $container->setDefinition($clientId, $client);
345            }
346            $definition->setArguments([
347                new Reference($clientId),
348                $handler['redis']['key_name'],
349                $handler['level'],
350                $handler['bubble'],
351            ]);
352            break;
353
354        case 'chromephp':
355            $definition->setArguments([
356                $handler['level'],
357                $handler['bubble'],
358            ]);
359            $definition->addTag('kernel.event_listener', ['event' => 'kernel.response', 'method' => 'onKernelResponse']);
360            break;
361
362        case 'rotating_file':
363            $definition->setArguments([
364                $handler['path'],
365                $handler['max_files'],
366                $handler['level'],
367                $handler['bubble'],
368                $handler['file_permission'],
369            ]);
370            $definition->addMethodCall('setFilenameFormat', [
371                $handler['filename_format'],
372                $handler['date_format'],
373            ]);
374            break;
375
376        case 'fingers_crossed':
377            $handler['action_level'] = $this->levelToMonologConst($handler['action_level']);
378            if (null !== $handler['passthru_level']) {
379                $handler['passthru_level'] = $this->levelToMonologConst($handler['passthru_level']);
380            }
381            $nestedHandlerId = $this->getHandlerId($handler['handler']);
382            $this->markNestedHandler($nestedHandlerId);
383
384            if (isset($handler['activation_strategy'])) {
385                $activation = new Reference($handler['activation_strategy']);
386            } elseif (!empty($handler['excluded_404s'])) {
387                if (class_exists(HttpCodeActivationStrategy::class)) {
388                    @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);
389                }
390                $activationDef = new Definition('Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy', [
391                    new Reference('request_stack'),
392                    $handler['excluded_404s'],
393                    $handler['action_level']
394                ]);
395                $container->setDefinition($handlerId.'.not_found_strategy', $activationDef);
396                $activation = new Reference($handlerId.'.not_found_strategy');
397            } elseif (!empty($handler['excluded_http_codes'])) {
398                if (!class_exists('Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy')) {
399                    throw new \LogicException('"excluded_http_codes" cannot be used as your version of Monolog bridge does not support it.');
400                }
401                $activationDef = new Definition('Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy', [
402                    new Reference('request_stack'),
403                    $handler['excluded_http_codes'],
404                    $handler['action_level']
405                ]);
406                $container->setDefinition($handlerId.'.http_code_strategy', $activationDef);
407                $activation = new Reference($handlerId.'.http_code_strategy');
408            } else {
409                $activation = $handler['action_level'];
410            }
411
412            $definition->setArguments([
413                new Reference($nestedHandlerId),
414                $activation,
415                $handler['buffer_size'],
416                $handler['bubble'],
417                $handler['stop_buffering'],
418                $handler['passthru_level'],
419            ]);
420            break;
421
422        case 'filter':
423            $handler['min_level'] = $this->levelToMonologConst($handler['min_level']);
424            $handler['max_level'] = $this->levelToMonologConst($handler['max_level']);
425            foreach (array_keys($handler['accepted_levels']) as $k) {
426                $handler['accepted_levels'][$k] = $this->levelToMonologConst($handler['accepted_levels'][$k]);
427            }
428
429            $nestedHandlerId = $this->getHandlerId($handler['handler']);
430            $this->markNestedHandler($nestedHandlerId);
431            $minLevelOrList = !empty($handler['accepted_levels']) ? $handler['accepted_levels'] : $handler['min_level'];
432
433            $definition->setArguments([
434                new Reference($nestedHandlerId),
435                $minLevelOrList,
436                $handler['max_level'],
437                $handler['bubble'],
438            ]);
439            break;
440
441        case 'buffer':
442            $nestedHandlerId = $this->getHandlerId($handler['handler']);
443            $this->markNestedHandler($nestedHandlerId);
444
445            $definition->setArguments([
446                new Reference($nestedHandlerId),
447                $handler['buffer_size'],
448                $handler['level'],
449                $handler['bubble'],
450                $handler['flush_on_overflow'],
451            ]);
452            break;
453
454        case 'deduplication':
455            $nestedHandlerId = $this->getHandlerId($handler['handler']);
456            $this->markNestedHandler($nestedHandlerId);
457            $defaultStore = '%kernel.cache_dir%/monolog_dedup_'.sha1($handlerId);
458
459            $definition->setArguments([
460                new Reference($nestedHandlerId),
461                isset($handler['store']) ? $handler['store'] : $defaultStore,
462                $handler['deduplication_level'],
463                $handler['time'],
464                $handler['bubble'],
465            ]);
466            break;
467
468        case 'group':
469        case 'whatfailuregroup':
470        case 'fallbackgroup':
471            $references = [];
472            foreach ($handler['members'] as $nestedHandler) {
473                $nestedHandlerId = $this->getHandlerId($nestedHandler);
474                $this->markNestedHandler($nestedHandlerId);
475                $references[] = new Reference($nestedHandlerId);
476            }
477
478            $definition->setArguments([
479                $references,
480                $handler['bubble'],
481            ]);
482            break;
483
484        case 'syslog':
485            $definition->setArguments([
486                $handler['ident'],
487                $handler['facility'],
488                $handler['level'],
489                $handler['bubble'],
490                $handler['logopts'],
491            ]);
492            break;
493
494        case 'syslogudp':
495            $definition->setArguments([
496                $handler['host'],
497                $handler['port'],
498                $handler['facility'],
499                $handler['level'],
500                $handler['bubble'],
501            ]);
502            if ($handler['ident']) {
503                $definition->addArgument($handler['ident']);
504            }
505            break;
506
507        case 'swift_mailer':
508            if (isset($handler['email_prototype'])) {
509                if (!empty($handler['email_prototype']['method'])) {
510                    $prototype = [new Reference($handler['email_prototype']['id']), $handler['email_prototype']['method']];
511                } else {
512                    $prototype = new Reference($handler['email_prototype']['id']);
513                }
514            } else {
515                $messageFactory = new Definition('Symfony\Bundle\MonologBundle\SwiftMailer\MessageFactory');
516                $messageFactory->setLazy(true);
517                $messageFactory->setPublic(false);
518                $messageFactory->setArguments([
519                    new Reference($handler['mailer']),
520                    $handler['from_email'],
521                    $handler['to_email'],
522                    $handler['subject'],
523                    $handler['content_type']
524                ]);
525
526                $messageFactoryId = sprintf('%s.mail_message_factory', $handlerId);
527                $container->setDefinition($messageFactoryId, $messageFactory);
528                // set the prototype as a callable
529                $prototype = [new Reference($messageFactoryId), 'createMessage'];
530            }
531            $definition->setArguments([
532                new Reference($handler['mailer']),
533                $prototype,
534                $handler['level'],
535                $handler['bubble'],
536            ]);
537
538            $this->swiftMailerHandlers[] = $handlerId;
539            $definition->addTag('kernel.event_listener', ['event' => 'kernel.terminate', 'method' => 'onKernelTerminate']);
540            $definition->addTag('kernel.event_listener', ['event' => 'console.terminate', 'method' => 'onCliTerminate']);
541            break;
542
543        case 'native_mailer':
544            $definition->setArguments([
545                $handler['to_email'],
546                $handler['subject'],
547                $handler['from_email'],
548                $handler['level'],
549                $handler['bubble'],
550            ]);
551            if (!empty($handler['headers'])) {
552                $definition->addMethodCall('addHeader', [$handler['headers']]);
553            }
554            break;
555
556        case 'socket':
557            $definition->setArguments([
558                $handler['connection_string'],
559                $handler['level'],
560                $handler['bubble'],
561            ]);
562            if (isset($handler['timeout'])) {
563                $definition->addMethodCall('setTimeout', [$handler['timeout']]);
564            }
565            if (isset($handler['connection_timeout'])) {
566                $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]);
567            }
568            if (isset($handler['persistent'])) {
569                $definition->addMethodCall('setPersistent', [$handler['persistent']]);
570            }
571            break;
572
573        case 'pushover':
574            $definition->setArguments([
575                $handler['token'],
576                $handler['user'],
577                $handler['title'],
578                $handler['level'],
579                $handler['bubble'],
580            ]);
581            if (isset($handler['timeout'])) {
582                $definition->addMethodCall('setTimeout', [$handler['timeout']]);
583            }
584            if (isset($handler['connection_timeout'])) {
585                $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]);
586            }
587            break;
588
589        case 'hipchat':
590            $definition->setArguments([
591                $handler['token'],
592                $handler['room'],
593                $handler['nickname'],
594                $handler['notify'],
595                $handler['level'],
596                $handler['bubble'],
597                $handler['use_ssl'],
598                $handler['message_format'],
599                !empty($handler['host']) ? $handler['host'] : 'api.hipchat.com',
600                !empty($handler['api_version']) ? $handler['api_version'] : 'v1',
601            ]);
602            if (isset($handler['timeout'])) {
603                $definition->addMethodCall('setTimeout', [$handler['timeout']]);
604            }
605            if (isset($handler['connection_timeout'])) {
606                $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]);
607            }
608            break;
609
610        case 'slack':
611            $definition->setArguments([
612                $handler['token'],
613                $handler['channel'],
614                $handler['bot_name'],
615                $handler['use_attachment'],
616                $handler['icon_emoji'],
617                $handler['level'],
618                $handler['bubble'],
619                $handler['use_short_attachment'],
620                $handler['include_extra'],
621            ]);
622            if (isset($handler['timeout'])) {
623                $definition->addMethodCall('setTimeout', [$handler['timeout']]);
624            }
625            if (isset($handler['connection_timeout'])) {
626                $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]);
627            }
628            break;
629
630        case 'slackwebhook':
631            $definition->setArguments([
632                $handler['webhook_url'],
633                $handler['channel'],
634                $handler['bot_name'],
635                $handler['use_attachment'],
636                $handler['icon_emoji'],
637                $handler['use_short_attachment'],
638                $handler['include_extra'],
639                $handler['level'],
640                $handler['bubble'],
641            ]);
642            break;
643
644        case 'slackbot':
645            $definition->setArguments([
646                $handler['team'],
647                $handler['token'],
648                urlencode($handler['channel']),
649                $handler['level'],
650                $handler['bubble'],
651            ]);
652            break;
653
654        case 'cube':
655            $definition->setArguments([
656                $handler['url'],
657                $handler['level'],
658                $handler['bubble'],
659            ]);
660            break;
661
662        case 'amqp':
663            $definition->setArguments([
664                new Reference($handler['exchange']),
665                $handler['exchange_name'],
666                $handler['level'],
667                $handler['bubble'],
668            ]);
669            break;
670
671        case 'error_log':
672            $definition->setArguments([
673                $handler['message_type'],
674                $handler['level'],
675                $handler['bubble'],
676            ]);
677            break;
678
679        case 'sentry':
680            if (null !== $handler['client_id']) {
681                $clientId = $handler['client_id'];
682            } else {
683                $options = new Definition(
684                    'Sentry\\Options',
685                    [['dsn' => $handler['dsn']]]
686                );
687
688                if (!empty($handler['environment'])) {
689                    $options->addMethodCall('setEnvironment', [$handler['environment']]);
690                }
691
692                if (!empty($handler['release'])) {
693                    $options->addMethodCall('setRelease', [$handler['release']]);
694                }
695
696                $builder = new Definition('Sentry\\ClientBuilder', [$options]);
697
698                $client = new Definition('Sentry\\Client');
699                $client->setFactory([$builder, 'getClient']);
700
701                $clientId = 'monolog.sentry.client.'.sha1($handler['dsn']);
702                $container->setDefinition($clientId, $client);
703
704                if (!$container->hasAlias('Sentry\\ClientInterface')) {
705                    $container->setAlias('Sentry\\ClientInterface', $clientId);
706                }
707            }
708
709            $hub = new Definition(
710                'Sentry\\State\\Hub',
711                [new Reference($clientId)]
712            );
713
714            // can't set the hub to the current hub, getting into a recursion otherwise...
715            //$hub->addMethodCall('setCurrent', array($hub));
716
717            $definition->setArguments([
718                $hub,
719                $handler['level'],
720                $handler['bubble'],
721            ]);
722            break;
723
724        case 'raven':
725            if (null !== $handler['client_id']) {
726                $clientId = $handler['client_id'];
727            } else {
728                $client = new Definition('Raven_Client', [
729                    $handler['dsn'],
730                    [
731                        'auto_log_stacks' => $handler['auto_log_stacks'],
732                        'environment' => $handler['environment']
733                    ]
734                ]);
735                $client->setPublic(false);
736                $clientId = 'monolog.raven.client.'.sha1($handler['dsn']);
737                $container->setDefinition($clientId, $client);
738            }
739            $definition->setArguments([
740                new Reference($clientId),
741                $handler['level'],
742                $handler['bubble'],
743            ]);
744            if (!empty($handler['release'])) {
745                $definition->addMethodCall('setRelease', [$handler['release']]);
746            }
747            break;
748
749        case 'loggly':
750            $definition->setArguments([
751                $handler['token'],
752                $handler['level'],
753                $handler['bubble'],
754            ]);
755            if (!empty($handler['tags'])) {
756                $definition->addMethodCall('setTag', [implode(',', $handler['tags'])]);
757            }
758            break;
759
760        case 'logentries':
761            $definition->setArguments([
762                $handler['token'],
763                $handler['use_ssl'],
764                $handler['level'],
765                $handler['bubble'],
766            ]);
767            if (isset($handler['timeout'])) {
768                $definition->addMethodCall('setTimeout', [$handler['timeout']]);
769            }
770            if (isset($handler['connection_timeout'])) {
771                $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]);
772            }
773            break;
774
775        case 'insightops':
776            $definition->setArguments([
777                $handler['token'],
778                $handler['region'] ? $handler['region'] : 'us',
779                $handler['use_ssl'],
780                $handler['level'],
781                $handler['bubble'],
782            ]);
783            break;
784
785        case 'flowdock':
786            $definition->setArguments([
787                $handler['token'],
788                $handler['level'],
789                $handler['bubble'],
790            ]);
791
792            if (empty($handler['formatter'])) {
793                $formatter = new Definition("Monolog\Formatter\FlowdockFormatter", [
794                    $handler['source'],
795                    $handler['from_email'],
796                ]);
797                $formatterId = 'monolog.flowdock.formatter.'.sha1($handler['source'].'|'.$handler['from_email']);
798                $formatter->setPublic(false);
799                $container->setDefinition($formatterId, $formatter);
800
801                $definition->addMethodCall('setFormatter', [new Reference($formatterId)]);
802            }
803            break;
804
805        case 'rollbar':
806            if (!empty($handler['id'])) {
807                $rollbarId = $handler['id'];
808            } else {
809                $config = $handler['config'] ?: [];
810                $config['access_token'] = $handler['token'];
811                $rollbar = new Definition('RollbarNotifier', [
812                    $config,
813                ]);
814                $rollbarId = 'monolog.rollbar.notifier.'.sha1(json_encode($config));
815                $rollbar->setPublic(false);
816                $container->setDefinition($rollbarId, $rollbar);
817            }
818
819            $definition->setArguments([
820                new Reference($rollbarId),
821                $handler['level'],
822                $handler['bubble'],
823            ]);
824            break;
825        case 'newrelic':
826            $definition->setArguments([
827                $handler['level'],
828                $handler['bubble'],
829                $handler['app_name'],
830            ]);
831            break;
832        case 'server_log':
833            if (!class_exists('Symfony\Bridge\Monolog\Handler\ServerLogHandler')) {
834                throw new \RuntimeException('The ServerLogHandler is not available. Please update "symfony/monolog-bridge" to 3.3.');
835            }
836
837            $definition->setArguments([
838                $handler['host'],
839                $handler['level'],
840                $handler['bubble'],
841            ]);
842            break;
843
844        // Handlers using the constructor of AbstractHandler without adding their own arguments
845        case 'browser_console':
846        case 'test':
847        case 'null':
848        case 'debug':
849            $definition->setArguments([
850                $handler['level'],
851                $handler['bubble'],
852            ]);
853            break;
854
855        default:
856            $nullWarning = '';
857            if ($handler['type'] == '') {
858                $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';
859            }
860
861            throw new \InvalidArgumentException(sprintf('Invalid handler type "%s" given for handler "%s"' . $nullWarning, $handler['type'], $name));
862        }
863
864        if (!empty($handler['nested']) && true === $handler['nested']) {
865            $this->markNestedHandler($handlerId);
866        }
867
868        if (!empty($handler['formatter'])) {
869            $definition->addMethodCall('setFormatter', [new Reference($handler['formatter'])]);
870        }
871
872        if (!in_array($handlerId, $this->nestedHandlers) && is_subclass_of($handlerClass, ResettableInterface::class)) {
873            $definition->addTag('kernel.reset', ['method' => 'reset']);
874        }
875
876        $container->setDefinition($handlerId, $definition);
877
878        return $handlerId;
879    }
880
881    private function markNestedHandler($nestedHandlerId)
882    {
883        if (in_array($nestedHandlerId, $this->nestedHandlers)) {
884            return;
885        }
886
887        $this->nestedHandlers[] = $nestedHandlerId;
888    }
889
890    private function getHandlerId($name)
891    {
892        return sprintf('monolog.handler.%s', $name);
893    }
894
895    private function getHandlerClassByType($handlerType)
896    {
897        $typeToClassMapping = [
898            'stream' => 'Monolog\Handler\StreamHandler',
899            'console' => 'Symfony\Bridge\Monolog\Handler\ConsoleHandler',
900            'group' => 'Monolog\Handler\GroupHandler',
901            'buffer' => 'Monolog\Handler\BufferHandler',
902            'deduplication' => 'Monolog\Handler\DeduplicationHandler',
903            'rotating_file' => 'Monolog\Handler\RotatingFileHandler',
904            'syslog' => 'Monolog\Handler\SyslogHandler',
905            'syslogudp' => 'Monolog\Handler\SyslogUdpHandler',
906            'null' => 'Monolog\Handler\NullHandler',
907            'test' => 'Monolog\Handler\TestHandler',
908            'gelf' => 'Monolog\Handler\GelfHandler',
909            'rollbar' => 'Monolog\Handler\RollbarHandler',
910            'flowdock' => 'Monolog\Handler\FlowdockHandler',
911            'browser_console' => 'Monolog\Handler\BrowserConsoleHandler',
912            'firephp' => 'Symfony\Bridge\Monolog\Handler\FirePHPHandler',
913            'chromephp' => 'Symfony\Bridge\Monolog\Handler\ChromePhpHandler',
914            'debug' => 'Symfony\Bridge\Monolog\Handler\DebugHandler',
915            'swift_mailer' => 'Symfony\Bridge\Monolog\Handler\SwiftMailerHandler',
916            'native_mailer' => 'Monolog\Handler\NativeMailerHandler',
917            'socket' => 'Monolog\Handler\SocketHandler',
918            'pushover' => 'Monolog\Handler\PushoverHandler',
919            'raven' => 'Monolog\Handler\RavenHandler',
920            'sentry' => 'Sentry\Monolog\Handler',
921            'newrelic' => 'Monolog\Handler\NewRelicHandler',
922            'hipchat' => 'Monolog\Handler\HipChatHandler',
923            'slack' => 'Monolog\Handler\SlackHandler',
924            'slackwebhook' => 'Monolog\Handler\SlackWebhookHandler',
925            'slackbot' => 'Monolog\Handler\SlackbotHandler',
926            'cube' => 'Monolog\Handler\CubeHandler',
927            'amqp' => 'Monolog\Handler\AmqpHandler',
928            'error_log' => 'Monolog\Handler\ErrorLogHandler',
929            'loggly' => 'Monolog\Handler\LogglyHandler',
930            'logentries' => 'Monolog\Handler\LogEntriesHandler',
931            'whatfailuregroup' => 'Monolog\Handler\WhatFailureGroupHandler',
932            'fingers_crossed' => 'Monolog\Handler\FingersCrossedHandler',
933            'filter' => 'Monolog\Handler\FilterHandler',
934            'mongo' => 'Monolog\Handler\MongoDBHandler',
935            'elasticsearch' => 'Monolog\Handler\ElasticSearchHandler',
936            'server_log' => 'Symfony\Bridge\Monolog\Handler\ServerLogHandler',
937            'redis' => 'Monolog\Handler\RedisHandler',
938            'predis' => 'Monolog\Handler\RedisHandler',
939            'insightops' => 'Monolog\Handler\InsightOpsHandler',
940        ];
941
942        $v2HandlerTypesAdded = [
943            'elasticsearch' => 'Monolog\Handler\ElasticaHandler',
944            'fallbackgroup' => 'Monolog\Handler\FallbackGroupHandler',
945            'logmatic' => 'Monolog\Handler\LogmaticHandler',
946            'noop' => 'Monolog\Handler\NoopHandler',
947            'overflow' => 'Monolog\Handler\OverflowHandler',
948            'process' => 'Monolog\Handler\ProcessHandler',
949            'sendgrid' => 'Monolog\Handler\SendGridHandler',
950            'sqs' => 'Monolog\Handler\SqsHandler',
951            'telegram' => 'Monolog\Handler\TelegramBotHandler',
952        ];
953
954        $v2HandlerTypesRemoved = [
955            'hipchat',
956            'raven',
957            'slackbot',
958        ];
959
960        if (Logger::API === 2) {
961            $typeToClassMapping = array_merge($typeToClassMapping, $v2HandlerTypesAdded);
962            foreach($v2HandlerTypesRemoved as $v2HandlerTypeRemoved) {
963                unset($typeToClassMapping[$v2HandlerTypeRemoved]);
964            }
965        }
966
967        if (!isset($typeToClassMapping[$handlerType])) {
968            if (Logger::API === 1 && array_key_exists($handlerType, $v2HandlerTypesAdded)) {
969                throw new \InvalidArgumentException(sprintf('"%s" was added in Monolog v2, please upgrade if you wish to use it.', $handlerType));
970            }
971
972            if (Logger::API === 2 && array_key_exists($handlerType, $v2HandlerTypesRemoved)) {
973                throw new \InvalidArgumentException(sprintf('"%s" was removed in Monolog v2.', $handlerType));
974            }
975
976            throw new \InvalidArgumentException(sprintf('There is no handler class defined for handler "%s".', $handlerType));
977        }
978
979        return $typeToClassMapping[$handlerType];
980    }
981}
982