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

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.  * - sampling:
  366.  *   - handler: the wrapped handler's name
  367.  *   - factor: the sampling factor (e.g. 10 means every ~10th record is sampled)
  368.  *
  369.  * All handlers can also be marked with `nested: true` to make sure they are never added explicitly to the stack
  370.  *
  371.  * @author Jordi Boggiano <j.boggiano@seld.be>
  372.  * @author Christophe Coevoet <stof@notk.org>
  373.  *
  374.  * @finalsince 3.9.0
  375.  */
  376. class Configuration implements ConfigurationInterface
  377. {
  378.     /**
  379.      * Generates the configuration tree builder.
  380.      */
  381.     public function getConfigTreeBuilder(): TreeBuilder
  382.     {
  383.         $treeBuilder = new TreeBuilder('monolog');
  384.         $rootNode $treeBuilder->getRootNode();
  385.         $handlers $rootNode
  386.             ->fixXmlConfig('channel')
  387.             ->fixXmlConfig('handler')
  388.             ->children()
  389.                 ->scalarNode('use_microseconds')->defaultTrue()->end()
  390.                 ->arrayNode('channels')
  391.                     ->canBeUnset()
  392.                     ->prototype('scalar')->end()
  393.                 ->end()
  394.                 ->arrayNode('handlers');
  395.         $handlers
  396.             ->canBeUnset()
  397.             ->useAttributeAsKey('name')
  398.             ->validate()
  399.                 ->ifTrue(function ($v) { return isset($v['debug']); })
  400.                 ->thenInvalid('The "debug" name cannot be used as it is reserved for the handler of the profiler')
  401.             ->end()
  402.             ->example([
  403.                 'syslog' => [
  404.                     'type' => 'stream',
  405.                     'path' => '/var/log/symfony.log',
  406.                     'level' => 'ERROR',
  407.                     'bubble' => 'false',
  408.                     'formatter' => 'my_formatter',
  409.                 ],
  410.                 'main' => [
  411.                     'type' => 'fingers_crossed',
  412.                     'action_level' => 'WARNING',
  413.                     'buffer_size' => 30,
  414.                     'handler' => 'custom',
  415.                 ],
  416.                 'custom' => [
  417.                     'type' => 'service',
  418.                     'id' => 'my_handler',
  419.                 ]
  420.             ]);
  421.         $handlerNode $handlers
  422.             ->prototype('array')
  423.                 ->fixXmlConfig('member')
  424.                 ->fixXmlConfig('excluded_404')
  425.                 ->fixXmlConfig('excluded_http_code')
  426.                 ->fixXmlConfig('tag')
  427.                 ->fixXmlConfig('accepted_level')
  428.                 ->fixXmlConfig('header')
  429.                 ->canBeUnset();
  430.         $handlerNode
  431.             ->children()
  432.                 ->scalarNode('type')
  433.                     ->isRequired()
  434.                     ->treatNullLike('null')
  435.                     ->beforeNormalization()
  436.                         ->always()
  437.                         ->then(function ($v) { return strtolower($v); })
  438.                     ->end()
  439.                 ->end()
  440.                 ->scalarNode('id')->end() // service & rollbar
  441.                 ->scalarNode('priority')->defaultValue(0)->end()
  442.                 ->scalarNode('level')->defaultValue('DEBUG')->end()
  443.                 ->booleanNode('bubble')->defaultTrue()->end()
  444.                 ->scalarNode('app_name')->defaultNull()->end()
  445.                 ->booleanNode('fill_extra_context')->defaultFalse()->end() // sentry
  446.                 ->booleanNode('include_stacktraces')->defaultFalse()->end()
  447.                 ->arrayNode('process_psr_3_messages')
  448.                     ->addDefaultsIfNotSet()
  449.                     ->beforeNormalization()
  450.                         ->ifTrue(static function ($v) { return !\is_array($v); })
  451.                         ->then(static function ($v) { return ['enabled' => $v]; })
  452.                     ->end()
  453.                     ->children()
  454.                         ->booleanNode('enabled')->defaultNull()->end()
  455.                         ->scalarNode('date_format')->end()
  456.                         ->booleanNode('remove_used_context_fields')->end()
  457.                     ->end()
  458.                 ->end()
  459.                 ->scalarNode('path')->defaultValue('%kernel.logs_dir%/%kernel.environment%.log')->end() // stream and rotating
  460.                 ->scalarNode('file_permission')  // stream and rotating
  461.                     ->defaultNull()
  462.                     ->beforeNormalization()
  463.                         ->ifString()
  464.                         ->then(function ($v) {
  465.                             if (substr($v01) === '0') {
  466.                                 return octdec($v);
  467.                             }
  468.                             return (int) $v;
  469.                         })
  470.                     ->end()
  471.                 ->end()
  472.                 ->booleanNode('use_locking')->defaultFalse()->end() // stream and rotating
  473.                 ->scalarNode('filename_format')->defaultValue('{filename}-{date}')->end() //rotating
  474.                 ->scalarNode('date_format')->defaultValue('Y-m-d')->end() //rotating
  475.                 ->scalarNode('ident')->defaultFalse()->end() // syslog and syslogudp
  476.                 ->scalarNode('logopts')->defaultValue(LOG_PID)->end() // syslog
  477.                 ->scalarNode('facility')->defaultValue('user')->end() // syslog
  478.                 ->scalarNode('max_files')->defaultValue(0)->end() // rotating
  479.                 ->scalarNode('action_level')->defaultValue('WARNING')->end() // fingers_crossed
  480.                 ->scalarNode('activation_strategy')->defaultNull()->end() // fingers_crossed
  481.                 ->booleanNode('stop_buffering')->defaultTrue()->end()// fingers_crossed
  482.                 ->scalarNode('passthru_level')->defaultNull()->end() // fingers_crossed
  483.                 ->arrayNode('excluded_404s'// fingers_crossed
  484.                     ->canBeUnset()
  485.                     ->prototype('scalar')->end()
  486.                 ->end()
  487.                 ->arrayNode('excluded_http_codes'// fingers_crossed
  488.                     ->canBeUnset()
  489.                     ->beforeNormalization()
  490.                         ->always(function ($values) {
  491.                             return array_map(function ($value) {
  492.                                 /*
  493.                                  * Allows YAML:
  494.                                  *   excluded_http_codes: [403, 404, { 400: ['^/foo', '^/bar'] }]
  495.                                  *
  496.                                  * and XML:
  497.                                  *   <monolog:excluded-http-code code="403">
  498.                                  *     <monolog:url>^/foo</monolog:url>
  499.                                  *     <monolog:url>^/bar</monolog:url>
  500.                                  *   </monolog:excluded-http-code>
  501.                                  *   <monolog:excluded-http-code code="404" />
  502.                                  */
  503.                                 if (is_array($value)) {
  504.                                     return isset($value['code']) ? $value : ['code' => key($value), 'urls' => current($value)];
  505.                                 }
  506.                                 return ['code' => $value'urls' => []];
  507.                             }, $values);
  508.                         })
  509.                     ->end()
  510.                     ->prototype('array')
  511.                         ->children()
  512.                             ->scalarNode('code')->end()
  513.                             ->arrayNode('urls')
  514.                                 ->prototype('scalar')->end()
  515.                             ->end()
  516.                         ->end()
  517.                     ->end()
  518.                 ->end()
  519.                 ->arrayNode('accepted_levels'// filter
  520.                     ->canBeUnset()
  521.                     ->prototype('scalar')->end()
  522.                 ->end()
  523.                 ->scalarNode('min_level')->defaultValue('DEBUG')->end() // filter
  524.                 ->scalarNode('max_level')->defaultValue('EMERGENCY')->end() //filter
  525.                 ->scalarNode('buffer_size')->defaultValue(0)->end() // fingers_crossed and buffer
  526.                 ->booleanNode('flush_on_overflow')->defaultFalse()->end() // buffer
  527.                 ->scalarNode('handler')->end() // fingers_crossed and buffer
  528.                 ->scalarNode('url')->end() // cube
  529.                 ->scalarNode('exchange')->end() // amqp
  530.                 ->scalarNode('exchange_name')->defaultValue('log')->end() // amqp
  531.                 ->scalarNode('room')->end() // hipchat
  532.                 ->scalarNode('message_format')->defaultValue('text')->end() // hipchat
  533.                 ->scalarNode('api_version')->defaultNull()->end() // hipchat
  534.                 ->scalarNode('channel')->defaultNull()->end() // slack & slackwebhook & slackbot & telegram
  535.                 ->scalarNode('bot_name')->defaultValue('Monolog')->end() // slack & slackwebhook
  536.                 ->scalarNode('use_attachment')->defaultTrue()->end() // slack & slackwebhook
  537.                 ->scalarNode('use_short_attachment')->defaultFalse()->end() // slack & slackwebhook
  538.                 ->scalarNode('include_extra')->defaultFalse()->end() // slack & slackwebhook
  539.                 ->scalarNode('icon_emoji')->defaultNull()->end() // slack & slackwebhook
  540.                 ->scalarNode('webhook_url')->end() // slackwebhook
  541.                 ->scalarNode('team')->end() // slackbot
  542.                 ->scalarNode('notify')->defaultFalse()->end() // hipchat
  543.                 ->scalarNode('nickname')->defaultValue('Monolog')->end() // hipchat
  544.                 ->scalarNode('token')->end() // pushover & hipchat & loggly & logentries & flowdock & rollbar & slack & slackbot & insightops & telegram
  545.                 ->scalarNode('region')->end() // insightops
  546.                 ->scalarNode('source')->end() // flowdock
  547.                 ->booleanNode('use_ssl')->defaultTrue()->end() // logentries & hipchat & insightops
  548.                 ->variableNode('user'// pushover
  549.                     ->validate()
  550.                         ->ifTrue(function ($v) {
  551.                             return !is_string($v) && !is_array($v);
  552.                         })
  553.                         ->thenInvalid('User must be a string or an array.')
  554.                     ->end()
  555.                 ->end()
  556.                 ->scalarNode('title')->defaultNull()->end() // pushover
  557.                 ->scalarNode('host')->defaultNull()->end() // syslogudp & hipchat
  558.                 ->scalarNode('port')->defaultValue(514)->end() // syslogudp
  559.                 ->arrayNode('config')
  560.                     ->canBeUnset()
  561.                     ->prototype('scalar')->end()
  562.                 ->end() // rollbar
  563.                 ->arrayNode('members'// group, whatfailuregroup, fallbackgroup
  564.                     ->canBeUnset()
  565.                     ->performNoDeepMerging()
  566.                     ->prototype('scalar')->end()
  567.                 ->end()
  568.                 ->scalarNode('connection_string')->end() // socket_handler
  569.                 ->scalarNode('timeout')->end() // socket_handler, logentries, pushover, hipchat & slack
  570.                 ->scalarNode('time')->defaultValue(60)->end() // deduplication
  571.                 ->scalarNode('deduplication_level')->defaultValue(Logger::ERROR)->end() // deduplication
  572.                 ->scalarNode('store')->defaultNull()->end() // deduplication
  573.                 ->scalarNode('connection_timeout')->end() // socket_handler, logentries, pushover, hipchat & slack
  574.                 ->booleanNode('persistent')->end() // socket_handler
  575.                 ->scalarNode('dsn')->end() // raven_handler, sentry_handler
  576.                 ->scalarNode('hub_id')->defaultNull()->end() // sentry_handler
  577.                 ->scalarNode('client_id')->defaultNull()->end() // raven_handler, sentry_handler
  578.                 ->scalarNode('auto_log_stacks')->defaultFalse()->end() // raven_handler
  579.                 ->scalarNode('release')->defaultNull()->end() // raven_handler, sentry_handler
  580.                 ->scalarNode('environment')->defaultNull()->end() // raven_handler, sentry_handler
  581.                 ->scalarNode('message_type')->defaultValue(0)->end() // error_log
  582.                 ->scalarNode('parse_mode')->defaultNull()->end() // telegram
  583.                 ->booleanNode('disable_webpage_preview')->defaultNull()->end() // telegram
  584.                 ->booleanNode('disable_notification')->defaultNull()->end() // telegram
  585.                 ->booleanNode('split_long_messages')->defaultFalse()->end() // telegram
  586.                 ->booleanNode('delay_between_messages')->defaultFalse()->end() // telegram
  587.                 ->integerNode('factor')->defaultValue(1)->min(1)->end() // sampling
  588.                 ->arrayNode('tags'// loggly
  589.                     ->beforeNormalization()
  590.                         ->ifString()
  591.                         ->then(function ($v) { return explode(','$v); })
  592.                     ->end()
  593.                     ->beforeNormalization()
  594.                         ->ifArray()
  595.                         ->then(function ($v) { return array_filter(array_map('trim'$v)); })
  596.                     ->end()
  597.                     ->prototype('scalar')->end()
  598.                 ->end()
  599.                  // console
  600.                 ->variableNode('console_formater_options')
  601.                     ->setDeprecated('symfony/monolog-bundle'3.7'"%path%.%node%" is deprecated, use "%path%.console_formatter_options" instead.')
  602.                     ->validate()
  603.                         ->ifTrue(function ($v) {
  604.                             return !is_array($v);
  605.                         })
  606.                         ->thenInvalid('The console_formater_options must be an array.')
  607.                     ->end()
  608.                 ->end()
  609.                 ->variableNode('console_formatter_options')
  610.                     ->defaultValue([])
  611.                     ->validate()
  612.                         ->ifTrue(static function ($v) { return !is_array($v); })
  613.                         ->thenInvalid('The console_formatter_options must be an array.')
  614.                     ->end()
  615.                 ->end()
  616.                 ->scalarNode('formatter')->end()
  617.                 ->booleanNode('nested')->defaultFalse()->end()
  618.             ->end();
  619.         $this->addGelfSection($handlerNode);
  620.         $this->addMongoSection($handlerNode);
  621.         $this->addElasticsearchSection($handlerNode);
  622.         $this->addRedisSection($handlerNode);
  623.         $this->addPredisSection($handlerNode);
  624.         $this->addMailerSection($handlerNode);
  625.         $this->addVerbosityLevelSection($handlerNode);
  626.         $this->addChannelsSection($handlerNode);
  627.         $handlerNode
  628.             ->beforeNormalization()
  629.                 ->always(static function ($v) {
  630.                     if (empty($v['console_formatter_options']) && !empty($v['console_formater_options'])) {
  631.                         $v['console_formatter_options'] = $v['console_formater_options'];
  632.                     }
  633.                     return $v;
  634.                 })
  635.             ->end()
  636.             ->validate()
  637.                 ->always(static function ($v) { unset($v['console_formater_options']); return $v; })
  638.             ->end()
  639.             ->validate()
  640.                 ->ifTrue(function ($v) { return 'service' === $v['type'] && !empty($v['formatter']); })
  641.                 ->thenInvalid('Service handlers can not have a formatter configured in the bundle, you must reconfigure the service itself instead')
  642.             ->end()
  643.             ->validate()
  644.                 ->ifTrue(function ($v) { return ('fingers_crossed' === $v['type'] || 'buffer' === $v['type'] || 'filter' === $v['type'] || 'sampling' === $v['type']) && empty($v['handler']); })
  645.                 ->thenInvalid('The handler has to be specified to use a FingersCrossedHandler or BufferHandler or FilterHandler or SamplingHandler')
  646.             ->end()
  647.             ->validate()
  648.                 ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_404s']) && !empty($v['activation_strategy']); })
  649.                 ->thenInvalid('You can not use excluded_404s together with a custom activation_strategy in a FingersCrossedHandler')
  650.             ->end()
  651.             ->validate()
  652.                 ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['activation_strategy']); })
  653.                 ->thenInvalid('You can not use excluded_http_codes together with a custom activation_strategy in a FingersCrossedHandler')
  654.             ->end()
  655.             ->validate()
  656.                 ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['excluded_404s']); })
  657.                 ->thenInvalid('You can not use excluded_http_codes together with excluded_404s in a FingersCrossedHandler')
  658.             ->end()
  659.             ->validate()
  660.                 ->ifTrue(function ($v) { return 'fingers_crossed' !== $v['type'] && (!empty($v['excluded_http_codes']) || !empty($v['excluded_404s'])); })
  661.                 ->thenInvalid('You can only use excluded_http_codes/excluded_404s with a FingersCrossedHandler definition')
  662.             ->end()
  663.             ->validate()
  664.                 ->ifTrue(function ($v) { return 'filter' === $v['type'] && "DEBUG" !== $v['min_level'] && !empty($v['accepted_levels']); })
  665.                 ->thenInvalid('You can not use min_level together with accepted_levels in a FilterHandler')
  666.             ->end()
  667.             ->validate()
  668.                 ->ifTrue(function ($v) { return 'filter' === $v['type'] && "EMERGENCY" !== $v['max_level'] && !empty($v['accepted_levels']); })
  669.                 ->thenInvalid('You can not use max_level together with accepted_levels in a FilterHandler')
  670.             ->end()
  671.             ->validate()
  672.                 ->ifTrue(function ($v) { return 'rollbar' === $v['type'] && !empty($v['id']) && !empty($v['token']); })
  673.                 ->thenInvalid('You can not use both an id and a token in a RollbarHandler')
  674.             ->end()
  675.             ->validate()
  676.                 ->ifTrue(function ($v) { return 'rollbar' === $v['type'] && empty($v['id']) && empty($v['token']); })
  677.                 ->thenInvalid('The id or the token has to be specified to use a RollbarHandler')
  678.             ->end()
  679.             ->validate()
  680.                 ->ifTrue(function ($v) { return 'telegram' === $v['type'] && (empty($v['token']) || empty($v['channel'])); })
  681.                 ->thenInvalid('The token and channel have to be specified to use a TelegramBotHandler')
  682.             ->end()
  683.             ->validate()
  684.                 ->ifTrue(function ($v) { return 'service' === $v['type'] && !isset($v['id']); })
  685.                 ->thenInvalid('The id has to be specified to use a service as handler')
  686.             ->end()
  687.             ->validate()
  688.                 ->ifTrue(function ($v) { return 'syslogudp' === $v['type'] && !isset($v['host']); })
  689.                 ->thenInvalid('The host has to be specified to use a syslogudp as handler')
  690.             ->end()
  691.             ->validate()
  692.                 ->ifTrue(function ($v) { return 'socket' === $v['type'] && !isset($v['connection_string']); })
  693.                 ->thenInvalid('The connection_string has to be specified to use a SocketHandler')
  694.             ->end()
  695.             ->validate()
  696.                 ->ifTrue(function ($v) { return 'pushover' === $v['type'] && (empty($v['token']) || empty($v['user'])); })
  697.                 ->thenInvalid('The token and user have to be specified to use a PushoverHandler')
  698.             ->end()
  699.             ->validate()
  700.                 ->ifTrue(function ($v) { return 'raven' === $v['type'] && !array_key_exists('dsn'$v) && null === $v['client_id']; })
  701.                 ->thenInvalid('The DSN has to be specified to use a RavenHandler')
  702.             ->end()
  703.             ->validate()
  704.                 ->ifTrue(function ($v) { return 'sentry' === $v['type'] && !array_key_exists('dsn'$v) && null === $v['hub_id'] && null === $v['client_id']; })
  705.                 ->thenInvalid('The DSN has to be specified to use Sentry\'s handler')
  706.             ->end()
  707.             ->validate()
  708.                 ->ifTrue(function ($v) { return 'sentry' === $v['type'] && null !== $v['hub_id'] && null !== $v['client_id']; })
  709.                 ->thenInvalid('You can not use both a hub_id and a client_id in a Sentry handler')
  710.             ->end()
  711.             ->validate()
  712.                 ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && (empty($v['token']) || empty($v['room'])); })
  713.                 ->thenInvalid('The token and room have to be specified to use a HipChatHandler')
  714.             ->end()
  715.             ->validate()
  716.                 ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && !in_array($v['message_format'], ['text''html']); })
  717.                 ->thenInvalid('The message_format has to be "text" or "html" in a HipChatHandler')
  718.             ->end()
  719.             ->validate()
  720.                 ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && null !== $v['api_version'] && !in_array($v['api_version'], ['v1''v2'], true); })
  721.                 ->thenInvalid('The api_version has to be "v1" or "v2" in a HipChatHandler')
  722.             ->end()
  723.             ->validate()
  724.                 ->ifTrue(function ($v) { return 'slack' === $v['type'] && (empty($v['token']) || empty($v['channel'])); })
  725.                 ->thenInvalid('The token and channel have to be specified to use a SlackHandler')
  726.             ->end()
  727.             ->validate()
  728.                 ->ifTrue(function ($v) { return 'slackwebhook' === $v['type'] && (empty($v['webhook_url'])); })
  729.                 ->thenInvalid('The webhook_url have to be specified to use a SlackWebhookHandler')
  730.             ->end()
  731.             ->validate()
  732.                 ->ifTrue(function ($v) { return 'slackbot' === $v['type'] && (empty($v['team']) || empty($v['token']) || empty($v['channel'])); })
  733.                 ->thenInvalid('The team, token and channel have to be specified to use a SlackbotHandler')
  734.             ->end()
  735.             ->validate()
  736.                 ->ifTrue(function ($v) { return 'cube' === $v['type'] && empty($v['url']); })
  737.                 ->thenInvalid('The url has to be specified to use a CubeHandler')
  738.             ->end()
  739.             ->validate()
  740.                 ->ifTrue(function ($v) { return 'amqp' === $v['type'] && empty($v['exchange']); })
  741.                 ->thenInvalid('The exchange has to be specified to use a AmqpHandler')
  742.             ->end()
  743.             ->validate()
  744.                 ->ifTrue(function ($v) { return 'loggly' === $v['type'] && empty($v['token']); })
  745.                 ->thenInvalid('The token has to be specified to use a LogglyHandler')
  746.             ->end()
  747.             ->validate()
  748.                 ->ifTrue(function ($v) { return 'loggly' === $v['type'] && !empty($v['tags']); })
  749.                 ->then(function ($v) {
  750.                     $invalidTags preg_grep('/^[a-z0-9][a-z0-9\.\-_]*$/i'$v['tags'], PREG_GREP_INVERT);
  751.                     if (!empty($invalidTags)) {
  752.                         throw new InvalidConfigurationException(sprintf('The following Loggly tags are invalid: %s.'implode(', '$invalidTags)));
  753.                     }
  754.                     return $v;
  755.                 })
  756.             ->end()
  757.             ->validate()
  758.                 ->ifTrue(function ($v) { return 'logentries' === $v['type'] && empty($v['token']); })
  759.                 ->thenInvalid('The token has to be specified to use a LogEntriesHandler')
  760.             ->end()
  761.             ->validate()
  762.                 ->ifTrue(function ($v) { return 'insightops' === $v['type'] && empty($v['token']); })
  763.                 ->thenInvalid('The token has to be specified to use a InsightOpsHandler')
  764.             ->end()
  765.             ->validate()
  766.                 ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['token']); })
  767.                 ->thenInvalid('The token has to be specified to use a FlowdockHandler')
  768.             ->end()
  769.             ->validate()
  770.                 ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['from_email']); })
  771.                 ->thenInvalid('The from_email has to be specified to use a FlowdockHandler')
  772.             ->end()
  773.             ->validate()
  774.                 ->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['source']); })
  775.                 ->thenInvalid('The source has to be specified to use a FlowdockHandler')
  776.             ->end()
  777.             ->validate()
  778.                 ->ifTrue(function ($v) { return 'server_log' === $v['type'] && empty($v['host']); })
  779.                 ->thenInvalid('The host has to be specified to use a ServerLogHandler')
  780.             ->end()
  781.         ;
  782.         return $treeBuilder;
  783.     }
  784.     private function addGelfSection(ArrayNodeDefinition $handerNode)
  785.     {
  786.         $handerNode
  787.             ->children()
  788.                 ->arrayNode('publisher')
  789.                     ->canBeUnset()
  790.                     ->beforeNormalization()
  791.                         ->ifString()
  792.                         ->then(function ($v) { return ['id' => $v]; })
  793.                     ->end()
  794.                     ->children()
  795.                         ->scalarNode('id')->end()
  796.                         ->scalarNode('hostname')->end()
  797.                         ->scalarNode('port')->defaultValue(12201)->end()
  798.                         ->scalarNode('chunk_size')->defaultValue(1420)->end()
  799.                     ->end()
  800.                     ->validate()
  801.                         ->ifTrue(function ($v) {
  802.                             return !isset($v['id']) && !isset($v['hostname']);
  803.                         })
  804.                         ->thenInvalid('What must be set is either the hostname or the id.')
  805.                     ->end()
  806.                 ->end()
  807.             ->end()
  808.             ->validate()
  809.                 ->ifTrue(function ($v) { return 'gelf' === $v['type'] && !isset($v['publisher']); })
  810.                 ->thenInvalid('The publisher has to be specified to use a GelfHandler')
  811.             ->end()
  812.         ;
  813.     }
  814.     private function addMongoSection(ArrayNodeDefinition $handerNode)
  815.     {
  816.         $handerNode
  817.             ->children()
  818.                 ->arrayNode('mongo')
  819.                     ->canBeUnset()
  820.                     ->beforeNormalization()
  821.                     ->ifString()
  822.                     ->then(function ($v) { return ['id' => $v]; })
  823.                     ->end()
  824.                     ->children()
  825.                         ->scalarNode('id')->end()
  826.                         ->scalarNode('host')->end()
  827.                         ->scalarNode('port')->defaultValue(27017)->end()
  828.                         ->scalarNode('user')->end()
  829.                         ->scalarNode('pass')->end()
  830.                         ->scalarNode('database')->defaultValue('monolog')->end()
  831.                         ->scalarNode('collection')->defaultValue('logs')->end()
  832.                     ->end()
  833.                     ->validate()
  834.                     ->ifTrue(function ($v) {
  835.                         return !isset($v['id']) && !isset($v['host']);
  836.                     })
  837.                     ->thenInvalid('What must be set is either the host or the id.')
  838.                     ->end()
  839.                     ->validate()
  840.                     ->ifTrue(function ($v) {
  841.                         return isset($v['user']) && !isset($v['pass']);
  842.                     })
  843.                     ->thenInvalid('If you set user, you must provide a password.')
  844.                     ->end()
  845.                 ->end()
  846.             ->end()
  847.             ->validate()
  848.                 ->ifTrue(function ($v) { return 'mongo' === $v['type'] && !isset($v['mongo']); })
  849.                 ->thenInvalid('The mongo configuration has to be specified to use a MongoHandler')
  850.             ->end()
  851.         ;
  852.     }
  853.     private function addElasticsearchSection(ArrayNodeDefinition $handerNode)
  854.     {
  855.         $handerNode
  856.             ->children()
  857.                 ->arrayNode('elasticsearch')
  858.                     ->canBeUnset()
  859.                     ->beforeNormalization()
  860.                     ->ifString()
  861.                     ->then(function ($v) { return ['id' => $v]; })
  862.                     ->end()
  863.                     ->children()
  864.                         ->scalarNode('id')->end()
  865.                         ->scalarNode('host')->end()
  866.                         ->scalarNode('port')->defaultValue(9200)->end()
  867.                         ->scalarNode('transport')->defaultValue('Http')->end()
  868.                         ->scalarNode('user')->defaultNull()->end()
  869.                         ->scalarNode('password')->defaultNull()->end()
  870.                     ->end()
  871.                     ->validate()
  872.                     ->ifTrue(function ($v) {
  873.                         return !isset($v['id']) && !isset($v['host']);
  874.                     })
  875.                     ->thenInvalid('What must be set is either the host or the id.')
  876.                     ->end()
  877.                 ->end()
  878.                 ->scalarNode('index')->defaultValue('monolog')->end() // elasticsearch & elastic_search & elastica
  879.                 ->scalarNode('document_type')->defaultValue('logs')->end() // elasticsearch & elastic_search & elastica
  880.                 ->scalarNode('ignore_error')->defaultValue(false)->end() // elasticsearch & elastic_search & elastica
  881.             ->end()
  882.         ;
  883.     }
  884.     private function addRedisSection(ArrayNodeDefinition $handerNode)
  885.     {
  886.         $handerNode
  887.             ->children()
  888.                 ->arrayNode('redis')
  889.                     ->canBeUnset()
  890.                     ->beforeNormalization()
  891.                     ->ifString()
  892.                     ->then(function ($v) { return ['id' => $v]; })
  893.                     ->end()
  894.                     ->children()
  895.                         ->scalarNode('id')->end()
  896.                         ->scalarNode('host')->end()
  897.                         ->scalarNode('password')->defaultNull()->end()
  898.                         ->scalarNode('port')->defaultValue(6379)->end()
  899.                         ->scalarNode('database')->defaultValue(0)->end()
  900.                         ->scalarNode('key_name')->defaultValue('monolog_redis')->end()
  901.                     ->end()
  902.                     ->validate()
  903.                     ->ifTrue(function ($v) {
  904.                         return !isset($v['id']) && !isset($v['host']);
  905.                     })
  906.                     ->thenInvalid('What must be set is either the host or the service id of the Redis client.')
  907.                     ->end()
  908.                 ->end()
  909.             ->end()
  910.             ->validate()
  911.                 ->ifTrue(function ($v) { return 'redis' === $v['type'] && empty($v['redis']); })
  912.                 ->thenInvalid('The host has to be specified to use a RedisLogHandler')
  913.             ->end()
  914.         ;
  915.     }
  916.     private function addPredisSection(ArrayNodeDefinition $handerNode)
  917.     {
  918.         $handerNode
  919.             ->children()
  920.                 ->arrayNode('predis')
  921.                     ->canBeUnset()
  922.                     ->beforeNormalization()
  923.                     ->ifString()
  924.                     ->then(function ($v) { return ['id' => $v]; })
  925.                     ->end()
  926.                     ->children()
  927.                         ->scalarNode('id')->end()
  928.                         ->scalarNode('host')->end()
  929.                     ->end()
  930.                     ->validate()
  931.                     ->ifTrue(function ($v) {
  932.                         return !isset($v['id']) && !isset($v['host']);
  933.                     })
  934.                     ->thenInvalid('What must be set is either the host or the service id of the Predis client.')
  935.                     ->end()
  936.                 ->end()
  937.             ->end()
  938.             ->validate()
  939.                 ->ifTrue(function ($v) { return 'predis' === $v['type'] && empty($v['redis']); })
  940.                 ->thenInvalid('The host has to be specified to use a RedisLogHandler')
  941.             ->end()
  942.         ;
  943.     }
  944.     private function addMailerSection(ArrayNodeDefinition $handerNode)
  945.     {
  946.         $handerNode
  947.             ->children()
  948.                 ->scalarNode('from_email')->end() // swift_mailer, native_mailer, symfony_mailer and flowdock
  949.                 ->arrayNode('to_email'// swift_mailer, native_mailer and symfony_mailer
  950.                     ->prototype('scalar')->end()
  951.                     ->beforeNormalization()
  952.                         ->ifString()
  953.                         ->then(function ($v) { return [$v]; })
  954.                     ->end()
  955.                 ->end()
  956.                 ->scalarNode('subject')->end() // swift_mailer, native_mailer and symfony_mailer
  957.                 ->scalarNode('content_type')->defaultNull()->end() // swift_mailer and symfony_mailer
  958.                 ->arrayNode('headers'// native_mailer
  959.                     ->canBeUnset()
  960.                     ->scalarPrototype()->end()
  961.                 ->end()
  962.                 ->scalarNode('mailer')->defaultNull()->end() // swift_mailer and symfony_mailer
  963.                 ->arrayNode('email_prototype'// swift_mailer and symfony_mailer
  964.                     ->canBeUnset()
  965.                     ->beforeNormalization()
  966.                         ->ifString()
  967.                         ->then(function ($v) { return ['id' => $v]; })
  968.                     ->end()
  969.                     ->children()
  970.                         ->scalarNode('id')->isRequired()->end()
  971.                         ->scalarNode('method')->defaultNull()->end()
  972.                     ->end()
  973.                 ->end()
  974.                 ->booleanNode('lazy')->defaultValue(true)->end() // swift_mailer
  975.             ->end()
  976.             ->validate()
  977.                 ->ifTrue(function ($v) { return 'swift_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })
  978.                 ->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use a SwiftMailerHandler')
  979.             ->end()
  980.             ->validate()
  981.                 ->ifTrue(function ($v) { return 'native_mailer' === $v['type'] && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })
  982.                 ->thenInvalid('The sender, recipient and subject have to be specified to use a NativeMailerHandler')
  983.             ->end()
  984.             ->validate()
  985.                 ->ifTrue(function ($v) { return 'symfony_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })
  986.                 ->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use the Symfony MailerHandler')
  987.             ->end()
  988.         ;
  989.     }
  990.     private function addVerbosityLevelSection(ArrayNodeDefinition $handerNode)
  991.     {
  992.         $handerNode
  993.             ->children()
  994.                 ->arrayNode('verbosity_levels'// console
  995.                     ->beforeNormalization()
  996.                         ->ifArray()
  997.                         ->then(function ($v) {
  998.                             $map = [];
  999.                             $verbosities = ['VERBOSITY_QUIET''VERBOSITY_NORMAL''VERBOSITY_VERBOSE''VERBOSITY_VERY_VERBOSE''VERBOSITY_DEBUG'];
  1000.                             // allow numeric indexed array with ascendning verbosity and lowercase names of the constants
  1001.                             foreach ($v as $verbosity => $level) {
  1002.                                 if (is_int($verbosity) && isset($verbosities[$verbosity])) {
  1003.                                     $map[$verbosities[$verbosity]] = strtoupper($level);
  1004.                                 } else {
  1005.                                     $map[strtoupper($verbosity)] = strtoupper($level);
  1006.                                 }
  1007.                             }
  1008.                             return $map;
  1009.                         })
  1010.                     ->end()
  1011.                     ->children()
  1012.                         ->scalarNode('VERBOSITY_QUIET')->defaultValue('ERROR')->end()
  1013.                         ->scalarNode('VERBOSITY_NORMAL')->defaultValue('WARNING')->end()
  1014.                         ->scalarNode('VERBOSITY_VERBOSE')->defaultValue('NOTICE')->end()
  1015.                         ->scalarNode('VERBOSITY_VERY_VERBOSE')->defaultValue('INFO')->end()
  1016.                         ->scalarNode('VERBOSITY_DEBUG')->defaultValue('DEBUG')->end()
  1017.                     ->end()
  1018.                     ->validate()
  1019.                         ->always(function ($v) {
  1020.                             $map = [];
  1021.                             foreach ($v as $verbosity => $level) {
  1022.                                 $verbosityConstant 'Symfony\Component\Console\Output\OutputInterface::'.$verbosity;
  1023.                                 if (!defined($verbosityConstant)) {
  1024.                                     throw new InvalidConfigurationException(sprintf(
  1025.                                         'The configured verbosity "%s" is invalid as it is not defined in Symfony\Component\Console\Output\OutputInterface.',
  1026.                                          $verbosity
  1027.                                     ));
  1028.                                 }
  1029.                                 try {
  1030.                                     if (Logger::API === 3) {
  1031.                                         $level Logger::toMonologLevel($level)->value;
  1032.                                     } else {
  1033.                                         $level Logger::toMonologLevel(is_numeric($level) ? (int) $level $level);
  1034.                                     }
  1035.                                 } catch (\Psr\Log\InvalidArgumentException $e) {
  1036.                                     throw new InvalidConfigurationException(sprintf(
  1037.                                         'The configured minimum log level "%s" for verbosity "%s" is invalid as it is not defined in Monolog\Logger.',
  1038.                                          $level$verbosity
  1039.                                     ));
  1040.                                 }
  1041.                                 $map[constant($verbosityConstant)] = $level;
  1042.                             }
  1043.                             return $map;
  1044.                         })
  1045.                     ->end()
  1046.                 ->end()
  1047.             ->end()
  1048.         ;
  1049.     }
  1050.     private function addChannelsSection(ArrayNodeDefinition $handerNode)
  1051.     {
  1052.         $handerNode
  1053.             ->children()
  1054.                 ->arrayNode('channels')
  1055.                     ->fixXmlConfig('channel''elements')
  1056.                     ->canBeUnset()
  1057.                     ->beforeNormalization()
  1058.                         ->ifString()
  1059.                         ->then(function ($v) { return ['elements' => [$v]]; })
  1060.                     ->end()
  1061.                     ->beforeNormalization()
  1062.                         ->ifTrue(function ($v) { return is_array($v) && is_numeric(key($v)); })
  1063.                         ->then(function ($v) { return ['elements' => $v]; })
  1064.                     ->end()
  1065.                     ->validate()
  1066.                         ->ifTrue(function ($v) { return empty($v); })
  1067.                         ->thenUnset()
  1068.                     ->end()
  1069.                     ->validate()
  1070.                         ->always(function ($v) {
  1071.                             $isExclusive null;
  1072.                             if (isset($v['type'])) {
  1073.                                 $isExclusive 'exclusive' === $v['type'];
  1074.                             }
  1075.                             $elements = [];
  1076.                             foreach ($v['elements'] as $element) {
  1077.                                 if (=== strpos($element'!')) {
  1078.                                     if (false === $isExclusive) {
  1079.                                         throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list.');
  1080.                                     }
  1081.                                     $elements[] = substr($element1);
  1082.                                     $isExclusive true;
  1083.                                 } else {
  1084.                                     if (true === $isExclusive) {
  1085.                                         throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list');
  1086.                                     }
  1087.                                     $elements[] = $element;
  1088.                                     $isExclusive false;
  1089.                                 }
  1090.                             }
  1091.                             if (!count($elements)) {
  1092.                                 return null;
  1093.                             }
  1094.                             // de-duplicating $elements here in case the handlers are redefined, see https://github.com/symfony/monolog-bundle/issues/433
  1095.                             return ['type' => $isExclusive 'exclusive' 'inclusive''elements' => array_unique($elements)];
  1096.                         })
  1097.                     ->end()
  1098.                     ->children()
  1099.                         ->scalarNode('type')
  1100.                             ->validate()
  1101.                                 ->ifNotInArray(['inclusive''exclusive'])
  1102.                                 ->thenInvalid('The type of channels has to be inclusive or exclusive')
  1103.                             ->end()
  1104.                         ->end()
  1105.                         ->arrayNode('elements')
  1106.                             ->prototype('scalar')->end()
  1107.                         ->end()
  1108.                     ->end()
  1109.                 ->end()
  1110.             ->end()
  1111.         ;
  1112.     }
  1113. }