vendor/symfony/monolog-bundle/DependencyInjection/Configuration.php line 381

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bundle\MonologBundle\DependencyInjection;
  11. use Monolog\Logger;
  12. use Symfony\Component\Config\Definition\BaseNode;
  13. use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
  14. use Symfony\Component\Config\Definition\Builder\TreeBuilder;
  15. use Symfony\Component\Config\Definition\ConfigurationInterface;
  16. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  17. /**
  18.  * This class contains the configuration information for the bundle
  19.  *
  20.  * This information is solely responsible for how the different configuration
  21.  * sections are normalized, and merged.
  22.  *
  23.  * Possible handler types and related configurations (brackets indicate optional params):
  24.  *
  25.  * - service:
  26.  *   - id
  27.  *
  28.  * - stream:
  29.  *   - path: string
  30.  *   - [level]: level name or int value, defaults to DEBUG
  31.  *   - [bubble]: bool, defaults to true
  32.  *   - [file_permission]: int|null, defaults to null (0644)
  33.  *   - [use_locking]: bool, defaults to false
  34.  *
  35.  * - console:
  36.  *   - [verbosity_levels]: level => verbosity configuration
  37.  *   - [level]: level name or int value, defaults to DEBUG
  38.  *   - [bubble]: bool, defaults to true
  39.  *   - [console_formatter_options]: array
  40.  *
  41.  * - firephp:
  42.  *   - [level]: level name or int value, defaults to DEBUG
  43.  *   - [bubble]: bool, defaults to true
  44.  *
  45.  * - browser_console:
  46.  *   - [level]: level name or int value, defaults to DEBUG
  47.  *   - [bubble]: bool, defaults to true
  48.  *
  49.  * - gelf:
  50.  *   - publisher: {id: ...} or {hostname: ..., port: ..., chunk_size: ...}
  51.  *   - [level]: level name or int value, defaults to DEBUG
  52.  *   - [bubble]: bool, defaults to true
  53.  *
  54.  * - chromephp:
  55.  *   - [level]: level name or int value, defaults to DEBUG
  56.  *   - [bubble]: bool, defaults to true
  57.  *
  58.  * - rotating_file:
  59.  *   - path: string
  60.  *   - [max_files]: files to keep, defaults to zero (infinite)
  61.  *   - [level]: level name or int value, defaults to DEBUG
  62.  *   - [bubble]: bool, defaults to true
  63.  *   - [file_permission]: string|null, defaults to null
  64.  *   - [use_locking]: bool, defaults to false
  65.  *   - [filename_format]: string, defaults to '{filename}-{date}'
  66.  *   - [date_format]: string, defaults to 'Y-m-d'
  67.  *
  68.  * - mongo:
  69.  *   - mongo:
  70.  *      - id: optional if host is given
  71.  *      - host: database host name, optional if id is given
  72.  *      - [port]: defaults to 27017
  73.  *      - [user]: database user name
  74.  *      - pass: mandatory only if user is present
  75.  *      - [database]: defaults to monolog
  76.  *      - [collection]: defaults to logs
  77.  *   - [level]: level name or int value, defaults to DEBUG
  78.  *   - [bubble]: bool, defaults to true
  79.  *
  80.  * - elastic_search:
  81.  *   - elasticsearch:
  82.  *      - id: optional if host is given
  83.  *      - host: elastic search host name, with scheme (e.g. "https://127.0.0.1:9200")
  84.  *      - [user]: elastic search user name
  85.  *      - [password]: elastic search user password
  86.  *   - [index]: index name, defaults to monolog
  87.  *   - [document_type]: document_type, defaults to logs
  88.  *   - [level]: level name or int value, defaults to DEBUG
  89.  *   - [bubble]: bool, defaults to true
  90.  *
  91.  * - elastica:
  92.  *   - elasticsearch:
  93.  *      - id: optional if host is given
  94.  *      - host: elastic search host name. Do not prepend with http(s)://
  95.  *      - [port]: defaults to 9200
  96.  *      - [transport]: transport protocol (http by default)
  97.  *      - [user]: elastic search user name
  98.  *      - [password]: elastic search user password
  99.  *   - [index]: index name, defaults to monolog
  100.  *   - [document_type]: document_type, defaults to logs
  101.  *   - [level]: level name or int value, defaults to DEBUG
  102.  *   - [bubble]: bool, defaults to true
  103.  *
  104.  * - redis:
  105.  *   - redis:
  106.  *      - id: optional if host is given
  107.  *      - host: 127.0.0.1
  108.  *      - password: null
  109.  *      - port: 6379
  110.  *      - database: 0
  111.  *      - key_name: monolog_redis
  112.  *
  113.  * - predis:
  114.  *   - redis:
  115.  *      - id: optional if host is given
  116.  *      - host: tcp://10.0.0.1:6379
  117.  *      - key_name: monolog_redis
  118.  *
  119.  * - fingers_crossed:
  120.  *   - handler: the wrapped handler's name
  121.  *   - [action_level|activation_strategy]: minimum level or service id to activate the handler, defaults to WARNING
  122.  *   - [excluded_404s]: if set, the strategy will be changed to one that excludes 404s coming from URLs matching any of those patterns
  123.  *   - [excluded_http_codes]: if set, the strategy will be changed to one that excludes specific HTTP codes (requires Symfony Monolog bridge 4.1+)
  124.  *   - [buffer_size]: defaults to 0 (unlimited)
  125.  *   - [stop_buffering]: bool to disable buffering once the handler has been activated, defaults to true
  126.  *   - [passthru_level]: level name or int value for messages to always flush, disabled by default
  127.  *   - [bubble]: bool, defaults to true
  128.  *
  129.  * - filter:
  130.  *   - handler: the wrapped handler's name
  131.  *   - [accepted_levels]: list of levels to accept
  132.  *   - [min_level]: minimum level to accept (only used if accepted_levels not specified)
  133.  *   - [max_level]: maximum level to accept (only used if accepted_levels not specified)
  134.  *   - [bubble]: bool, defaults to true
  135.  *
  136.  * - buffer:
  137.  *   - handler: the wrapped handler's name
  138.  *   - [buffer_size]: defaults to 0 (unlimited)
  139.  *   - [level]: level name or int value, defaults to DEBUG
  140.  *   - [bubble]: bool, defaults to true
  141.  *   - [flush_on_overflow]: bool, defaults to false
  142.  *
  143.  * - deduplication:
  144.  *   - handler: the wrapped handler's name
  145.  *   - [store]: The file/path where the deduplication log should be kept, defaults to %kernel.cache_dir%/monolog_dedup_*
  146.  *   - [deduplication_level]: The minimum logging level for log records to be looked at for deduplication purposes, defaults to ERROR
  147.  *   - [time]: The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through, defaults to 60
  148.  *   - [bubble]: bool, defaults to true
  149.  *
  150.  * - group:
  151.  *   - members: the wrapped handlers by name
  152.  *   - [bubble]: bool, defaults to true
  153.  *
  154.  * - whatfailuregroup:
  155.  *   - members: the wrapped handlers by name
  156.  *   - [bubble]: bool, defaults to true
  157.  *
  158.  * - fallbackgroup
  159.  *   - members: the wrapped handlers by name
  160.  *   - [bubble]: bool, defaults to true
  161.  *
  162.  * - syslog:
  163.  *   - ident: string
  164.  *   - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER
  165.  *   - [logopts]: defaults to LOG_PID
  166.  *   - [level]: level name or int value, defaults to DEBUG
  167.  *   - [bubble]: bool, defaults to true
  168.  *
  169.  * - syslogudp:
  170.  *   - host: syslogd host name
  171.  *   - [port]: defaults to 514
  172.  *   - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER
  173.  *   - [logopts]: defaults to LOG_PID
  174.  *   - [level]: level name or int value, defaults to DEBUG
  175.  *   - [bubble]: bool, defaults to true
  176.  *   - [ident]: string, defaults to
  177.  *
  178.  * - swift_mailer:
  179.  *   - from_email: optional if email_prototype is given
  180.  *   - to_email: optional if email_prototype is given
  181.  *   - subject: optional if email_prototype is given
  182.  *   - [email_prototype]: service id of a message, defaults to a default message with the three fields above
  183.  *   - [content_type]: optional if email_prototype is given, defaults to text/plain
  184.  *   - [mailer]: mailer service, defaults to mailer
  185.  *   - [level]: level name or int value, defaults to DEBUG
  186.  *   - [bubble]: bool, defaults to true
  187.  *   - [lazy]: use service lazy loading, bool, defaults to true
  188.  *
  189.  * - native_mailer:
  190.  *   - from_email: string
  191.  *   - to_email: string
  192.  *   - subject: string
  193.  *   - [level]: level name or int value, defaults to DEBUG
  194.  *   - [bubble]: bool, defaults to true
  195.  *   - [headers]: optional array containing additional headers: ['Foo: Bar', '...']
  196.  *
  197.  * - symfony_mailer:
  198.  *   - from_email: optional if email_prototype is given
  199.  *   - to_email: optional if email_prototype is given
  200.  *   - subject: optional if email_prototype is given
  201.  *   - [email_prototype]: service id of a message, defaults to a default message with the three fields above
  202.  *   - [mailer]: mailer service id, defaults to mailer.mailer
  203.  *   - [level]: level name or int value, defaults to DEBUG
  204.  *   - [bubble]: bool, defaults to true
  205.  *
  206.  * - socket:
  207.  *   - connection_string: string
  208.  *   - [timeout]: float
  209.  *   - [connection_timeout]: float
  210.  *   - [persistent]: bool
  211.  *   - [level]: level name or int value, defaults to DEBUG
  212.  *   - [bubble]: bool, defaults to true
  213.  *
  214.  * - pushover:
  215.  *   - token: pushover api token
  216.  *   - user: user id or array of ids
  217.  *   - [title]: optional title for messages, defaults to the server hostname
  218.  *   - [level]: level name or int value, defaults to DEBUG
  219.  *   - [bubble]: bool, defaults to true
  220.  *   - [timeout]: float
  221.  *   - [connection_timeout]: float
  222.  *
  223.  * - raven / sentry:
  224.  *   - dsn: connection string
  225.  *   - client_id: Raven client custom service id (optional)
  226.  *   - [release]: release number of the application that will be attached to logs, defaults to null
  227.  *   - [level]: level name or int value, defaults to DEBUG
  228.  *   - [bubble]: bool, defaults to true
  229.  *   - [auto_log_stacks]: bool, defaults to false
  230.  *   - [environment]: string, default to null (no env specified)
  231.  *
  232.  * - sentry:
  233.  *   - hub_id: Sentry hub custom service id (optional)
  234.  *   - [fill_extra_context]: bool, defaults to false
  235.  *
  236.  * - newrelic:
  237.  *   - [level]: level name or int value, defaults to DEBUG
  238.  *   - [bubble]: bool, defaults to true
  239.  *   - [app_name]: new relic app name, default null
  240.  *
  241.  * - hipchat:
  242.  *   - token: hipchat api token
  243.  *   - room: room id or name
  244.  *   - [notify]: defaults to false
  245.  *   - [nickname]: defaults to Monolog
  246.  *   - [level]: level name or int value, defaults to DEBUG
  247.  *   - [bubble]: bool, defaults to true
  248.  *   - [use_ssl]: bool, defaults to true
  249.  *   - [message_format]: text or html, defaults to text
  250.  *   - [host]: defaults to "api.hipchat.com"
  251.  *   - [api_version]: defaults to "v1"
  252.  *   - [timeout]: float
  253.  *   - [connection_timeout]: float
  254.  *
  255.  * - slack:
  256.  *   - token: slack api token
  257.  *   - channel: channel name (with starting #)
  258.  *   - [bot_name]: defaults to Monolog
  259.  *   - [icon_emoji]: defaults to null
  260.  *   - [use_attachment]: bool, defaults to true
  261.  *   - [use_short_attachment]: bool, defaults to false
  262.  *   - [include_extra]: bool, defaults to false
  263.  *   - [level]: level name or int value, defaults to DEBUG
  264.  *   - [bubble]: bool, defaults to true
  265.  *   - [timeout]: float
  266.  *   - [connection_timeout]: float
  267.  *
  268.  * - slackwebhook:
  269.  *   - webhook_url: slack webhook URL
  270.  *   - channel: channel name (with starting #)
  271.  *   - [bot_name]: defaults to Monolog
  272.  *   - [icon_emoji]: defaults to null
  273.  *   - [use_attachment]: bool, defaults to true
  274.  *   - [use_short_attachment]: bool, defaults to false
  275.  *   - [include_extra]: bool, defaults to false
  276.  *   - [level]: level name or int value, defaults to DEBUG
  277.  *   - [bubble]: bool, defaults to true
  278.  *
  279.  * - slackbot:
  280.  *   - team: slack team slug
  281.  *   - token: slackbot token
  282.  *   - channel: channel name (with starting #)
  283.  *   - [level]: level name or int value, defaults to DEBUG
  284.  *   - [bubble]: bool, defaults to true
  285.  *
  286.  * - cube:
  287.  *   - url: http/udp url to the cube server
  288.  *   - [level]: level name or int value, defaults to DEBUG
  289.  *   - [bubble]: bool, defaults to true
  290.  *
  291.  * - amqp:
  292.  *   - exchange: service id of an AMQPExchange
  293.  *   - [exchange_name]: string, defaults to log
  294.  *   - [level]: level name or int value, defaults to DEBUG
  295.  *   - [bubble]: bool, defaults to true
  296.  *
  297.  * - error_log:
  298.  *   - [message_type]: int 0 or 4, defaults to 0
  299.  *   - [level]: level name or int value, defaults to DEBUG
  300.  *   - [bubble]: bool, defaults to true
  301.  *
  302.  * - null:
  303.  *   - [level]: level name or int value, defaults to DEBUG
  304.  *   - [bubble]: bool, defaults to true
  305.  *
  306.  * - test:
  307.  *   - [level]: level name or int value, defaults to DEBUG
  308.  *   - [bubble]: bool, defaults to true
  309.  *
  310.  * - debug:
  311.  *   - [level]: level name or int value, defaults to DEBUG
  312.  *   - [bubble]: bool, defaults to true
  313.  *
  314.  * - loggly:
  315.  *   - token: loggly api token
  316.  *   - [level]: level name or int value, defaults to DEBUG
  317.  *   - [bubble]: bool, defaults to true
  318.  *   - [tags]: tag names
  319.  *
  320.  * - logentries:
  321.  *   - token: logentries api token
  322.  *   - [use_ssl]: whether or not SSL encryption should be used, defaults to true
  323.  *   - [level]: level name or int value, defaults to DEBUG
  324.  *   - [bubble]: bool, defaults to true
  325.  *   - [timeout]: float
  326.  *   - [connection_timeout]: float
  327.  *
  328.  * - insightops:
  329.  *   - token: Log token supplied by InsightOps
  330.  *   - region: Region where InsightOps account is hosted. Could be 'us' or 'eu'. Defaults to 'us'
  331.  *   - [use_ssl]: whether or not SSL encryption should be used, defaults to true
  332.  *   - [level]: level name or int value, defaults to DEBUG
  333.  *   - [bubble]: bool, defaults to true
  334.  *
  335.  * - flowdock:
  336.  *   - token: flowdock api token
  337.  *   - source: human readable identifier of the application
  338.  *   - from_email: email address of the message sender
  339.  *   - [level]: level name or int value, defaults to DEBUG
  340.  *   - [bubble]: bool, defaults to true
  341.  *
  342.  * - rollbar:
  343.  *   - id: RollbarNotifier service (mandatory if token is not provided)
  344.  *   - token: rollbar api token (skip if you provide a RollbarNotifier service id)
  345.  *   - [config]: config values from https://github.com/rollbar/rollbar-php#configuration-reference
  346.  *   - [level]: level name or int value, defaults to DEBUG
  347.  *   - [bubble]: bool, defaults to true
  348.  *
  349.  * - server_log:
  350.  *   - host: server log host. ex: 127.0.0.1:9911
  351.  *   - [level]: level name or int value, defaults to DEBUG
  352.  *   - [bubble]: bool, defaults to true
  353.  *
  354.  * - telegram:
  355.  *   - token: Telegram bot access token provided by BotFather
  356.  *   - channel: Telegram channel name
  357.  *   - [level]: level name or int value, defaults to DEBUG
  358.  *   - [bubble]: bool, defaults to true
  359.  *   - [parse_mode]: optional the kind of formatting that is used for the message
  360.  *   - [disable_webpage_preview]: bool, defaults to false, disables link previews for links in the message
  361.  *   - [disable_notification]: bool, defaults to false, sends the message silently. Users will receive a notification with no sound
  362.  *   - [split_long_messages]: bool, defaults to false, split messages longer than 4096 bytes into multiple messages
  363.  *   - [delay_between_messages]: bool, defaults to false, adds a 1sec delay/sleep between sending split messages
  364.  *
  365.  * All handlers can also be marked with `nested: true` to make sure they are never added explicitly to the stack
  366.  *
  367.  * @author Jordi Boggiano <j.boggiano@seld.be>
  368.  * @author Christophe Coevoet <stof@notk.org>
  369.  */
  370. class Configuration implements ConfigurationInterface
  371. {
  372.     /**
  373.      * Generates the configuration tree builder.
  374.      *
  375.      * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
  376.      */
  377.     public function getConfigTreeBuilder()
  378.     {
  379.         $treeBuilder = new TreeBuilder('monolog');
  380.         $rootNode method_exists(TreeBuilder::class, 'getRootNode') ? $treeBuilder->getRootNode() : $treeBuilder->root('monolog');
  381.         $handlers $rootNode
  382.             ->fixXmlConfig('channel')
  383.             ->fixXmlConfig('handler')
  384.             ->children()
  385.                 ->scalarNode('use_microseconds')->defaultTrue()->end()
  386.                 ->arrayNode('channels')
  387.                     ->canBeUnset()
  388.                     ->prototype('scalar')->end()
  389.                 ->end()
  390.                 ->arrayNode('handlers');
  391.         $handlers
  392.             ->canBeUnset()
  393.             ->useAttributeAsKey('name')
  394.             ->validate()
  395.                 ->ifTrue(function ($v) { return isset($v['debug']); })
  396.                 ->thenInvalid('The "debug" name cannot be used as it is reserved for the handler of the profiler')
  397.             ->end()
  398.             ->example([
  399.                 'syslog' => [
  400.                     'type' => 'stream',
  401.                     'path' => '/var/log/symfony.log',
  402.                     'level' => 'ERROR',
  403.                     'bubble' => 'false',
  404.                     'formatter' => 'my_formatter',
  405.                 ],
  406.                 'main' => [
  407.                     'type' => 'fingers_crossed',
  408.                     'action_level' => 'WARNING',
  409.                     'buffer_size' => 30,
  410.                     'handler' => 'custom',
  411.                 ],
  412.                 'custom' => [
  413.                     'type' => 'service',
  414.                     'id' => 'my_handler',
  415.                 ]
  416.             ]);
  417.         $handlerNode $handlers
  418.             ->prototype('array')
  419.                 ->fixXmlConfig('member')
  420.                 ->fixXmlConfig('excluded_404')
  421.                 ->fixXmlConfig('excluded_http_code')
  422.                 ->fixXmlConfig('tag')
  423.                 ->fixXmlConfig('accepted_level')
  424.                 ->fixXmlConfig('header')
  425.                 ->canBeUnset();
  426.         $handlerNode
  427.             ->children()
  428.                 ->scalarNode('type')
  429.                     ->isRequired()
  430.                     ->treatNullLike('null')
  431.                     ->beforeNormalization()
  432.                         ->always()
  433.                         ->then(function ($v) { return strtolower($v); })
  434.                     ->end()
  435.                 ->end()
  436.                 ->scalarNode('id')->end() // service & rollbar
  437.                 ->scalarNode('priority')->defaultValue(0)->end()
  438.                 ->scalarNode('level')->defaultValue('DEBUG')->end()
  439.                 ->booleanNode('bubble')->defaultTrue()->end()
  440.                 ->scalarNode('app_name')->defaultNull()->end()
  441.                 ->booleanNode('fill_extra_context')->defaultFalse()->end() // sentry
  442.                 ->booleanNode('include_stacktraces')->defaultFalse()->end()
  443.                 ->arrayNode('process_psr_3_messages')
  444.                     ->addDefaultsIfNotSet()
  445.                     ->beforeNormalization()
  446.                         ->ifTrue(static function ($v) { return !\is_array($v); })
  447.                         ->then(static function ($v) { return ['enabled' => $v]; })
  448.                     ->end()
  449.                     ->children()
  450.                         ->booleanNode('enabled')->defaultNull()->end()
  451.                         ->scalarNode('date_format')->end()
  452.                         ->booleanNode('remove_used_context_fields')->end()
  453.                     ->end()
  454.                 ->end()
  455.                 ->scalarNode('path')->defaultValue('%kernel.logs_dir%/%kernel.environment%.log')->end() // stream and rotating
  456.                 ->scalarNode('file_permission')  // stream and rotating
  457.                     ->defaultNull()
  458.                     ->beforeNormalization()
  459.                         ->ifString()
  460.                         ->then(function ($v) {
  461.                             if (substr($v01) === '0') {
  462.                                 return octdec($v);
  463.                             }
  464.                             return (int) $v;
  465.                         })
  466.                     ->end()
  467.                 ->end()
  468.                 ->booleanNode('use_locking')->defaultFalse()->end() // stream and rotating
  469.                 ->scalarNode('filename_format')->defaultValue('{filename}-{date}')->end() //rotating
  470.                 ->scalarNode('date_format')->defaultValue('Y-m-d')->end() //rotating
  471.                 ->scalarNode('ident')->defaultFalse()->end() // syslog and syslogudp
  472.                 ->scalarNode('logopts')->defaultValue(LOG_PID)->end() // syslog
  473.                 ->scalarNode('facility')->defaultValue('user')->end() // syslog
  474.                 ->scalarNode('max_files')->defaultValue(0)->end() // rotating
  475.                 ->scalarNode('action_level')->defaultValue('WARNING')->end() // fingers_crossed
  476.                 ->scalarNode('activation_strategy')->defaultNull()->end() // fingers_crossed
  477.                 ->booleanNode('stop_buffering')->defaultTrue()->end()// fingers_crossed
  478.                 ->scalarNode('passthru_level')->defaultNull()->end() // fingers_crossed
  479.                 ->arrayNode('excluded_404s'// fingers_crossed
  480.                     ->canBeUnset()
  481.                     ->prototype('scalar')->end()
  482.                 ->end()
  483.                 ->arrayNode('excluded_http_codes'// fingers_crossed
  484.                     ->canBeUnset()
  485.                     ->beforeNormalization()
  486.                         ->always(function ($values) {
  487.                             return array_map(function ($value) {
  488.                                 /*
  489.                                  * Allows YAML:
  490.                                  *   excluded_http_codes: [403, 404, { 400: ['^/foo', '^/bar'] }]
  491.                                  *
  492.                                  * and XML:
  493.                                  *   <monolog:excluded-http-code code="403">
  494.                                  *     <monolog:url>^/foo</monolog:url>
  495.                                  *     <monolog:url>^/bar</monolog:url>
  496.                                  *   </monolog:excluded-http-code>
  497.                                  *   <monolog:excluded-http-code code="404" />
  498.                                  */
  499.                                 if (is_array($value)) {
  500.                                     return isset($value['code']) ? $value : ['code' => key($value), 'urls' => current($value)];
  501.                                 }
  502.                                 return ['code' => $value'urls' => []];
  503.                             }, $values);
  504.                         })
  505.                     ->end()
  506.                     ->prototype('array')
  507.                         ->children()
  508.                             ->scalarNode('code')->end()
  509.                             ->arrayNode('urls')
  510.                                 ->prototype('scalar')->end()
  511.                             ->end()
  512.                         ->end()
  513.                     ->end()
  514.                 ->end()
  515.                 ->arrayNode('accepted_levels'// filter
  516.                     ->canBeUnset()
  517.                     ->prototype('scalar')->end()
  518.                 ->end()
  519.                 ->scalarNode('min_level')->defaultValue('DEBUG')->end() // filter
  520.                 ->scalarNode('max_level')->defaultValue('EMERGENCY')->end() //filter
  521.                 ->scalarNode('buffer_size')->defaultValue(0)->end() // fingers_crossed and buffer
  522.                 ->booleanNode('flush_on_overflow')->defaultFalse()->end() // buffer
  523.                 ->scalarNode('handler')->end() // fingers_crossed and buffer
  524.                 ->scalarNode('url')->end() // cube
  525.                 ->scalarNode('exchange')->end() // amqp
  526.                 ->scalarNode('exchange_name')->defaultValue('log')->end() // amqp
  527.                 ->scalarNode('room')->end() // hipchat
  528.                 ->scalarNode('message_format')->defaultValue('text')->end() // hipchat
  529.                 ->scalarNode('api_version')->defaultNull()->end() // hipchat
  530.                 ->scalarNode('channel')->defaultNull()->end() // slack & slackwebhook & slackbot & telegram
  531.                 ->scalarNode('bot_name')->defaultValue('Monolog')->end() // slack & slackwebhook
  532.                 ->scalarNode('use_attachment')->defaultTrue()->end() // slack & slackwebhook
  533.                 ->scalarNode('use_short_attachment')->defaultFalse()->end() // slack & slackwebhook
  534.                 ->scalarNode('include_extra')->defaultFalse()->end() // slack & slackwebhook
  535.                 ->scalarNode('icon_emoji')->defaultNull()->end() // slack & slackwebhook
  536.                 ->scalarNode('webhook_url')->end() // slackwebhook
  537.                 ->scalarNode('team')->end() // slackbot
  538.                 ->scalarNode('notify')->defaultFalse()->end() // hipchat
  539.                 ->scalarNode('nickname')->defaultValue('Monolog')->end() // hipchat
  540.                 ->scalarNode('token')->end() // pushover & hipchat & loggly & logentries & flowdock & rollbar & slack & slackbot & insightops & telegram
  541.                 ->scalarNode('region')->end() // insightops
  542.                 ->scalarNode('source')->end() // flowdock
  543.                 ->booleanNode('use_ssl')->defaultTrue()->end() // logentries & hipchat & insightops
  544.                 ->variableNode('user'// pushover
  545.                     ->validate()
  546.                         ->ifTrue(function ($v) {
  547.                             return !is_string($v) && !is_array($v);
  548.                         })
  549.                         ->thenInvalid('User must be a string or an array.')
  550.                     ->end()
  551.                 ->end()
  552.                 ->scalarNode('title')->defaultNull()->end() // pushover
  553.                 ->scalarNode('host')->defaultNull()->end() // syslogudp & hipchat
  554.                 ->scalarNode('port')->defaultValue(514)->end() // syslogudp
  555.                 ->arrayNode('config')
  556.                     ->canBeUnset()
  557.                     ->prototype('scalar')->end()
  558.                 ->end() // rollbar
  559.                 ->arrayNode('members'// group, whatfailuregroup, fallbackgroup
  560.                     ->canBeUnset()
  561.                     ->performNoDeepMerging()
  562.                     ->prototype('scalar')->end()
  563.                 ->end()
  564.                 ->scalarNode('connection_string')->end() // socket_handler
  565.                 ->scalarNode('timeout')->end() // socket_handler, logentries, pushover, hipchat & slack
  566.                 ->scalarNode('time')->defaultValue(60)->end() // deduplication
  567.                 ->scalarNode('deduplication_level')->defaultValue(Logger::ERROR)->end() // deduplication
  568.                 ->scalarNode('store')->defaultNull()->end() // deduplication
  569.                 ->scalarNode('connection_timeout')->end() // socket_handler, logentries, pushover, hipchat & slack
  570.                 ->booleanNode('persistent')->end() // socket_handler
  571.                 ->scalarNode('dsn')->end() // raven_handler, sentry_handler
  572.                 ->scalarNode('hub_id')->defaultNull()->end() // sentry_handler
  573.                 ->scalarNode('client_id')->defaultNull()->end() // raven_handler, sentry_handler
  574.                 ->scalarNode('auto_log_stacks')->defaultFalse()->end() // raven_handler
  575.                 ->scalarNode('release')->defaultNull()->end() // raven_handler, sentry_handler
  576.                 ->scalarNode('environment')->defaultNull()->end() // raven_handler, sentry_handler
  577.                 ->scalarNode('message_type')->defaultValue(0)->end() // error_log
  578.                 ->scalarNode('parse_mode')->defaultNull()->end() // telegram
  579.                 ->booleanNode('disable_webpage_preview')->defaultNull()->end() // telegram
  580.                 ->booleanNode('disable_notification')->defaultNull()->end() // telegram
  581.                 ->booleanNode('split_long_messages')->defaultFalse()->end() // telegram
  582.                 ->booleanNode('delay_between_messages')->defaultFalse()->end() // telegram
  583.                 ->arrayNode('tags'// loggly
  584.                     ->beforeNormalization()
  585.                         ->ifString()
  586.                         ->then(function ($v) { return explode(','$v); })
  587.                     ->end()
  588.                     ->beforeNormalization()
  589.                         ->ifArray()
  590.                         ->then(function ($v) { return array_filter(array_map('trim'$v)); })
  591.                     ->end()
  592.                     ->prototype('scalar')->end()
  593.                 ->end()
  594.                  // console
  595.                 ->variableNode('console_formater_options')
  596.                     ->setDeprecated(...$this->getDeprecationMsg('"%path%.%node%" is deprecated, use "%path%.console_formatter_options" instead.'3.7))
  597.                     ->validate()
  598.                         ->ifTrue(function ($v) {
  599.                             return !is_array($v);
  600.                         })
  601.                         ->thenInvalid('The console_formater_options must be an array.')
  602.                     ->end()
  603.                 ->end()
  604.                 ->variableNode('console_formatter_options')
  605.                     ->defaultValue([])
  606.                     ->validate()
  607.                         ->ifTrue(static function ($v) { return !is_array($v); })
  608.                         ->thenInvalid('The console_formatter_options must be an array.')
  609.                     ->end()
  610.                 ->end()
  611.                 ->scalarNode('formatter')->end()
  612.                 ->booleanNode('nested')->defaultFalse()->end()
  613.             ->end();
  614.         $this->addGelfSection($handlerNode);
  615.         $this->addMongoSection($handlerNode);
  616.         $this->addElasticsearchSection($handlerNode);
  617.         $this->addRedisSection($handlerNode);
  618.         $this->addPredisSection($handlerNode);
  619.         $this->addMailerSection($handlerNode);
  620.         $this->addVerbosityLevelSection($handlerNode);
  621.         $this->addChannelsSection($handlerNode);
  622.         $handlerNode
  623.             ->beforeNormalization()
  624.                 ->always(static function ($v) {
  625.                     if (empty($v['console_formatter_options']) && !empty($v['console_formater_options'])) {
  626.                         $v['console_formatter_options'] = $v['console_formater_options'];
  627.                     }
  628.                     return $v;
  629.                 })
  630.             ->end()
  631.             ->validate()
  632.                 ->always(static function ($v) { unset($v['console_formater_options']); return $v; })
  633.             ->end()
  634.             ->validate()
  635.                 ->ifTrue(function ($v) { return 'service' === $v['type'] && !empty($v['formatter']); })
  636.                 ->thenInvalid('Service handlers can not have a formatter configured in the bundle, you must reconfigure the service itself instead')
  637.             ->end()
  638.             ->validate()
  639.                 ->ifTrue(function ($v) { return ('fingers_crossed' === $v['type'] || 'buffer' === $v['type'] || 'filter' === $v['type']) && empty($v['handler']); })
  640.                 ->thenInvalid('The handler has to be specified to use a FingersCrossedHandler or BufferHandler or FilterHandler')
  641.             ->end()
  642.             ->validate()
  643.                 ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_404s']) && !empty($v['activation_strategy']); })
  644.                 ->thenInvalid('You can not use excluded_404s together with a custom activation_strategy in a FingersCrossedHandler')
  645.             ->end()
  646.             ->validate()
  647.                 ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['activation_strategy']); })
  648.                 ->thenInvalid('You can not use excluded_http_codes together with a custom activation_strategy in a FingersCrossedHandler')
  649.             ->end()
  650.             ->validate()
  651.                 ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['excluded_404s']); })
  652.                 ->thenInvalid('You can not use excluded_http_codes together with excluded_404s in a FingersCrossedHandler')
  653.             ->end()
  654.             ->validate()
  655.                 ->ifTrue(function ($v) { return 'fingers_crossed' !== $v['type'] && (!empty($v['excluded_http_codes']) || !empty($v['excluded_404s'])); })
  656.                 ->thenInvalid('You can only use excluded_http_codes/excluded_404s with a FingersCrossedHandler definition')
  657.             ->end()
  658.             ->validate()
  659.                 ->ifTrue(function ($v) { return 'filter' === $v['type'] && "DEBUG" !== $v['min_level'] && !empty($v['accepted_levels']); })
  660.                 ->thenInvalid('You can not use min_level together with accepted_levels in a FilterHandler')
  661.             ->end()
  662.             ->validate()
  663.                 ->ifTrue(function ($v) { return 'filter' === $v['type'] && "EMERGENCY" !== $v['max_level'] && !empty($v['accepted_levels']); })
  664.                 ->thenInvalid('You can not use max_level together with accepted_levels in a FilterHandler')
  665.             ->end()
  666.             ->validate()
  667.                 ->ifTrue(function ($v) { return 'rollbar' === $v['type'] && !empty($v['id']) && !empty($v['token']); })
  668.                 ->thenInvalid('You can not use both an id and a token in a RollbarHandler')
  669.             ->end()
  670.             ->validate()
  671.                 ->ifTrue(function ($v) { return 'rollbar' === $v['type'] && empty($v['id']) && empty($v['token']); })
  672.                 ->thenInvalid('The id or the token has to be specified to use a RollbarHandler')
  673.             ->end()
  674.             ->validate()
  675.                 ->ifTrue(function ($v) { return 'telegram' === $v['type'] && (empty($v['token']) || empty($v['channel'])); })
  676.                 ->thenInvalid('The token and channel have to be specified to use a TelegramBotHandler')
  677.             ->end()
  678.             ->validate()
  679.                 ->ifTrue(function ($v) { return 'service' === $v['type'] && !isset($v['id']); })
  680.                 ->thenInvalid('The id has to be specified to use a service as handler')
  681.             ->end()
  682.             ->validate()
  683.                 ->ifTrue(function ($v) { return 'syslogudp' === $v['type'] && !isset($v['host']); })
  684.                 ->thenInvalid('The host has to be specified to use a syslogudp as handler')
  685.             ->end()
  686.             ->validate()
  687.                 ->ifTrue(function ($v) { return 'socket' === $v['type'] && !isset($v['connection_string']); })
  688.                 ->thenInvalid('The connection_string has to be specified to use a SocketHandler')
  689.             ->end()
  690.             ->validate()
  691.                 ->ifTrue(function ($v) { return 'pushover' === $v['type'] && (empty($v['token']) || empty($v['user'])); })
  692.                 ->thenInvalid('The token and user have to be specified to use a PushoverHandler')
  693.             ->end()
  694.             ->validate()
  695.                 ->ifTrue(function ($v) { return 'raven' === $v['type'] && !array_key_exists('dsn'$v) && null === $v['client_id']; })
  696.                 ->thenInvalid('The DSN has to be specified to use a RavenHandler')
  697.             ->end()
  698.             ->validate()
  699.                 ->ifTrue(function ($v) { return 'sentry' === $v['type'] && !array_key_exists('dsn'$v) && null === $v['hub_id'] && null === $v['client_id']; })
  700.                 ->thenInvalid('The DSN has to be specified to use Sentry\'s handler')
  701.             ->end()
  702.             ->validate()
  703.                 ->ifTrue(function ($v) { return 'sentry' === $v['type'] && null !== $v['hub_id'] && null !== $v['client_id']; })
  704.                 ->thenInvalid('You can not use both a hub_id and a client_id in a Sentry handler')
  705.             ->end()
  706.             ->validate()
  707.                 ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && (empty($v['token']) || empty($v['room'])); })
  708.                 ->thenInvalid('The token and room have to be specified to use a HipChatHandler')
  709.             ->end()
  710.             ->validate()
  711.                 ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && !in_array($v['message_format'], ['text''html']); })
  712.                 ->thenInvalid('The message_format has to be "text" or "html" in a HipChatHandler')
  713.             ->end()
  714.             ->validate()
  715.                 ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && null !== $v['api_version'] && !in_array($v['api_version'], ['v1''v2'], true); })
  716.                 ->thenInvalid('The api_version has to be "v1" or "v2" in a HipChatHandler')
  717.             ->end()
  718.             ->validate()
  719.                 ->ifTrue(function ($v) { return 'slack' === $v['type'] && (empty($v['token']) || empty($v['channel'])); })
  720.                 ->thenInvalid('The token and channel have to be specified to use a SlackHandler')
  721.             ->end()
  722.             ->validate()
  723.                 ->ifTrue(function ($v) { return 'slackwebhook' === $v['type'] && (empty($v['webhook_url'])); })
  724.                 ->thenInvalid('The webhook_url have to be specified to use a SlackWebhookHandler')
  725.             ->end()
  726.             ->validate()
  727.                 ->ifTrue(function ($v) { return 'slackbot' === $v['type'] && (empty($v['team']) || empty($v['token']) || empty($v['channel'])); })
  728.                 ->thenInvalid('The team, token and channel have to be specified to use a SlackbotHandler')
  729.             ->end()
  730.             ->validate()
  731.                 ->ifTrue(function ($v) { return 'cube' === $v['type'] && empty($v['url']); })
  732.                 ->thenInvalid('The url has to be specified to use a CubeHandler')
  733.             ->end()
  734.             ->validate()
  735.                 ->ifTrue(function ($v) { return 'amqp' === $v['type'] && empty($v['exchange']); })
  736.                 ->thenInvalid('The exchange has to be specified to use a AmqpHandler')
  737.             ->end()
  738.             ->validate()
  739.                 ->ifTrue(function ($v) { return 'loggly' === $v['type'] && empty($v['token']); })
  740.                 ->thenInvalid('The token has to be specified to use a LogglyHandler')
  741.             ->end()
  742.             ->validate()
  743.                 ->ifTrue(function ($v) { return 'loggly' === $v['type'] && !empty($v['tags']); })
  744.                 ->then(function ($v) {
  745.                     $invalidTags preg_grep('/^[a-z0-9][a-z0-9\.\-_]*$/i'$v['tags'], PREG_GREP_INVERT);
  746.                     if (!empty($invalidTags)) {
  747.                         throw new InvalidConfigurationException(sprintf('The following Loggly tags are invalid: %s.'implode(', '$invalidTags)));
  748.                     }
  749.                     return $v;
  750.                 })
  751.             ->end()
  752.             ->validate()
  753.                 ->ifTrue(function ($v) { return 'logentries' === $v['type'] && empty($v['token']); })
  754.                 ->thenInvalid('The token has to be specified to use a LogEntriesHandler')
  755.             ->end()
  756.             ->validate()
  757.                 ->ifTrue(function ($v) { return 'insightops' === $v['type'] && empty($v['token']); })
  758.                 ->thenInvalid('The token has to be specified to use a InsightOpsHandler')
  759.             ->end()
  760.             ->validate()
  761.                 ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['token']); })
  762.                 ->thenInvalid('The token has to be specified to use a FlowdockHandler')
  763.             ->end()
  764.             ->validate()
  765.                 ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['from_email']); })
  766.                 ->thenInvalid('The from_email has to be specified to use a FlowdockHandler')
  767.             ->end()
  768.             ->validate()
  769.                 ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['source']); })
  770.                 ->thenInvalid('The source has to be specified to use a FlowdockHandler')
  771.             ->end()
  772.             ->validate()
  773.                 ->ifTrue(function ($v) { return 'server_log' === $v['type'] && empty($v['host']); })
  774.                 ->thenInvalid('The host has to be specified to use a ServerLogHandler')
  775.             ->end()
  776.         ;
  777.         return $treeBuilder;
  778.     }
  779.     private function addGelfSection(ArrayNodeDefinition $handerNode)
  780.     {
  781.         $handerNode
  782.             ->children()
  783.                 ->arrayNode('publisher')
  784.                     ->canBeUnset()
  785.                     ->beforeNormalization()
  786.                         ->ifString()
  787.                         ->then(function ($v) { return ['id' => $v]; })
  788.                     ->end()
  789.                     ->children()
  790.                         ->scalarNode('id')->end()
  791.                         ->scalarNode('hostname')->end()
  792.                         ->scalarNode('port')->defaultValue(12201)->end()
  793.                         ->scalarNode('chunk_size')->defaultValue(1420)->end()
  794.                     ->end()
  795.                     ->validate()
  796.                         ->ifTrue(function ($v) {
  797.                             return !isset($v['id']) && !isset($v['hostname']);
  798.                         })
  799.                         ->thenInvalid('What must be set is either the hostname or the id.')
  800.                     ->end()
  801.                 ->end()
  802.             ->end()
  803.             ->validate()
  804.                 ->ifTrue(function ($v) { return 'gelf' === $v['type'] && !isset($v['publisher']); })
  805.                 ->thenInvalid('The publisher has to be specified to use a GelfHandler')
  806.             ->end()
  807.         ;
  808.     }
  809.     private function addMongoSection(ArrayNodeDefinition $handerNode)
  810.     {
  811.         $handerNode
  812.             ->children()
  813.                 ->arrayNode('mongo')
  814.                     ->canBeUnset()
  815.                     ->beforeNormalization()
  816.                     ->ifString()
  817.                     ->then(function ($v) { return ['id' => $v]; })
  818.                     ->end()
  819.                     ->children()
  820.                         ->scalarNode('id')->end()
  821.                         ->scalarNode('host')->end()
  822.                         ->scalarNode('port')->defaultValue(27017)->end()
  823.                         ->scalarNode('user')->end()
  824.                         ->scalarNode('pass')->end()
  825.                         ->scalarNode('database')->defaultValue('monolog')->end()
  826.                         ->scalarNode('collection')->defaultValue('logs')->end()
  827.                     ->end()
  828.                     ->validate()
  829.                     ->ifTrue(function ($v) {
  830.                         return !isset($v['id']) && !isset($v['host']);
  831.                     })
  832.                     ->thenInvalid('What must be set is either the host or the id.')
  833.                     ->end()
  834.                     ->validate()
  835.                     ->ifTrue(function ($v) {
  836.                         return isset($v['user']) && !isset($v['pass']);
  837.                     })
  838.                     ->thenInvalid('If you set user, you must provide a password.')
  839.                     ->end()
  840.                 ->end()
  841.             ->end()
  842.             ->validate()
  843.                 ->ifTrue(function ($v) { return 'mongo' === $v['type'] && !isset($v['mongo']); })
  844.                 ->thenInvalid('The mongo configuration has to be specified to use a MongoHandler')
  845.             ->end()
  846.         ;
  847.     }
  848.     private function addElasticsearchSection(ArrayNodeDefinition $handerNode)
  849.     {
  850.         $handerNode
  851.             ->children()
  852.                 ->arrayNode('elasticsearch')
  853.                     ->canBeUnset()
  854.                     ->beforeNormalization()
  855.                     ->ifString()
  856.                     ->then(function ($v) { return ['id' => $v]; })
  857.                     ->end()
  858.                     ->children()
  859.                         ->scalarNode('id')->end()
  860.                         ->scalarNode('host')->end()
  861.                         ->scalarNode('port')->defaultValue(9200)->end()
  862.                         ->scalarNode('transport')->defaultValue('Http')->end()
  863.                         ->scalarNode('user')->defaultNull()->end()
  864.                         ->scalarNode('password')->defaultNull()->end()
  865.                     ->end()
  866.                     ->validate()
  867.                     ->ifTrue(function ($v) {
  868.                         return !isset($v['id']) && !isset($v['host']);
  869.                     })
  870.                     ->thenInvalid('What must be set is either the host or the id.')
  871.                     ->end()
  872.                 ->end()
  873.                 ->scalarNode('index')->defaultValue('monolog')->end() // elasticsearch & elastic_search & elastica
  874.                 ->scalarNode('document_type')->defaultValue('logs')->end() // elasticsearch & elastic_search & elastica
  875.                 ->scalarNode('ignore_error')->defaultValue(false)->end() // elasticsearch & elastic_search & elastica
  876.             ->end()
  877.         ;
  878.     }
  879.     private function addRedisSection(ArrayNodeDefinition $handerNode)
  880.     {
  881.         $handerNode
  882.             ->children()
  883.                 ->arrayNode('redis')
  884.                     ->canBeUnset()
  885.                     ->beforeNormalization()
  886.                     ->ifString()
  887.                     ->then(function ($v) { return ['id' => $v]; })
  888.                     ->end()
  889.                     ->children()
  890.                         ->scalarNode('id')->end()
  891.                         ->scalarNode('host')->end()
  892.                         ->scalarNode('password')->defaultNull()->end()
  893.                         ->scalarNode('port')->defaultValue(6379)->end()
  894.                         ->scalarNode('database')->defaultValue(0)->end()
  895.                         ->scalarNode('key_name')->defaultValue('monolog_redis')->end()
  896.                     ->end()
  897.                     ->validate()
  898.                     ->ifTrue(function ($v) {
  899.                         return !isset($v['id']) && !isset($v['host']);
  900.                     })
  901.                     ->thenInvalid('What must be set is either the host or the service id of the Redis client.')
  902.                     ->end()
  903.                 ->end()
  904.             ->end()
  905.             ->validate()
  906.                 ->ifTrue(function ($v) { return 'redis' === $v['type'] && empty($v['redis']); })
  907.                 ->thenInvalid('The host has to be specified to use a RedisLogHandler')
  908.             ->end()
  909.         ;
  910.     }
  911.     private function addPredisSection(ArrayNodeDefinition $handerNode)
  912.     {
  913.         $handerNode
  914.             ->children()
  915.                 ->arrayNode('predis')
  916.                     ->canBeUnset()
  917.                     ->beforeNormalization()
  918.                     ->ifString()
  919.                     ->then(function ($v) { return ['id' => $v]; })
  920.                     ->end()
  921.                     ->children()
  922.                         ->scalarNode('id')->end()
  923.                         ->scalarNode('host')->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 Predis client.')
  930.                     ->end()
  931.                 ->end()
  932.             ->end()
  933.             ->validate()
  934.                 ->ifTrue(function ($v) { return 'predis' === $v['type'] && empty($v['redis']); })
  935.                 ->thenInvalid('The host has to be specified to use a RedisLogHandler')
  936.             ->end()
  937.         ;
  938.     }
  939.     private function addMailerSection(ArrayNodeDefinition $handerNode)
  940.     {
  941.         $handerNode
  942.             ->children()
  943.                 ->scalarNode('from_email')->end() // swift_mailer, native_mailer, symfony_mailer and flowdock
  944.                 ->arrayNode('to_email'// swift_mailer, native_mailer and symfony_mailer
  945.                     ->prototype('scalar')->end()
  946.                     ->beforeNormalization()
  947.                         ->ifString()
  948.                         ->then(function ($v) { return [$v]; })
  949.                     ->end()
  950.                 ->end()
  951.                 ->scalarNode('subject')->end() // swift_mailer, native_mailer and symfony_mailer
  952.                 ->scalarNode('content_type')->defaultNull()->end() // swift_mailer and symfony_mailer
  953.                 ->arrayNode('headers'// native_mailer
  954.                     ->canBeUnset()
  955.                     ->scalarPrototype()->end()
  956.                 ->end()
  957.                 ->scalarNode('mailer')->defaultNull()->end() // swift_mailer and symfony_mailer
  958.                 ->arrayNode('email_prototype'// swift_mailer and symfony_mailer
  959.                     ->canBeUnset()
  960.                     ->beforeNormalization()
  961.                         ->ifString()
  962.                         ->then(function ($v) { return ['id' => $v]; })
  963.                     ->end()
  964.                     ->children()
  965.                         ->scalarNode('id')->isRequired()->end()
  966.                         ->scalarNode('method')->defaultNull()->end()
  967.                     ->end()
  968.                 ->end()
  969.                 ->booleanNode('lazy')->defaultValue(true)->end() // swift_mailer
  970.             ->end()
  971.             ->validate()
  972.                 ->ifTrue(function ($v) { return 'swift_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })
  973.                 ->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use a SwiftMailerHandler')
  974.             ->end()
  975.             ->validate()
  976.                 ->ifTrue(function ($v) { return 'native_mailer' === $v['type'] && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })
  977.                 ->thenInvalid('The sender, recipient and subject have to be specified to use a NativeMailerHandler')
  978.             ->end()
  979.             ->validate()
  980.                 ->ifTrue(function ($v) { return 'symfony_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })
  981.                 ->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use the Symfony MailerHandler')
  982.             ->end()
  983.         ;
  984.     }
  985.     private function addVerbosityLevelSection(ArrayNodeDefinition $handerNode)
  986.     {
  987.         $handerNode
  988.             ->children()
  989.                 ->arrayNode('verbosity_levels'// console
  990.                     ->beforeNormalization()
  991.                         ->ifArray()
  992.                         ->then(function ($v) {
  993.                             $map = [];
  994.                             $verbosities = ['VERBOSITY_QUIET''VERBOSITY_NORMAL''VERBOSITY_VERBOSE''VERBOSITY_VERY_VERBOSE''VERBOSITY_DEBUG'];
  995.                             // allow numeric indexed array with ascendning verbosity and lowercase names of the constants
  996.                             foreach ($v as $verbosity => $level) {
  997.                                 if (is_int($verbosity) && isset($verbosities[$verbosity])) {
  998.                                     $map[$verbosities[$verbosity]] = strtoupper($level);
  999.                                 } else {
  1000.                                     $map[strtoupper($verbosity)] = strtoupper($level);
  1001.                                 }
  1002.                             }
  1003.                             return $map;
  1004.                         })
  1005.                     ->end()
  1006.                     ->children()
  1007.                         ->scalarNode('VERBOSITY_QUIET')->defaultValue('ERROR')->end()
  1008.                         ->scalarNode('VERBOSITY_NORMAL')->defaultValue('WARNING')->end()
  1009.                         ->scalarNode('VERBOSITY_VERBOSE')->defaultValue('NOTICE')->end()
  1010.                         ->scalarNode('VERBOSITY_VERY_VERBOSE')->defaultValue('INFO')->end()
  1011.                         ->scalarNode('VERBOSITY_DEBUG')->defaultValue('DEBUG')->end()
  1012.                     ->end()
  1013.                     ->validate()
  1014.                         ->always(function ($v) {
  1015.                             $map = [];
  1016.                             foreach ($v as $verbosity => $level) {
  1017.                                 $verbosityConstant 'Symfony\Component\Console\Output\OutputInterface::'.$verbosity;
  1018.                                 if (!defined($verbosityConstant)) {
  1019.                                     throw new InvalidConfigurationException(sprintf(
  1020.                                         'The configured verbosity "%s" is invalid as it is not defined in Symfony\Component\Console\Output\OutputInterface.',
  1021.                                          $verbosity
  1022.                                     ));
  1023.                                 }
  1024.                                 try {
  1025.                                     if (Logger::API === 3) {
  1026.                                         $level Logger::toMonologLevel($level)->value;
  1027.                                     } else {
  1028.                                         $level Logger::toMonologLevel(is_numeric($level) ? (int) $level $level);
  1029.                                     }
  1030.                                 } catch (\Psr\Log\InvalidArgumentException $e) {
  1031.                                     throw new InvalidConfigurationException(sprintf(
  1032.                                         'The configured minimum log level "%s" for verbosity "%s" is invalid as it is not defined in Monolog\Logger.',
  1033.                                          $level$verbosity
  1034.                                     ));
  1035.                                 }
  1036.                                 $map[constant($verbosityConstant)] = $level;
  1037.                             }
  1038.                             return $map;
  1039.                         })
  1040.                     ->end()
  1041.                 ->end()
  1042.             ->end()
  1043.         ;
  1044.     }
  1045.     private function addChannelsSection(ArrayNodeDefinition $handerNode)
  1046.     {
  1047.         $handerNode
  1048.             ->children()
  1049.                 ->arrayNode('channels')
  1050.                     ->fixXmlConfig('channel''elements')
  1051.                     ->canBeUnset()
  1052.                     ->beforeNormalization()
  1053.                         ->ifString()
  1054.                         ->then(function ($v) { return ['elements' => [$v]]; })
  1055.                     ->end()
  1056.                     ->beforeNormalization()
  1057.                         ->ifTrue(function ($v) { return is_array($v) && is_numeric(key($v)); })
  1058.                         ->then(function ($v) { return ['elements' => $v]; })
  1059.                     ->end()
  1060.                     ->validate()
  1061.                         ->ifTrue(function ($v) { return empty($v); })
  1062.                         ->thenUnset()
  1063.                     ->end()
  1064.                     ->validate()
  1065.                         ->always(function ($v) {
  1066.                             $isExclusive null;
  1067.                             if (isset($v['type'])) {
  1068.                                 $isExclusive 'exclusive' === $v['type'];
  1069.                             }
  1070.                             $elements = [];
  1071.                             foreach ($v['elements'] as $element) {
  1072.                                 if (=== strpos($element'!')) {
  1073.                                     if (false === $isExclusive) {
  1074.                                         throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list.');
  1075.                                     }
  1076.                                     $elements[] = substr($element1);
  1077.                                     $isExclusive true;
  1078.                                 } else {
  1079.                                     if (true === $isExclusive) {
  1080.                                         throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list');
  1081.                                     }
  1082.                                     $elements[] = $element;
  1083.                                     $isExclusive false;
  1084.                                 }
  1085.                             }
  1086.                             if (!count($elements)) {
  1087.                                 return null;
  1088.                             }
  1089.                             // de-duplicating $elements here in case the handlers are redefined, see https://github.com/symfony/monolog-bundle/issues/433
  1090.                             return ['type' => $isExclusive 'exclusive' 'inclusive''elements' => array_unique($elements)];
  1091.                         })
  1092.                     ->end()
  1093.                     ->children()
  1094.                         ->scalarNode('type')
  1095.                             ->validate()
  1096.                                 ->ifNotInArray(['inclusive''exclusive'])
  1097.                                 ->thenInvalid('The type of channels has to be inclusive or exclusive')
  1098.                             ->end()
  1099.                         ->end()
  1100.                         ->arrayNode('elements')
  1101.                             ->prototype('scalar')->end()
  1102.                         ->end()
  1103.                     ->end()
  1104.                 ->end()
  1105.             ->end()
  1106.         ;
  1107.     }
  1108.     /**
  1109.      * Returns the correct deprecation param's as an array for setDeprecated.
  1110.      *
  1111.      * Symfony/Config v5.1 introduces a deprecation notice when calling
  1112.      * setDeprecation() with less than 3 args and the getDeprecation method was
  1113.      * introduced at the same time. By checking if getDeprecation() exists,
  1114.      * we can determine the correct param count to use when calling setDeprecated.
  1115.      *
  1116.      * @return array{0:string}|array{0:string, 1: numeric-string, string}
  1117.      */
  1118.     private function getDeprecationMsg(string $messagestring $version): array
  1119.     {
  1120.         if (method_exists(BaseNode::class, 'getDeprecation')) {
  1121.             return [
  1122.                 'symfony/monolog-bundle',
  1123.                 $version,
  1124.                 $message,
  1125.             ];
  1126.         }
  1127.         return [$message];
  1128.     }
  1129. }