<p dir="auto">Hello.</p>
<h2 dir="auto">Problem</h2>
<p dir="auto">I had to implement something that seems simple:<br>
Use a (monolog) stream handler only for our production servers.<br>
This logger should not be present on developer machine (to avoid warning &amp; co)</p>
<p dir="auto">So I can not add a section in the <code class="notranslate">config.yml</code> because it depends on something<br>
else.</p>
<p dir="auto">So I add a new parameter in our <code class="notranslate">parameters.yml</code>.(I use to do that for many<br>
things that are enabled only on production server.)</p>
<p dir="auto">But here, it's very hard to achieve that.</p>
<h2 dir="auto">Some solutions</h2>
<p dir="auto"><strong>Note: Solution 2 + 4 + 5 are already rejected</strong></p>
<h3 dir="auto">1. Add an <code class="notranslate">enable</code> option on each monolog handler:</h3>
<div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# config.yml
monolog:
    handlers:
        only_on_production_server:
            type:  stream
            path:  &quot;/var/log/foobar/%kernel.environment%.log&quot;
            level: error
            enable: %we_are_on_production_server%"><pre class="notranslate"><span class="pl-c"><span class="pl-c">#</span> config.yml</span>
<span class="pl-ent">monolog</span>:
    <span class="pl-ent">handlers</span>:
        <span class="pl-ent">only_on_production_server</span>:
            <span class="pl-ent">type</span>:  <span class="pl-s">stream</span>
            <span class="pl-ent">path</span>:  <span class="pl-s"><span class="pl-pds">"</span>/var/log/foobar/%kernel.environment%.log<span class="pl-pds">"</span></span>
            <span class="pl-ent">level</span>: <span class="pl-s">error</span>
            <span class="pl-ent">enable</span>: <span class="pl-s">%we_are_on_production_server%</span></pre></div>
<h3 dir="auto">2. Add a new <code class="notranslate">monolog.handler</code> tag</h3>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class BundleExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        if ($container-&gt;hasParameter('we_are_on_production_server')) {
            $def = new Definition('MyHandler')
            // Listen all channels
            $def-&gt;addTag('we_are_on_production_server');
            // Listen only XX channel
            $def-&gt;addTag('we_are_on_production_server', [channels =&gt; 'XX']);
            $container-&gt;setDefinition('project.monolog.handler.prod', $def);
        }
    }
}"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">BundleExtension</span> <span class="pl-k">extends</span> <span class="pl-v">Extension</span>
{
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">load</span>(<span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>configs</span>, <span class="pl-smi"><span class="pl-smi">ContainerBuilder</span></span> <span class="pl-s1"><span class="pl-c1">$</span>container</span>)
    {
        <span class="pl-k">if</span> (<span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">hasParameter</span>(<span class="pl-s">'we_are_on_production_server'</span>)) {
            <span class="pl-s1"><span class="pl-c1">$</span>def</span> = <span class="pl-k">new</span> <span class="pl-v">Definition</span>(<span class="pl-s">'MyHandler'</span>)
            <span class="pl-c">// Listen all channels</span>
            <span class="pl-s1"><span class="pl-c1">$</span>def</span>-&gt;<span class="pl-en">addTag</span>(<span class="pl-s">'we_are_on_production_server'</span>);
            <span class="pl-c">// Listen only XX channel</span>
            <span class="pl-s1"><span class="pl-c1">$</span>def</span>-&gt;<span class="pl-en">addTag</span>(<span class="pl-s">'we_are_on_production_server'</span>, [channels =&gt; <span class="pl-s">'XX'</span>]);
            <span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">setDefinition</span>(<span class="pl-s">'project.monolog.handler.prod'</span>, <span class="pl-s1"><span class="pl-c1">$</span>def</span>);
        }
    }
}</pre></div>
<h3 dir="auto">3. Give bundle configuration to <code class="notranslate">PrependExtensionInterface::preprend</code></h3>
<p dir="auto">Note: I already need that, but for another issue which is not related to<br>
monolog. I really miss this feature in symfony...</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class BundleExtension extends Extension
{
    // BC layer..
    public function preprendWithConfig(array $configs, ContainerBuilder $container)
    {
        // here, configs is not validated, and not merged.
        // Let's pretend it is ;)
        if ($config['we_are_on_production_server']) {
            $container-&gt;prependExtensionConfig('monolog', array(
                'handlers' =&gt; array(
                    'only_on_production_server' =&gt; array(
                        'type' =&gt; 'stream',
                        'path' =&gt; '/var/log/foobar/%kernel.environment%.log',
                        'level' =&gt; 'error',
                    ),
                ),
            ));
        }
    }"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">BundleExtension</span> <span class="pl-k">extends</span> <span class="pl-v">Extension</span>
{
    <span class="pl-c">// BC layer..</span>
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">preprendWithConfig</span>(<span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>configs</span>, <span class="pl-smi"><span class="pl-smi">ContainerBuilder</span></span> <span class="pl-s1"><span class="pl-c1">$</span>container</span>)
    {
        <span class="pl-c">// here, configs is not validated, and not merged.</span>
        <span class="pl-c">// Let's pretend it is ;)</span>
        <span class="pl-k">if</span> (<span class="pl-s1"><span class="pl-c1">$</span>config</span>[<span class="pl-s">'we_are_on_production_server'</span>]) {
            <span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">prependExtensionConfig</span>(<span class="pl-s">'monolog'</span>, <span class="pl-en">array</span>(
                <span class="pl-s">'handlers'</span> =&gt; <span class="pl-en">array</span>(
                    <span class="pl-s">'only_on_production_server'</span> =&gt; <span class="pl-en">array</span>(
                        <span class="pl-s">'type'</span> =&gt; <span class="pl-s">'stream'</span>,
                        <span class="pl-s">'path'</span> =&gt; <span class="pl-s">'/var/log/foobar/%kernel.environment%.log'</span>,
                        <span class="pl-s">'level'</span> =&gt; <span class="pl-s">'error'</span>,
                    ),
                ),
            ));
        }
    }</pre></div>
<h3 dir="auto">Temp solution:</h3>
<h4 dir="auto">4. load a .php config script</h4>
<div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# config.yml
imports:
    - { resource: config.yml }
    - { resource: config_prod_logger.php }"><pre class="notranslate"><span class="pl-c"><span class="pl-c">#</span> config.yml</span>
<span class="pl-ent">imports</span>:
    - <span class="pl-s">{ resource: config.yml }</span>
    - <span class="pl-s">{ resource: config_prod_logger.php }</span></pre></div>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="if (!$container-&gt;hasParameter('we_are_on_production_server') || !$container-&gt;getParameter('we_are_on_production_server')) {
    return;
}

$container-&gt;prependExtensionConfig('monolog', array(
    'handlers' =&gt; array(
        'only_on_production_server' =&gt; array(
            'type' =&gt; 'stream',
            'path' =&gt; '/var/log/foobar/%kernel.environment%.log',
            'level' =&gt; 'error',
        ),
    ),
));"><pre class="notranslate"><span class="pl-k">if</span> (!<span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">hasParameter</span>(<span class="pl-s">'we_are_on_production_server'</span>) || !<span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">getParameter</span>(<span class="pl-s">'we_are_on_production_server'</span>)) {
    <span class="pl-k">return</span>;
}

<span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">prependExtensionConfig</span>(<span class="pl-s">'monolog'</span>, <span class="pl-en">array</span>(
    <span class="pl-s">'handlers'</span> =&gt; <span class="pl-en">array</span>(
        <span class="pl-s">'only_on_production_server'</span> =&gt; <span class="pl-en">array</span>(
            <span class="pl-s">'type'</span> =&gt; <span class="pl-s">'stream'</span>,
            <span class="pl-s">'path'</span> =&gt; <span class="pl-s">'/var/log/foobar/%kernel.environment%.log'</span>,
            <span class="pl-s">'level'</span> =&gt; <span class="pl-s">'error'</span>,
        ),
    ),
));</pre></div>
<h4 dir="auto">5. Compiler pass</h4>
<p dir="auto">Right now, I wax able to do that with a new compiler pass in my application:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;?php

namespace SensioLabs\Bundle\XXXXBundle\DependencyInjection\Compiler;

use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

class LoggerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container-&gt;hasDefinition('monolog.logger')) {
            return;
        }

        $logHekaUrl = $container-&gt;getParameter('log_heka_url');

        if (!$logHekaUrl) {
            return;
        }

        // I really don't like this part
        $channelPass = null;
        foreach ($container-&gt;getCompilerPassConfig()-&gt;getPasses() as $pass) {
            if ($pass instanceof LoggerChannelPass) {
                $channelPass = $pass;
            }
        }

        if (!$channelPass) {
            return;
        }

        $handlerId = $this-&gt;createSocketHandler($container, $logHekaUrl);

        foreach ($channelPass-&gt;getChannels() as $channel) {
            $container
                -&gt;getDefinition($channel === 'app' ? 'monolog.logger' : 'monolog.logger.'.$channel)
                -&gt;addMethodCall('pushHandler', array(new Reference($handlerId)));
        }
    }

    private function createSocketHandler(ContainerBuilder $container, $logHekaUrl)
    {
        $bufferDefinition = new Definition('%monolog.handler.buffer.class%');
        $bufferDefinition-&gt;setPublic(false);
        $bufferDefinition-&gt;setArguments(array(
            new Reference('monolog.handler.heka'),
        ));
        $container-&gt;setDefinition('monolog.handler.buffer_heka', $bufferDefinition);

        $hekaDefinition = new Definition('SensioLabs\Toolkit\Monolog\Handler\SocketHandler');
        $hekaDefinition-&gt;setPublic(false);
        $hekaDefinition-&gt;setArguments(array(
            $logHekaUrl,
        ));
        $hekaDefinition-&gt;addMethodCall('setTimeout', array(1));
        $hekaDefinition-&gt;addMethodCall('setConnectionTimeout', array(1));
        $container-&gt;setDefinition('monolog.handler.heka', $hekaDefinition);

        return 'monolog.handler.buffer_heka';
    }
}"><pre class="notranslate"><span class="pl-ent">&lt;?php</span>

<span class="pl-k">namespace</span> <span class="pl-v">SensioLabs</span>\<span class="pl-v">Bundle</span>\<span class="pl-v">XXXXBundle</span>\<span class="pl-v">DependencyInjection</span>\<span class="pl-v">Compiler</span>;

<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Bundle</span>\<span class="pl-v">MonologBundle</span>\<span class="pl-v">DependencyInjection</span>\<span class="pl-v">Compiler</span>\<span class="pl-v">LoggerChannelPass</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">DependencyInjection</span>\<span class="pl-v">Compiler</span>\<span class="pl-v">CompilerPassInterface</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">DependencyInjection</span>\<span class="pl-v">ContainerBuilder</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">DependencyInjection</span>\<span class="pl-v">Definition</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">DependencyInjection</span>\<span class="pl-v">Reference</span>;

<span class="pl-k">class</span> <span class="pl-v">LoggerPass</span> <span class="pl-k">implements</span> <span class="pl-v">CompilerPassInterface</span>
{
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">process</span>(<span class="pl-smi"><span class="pl-smi">ContainerBuilder</span></span> <span class="pl-s1"><span class="pl-c1">$</span>container</span>)
    {
        <span class="pl-k">if</span> (!<span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">hasDefinition</span>(<span class="pl-s">'monolog.logger'</span>)) {
            <span class="pl-k">return</span>;
        }

        <span class="pl-s1"><span class="pl-c1">$</span>logHekaUrl</span> = <span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">getParameter</span>(<span class="pl-s">'log_heka_url'</span>);

        <span class="pl-k">if</span> (!<span class="pl-s1"><span class="pl-c1">$</span>logHekaUrl</span>) {
            <span class="pl-k">return</span>;
        }

        <span class="pl-c">// I really don't like this part</span>
        <span class="pl-s1"><span class="pl-c1">$</span>channelPass</span> = <span class="pl-c1">null</span>;
        <span class="pl-k">foreach</span> (<span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">getCompilerPassConfig</span>()-&gt;<span class="pl-en">getPasses</span>() <span class="pl-k">as</span> <span class="pl-s1"><span class="pl-c1">$</span>pass</span>) {
            <span class="pl-k">if</span> (<span class="pl-s1"><span class="pl-c1">$</span>pass</span> instanceof <span class="pl-v">LoggerChannelPass</span>) {
                <span class="pl-s1"><span class="pl-c1">$</span>channelPass</span> = <span class="pl-s1"><span class="pl-c1">$</span>pass</span>;
            }
        }

        <span class="pl-k">if</span> (!<span class="pl-s1"><span class="pl-c1">$</span>channelPass</span>) {
            <span class="pl-k">return</span>;
        }

        <span class="pl-s1"><span class="pl-c1">$</span>handlerId</span> = <span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-en">createSocketHandler</span>(<span class="pl-s1"><span class="pl-c1">$</span>container</span>, <span class="pl-s1"><span class="pl-c1">$</span>logHekaUrl</span>);

        <span class="pl-k">foreach</span> (<span class="pl-s1"><span class="pl-c1">$</span>channelPass</span>-&gt;<span class="pl-en">getChannels</span>() <span class="pl-k">as</span> <span class="pl-s1"><span class="pl-c1">$</span>channel</span>) {
            <span class="pl-s1"><span class="pl-c1">$</span>container</span>
                -&gt;<span class="pl-en">getDefinition</span>(<span class="pl-s1"><span class="pl-c1">$</span>channel</span> === <span class="pl-s">'app'</span> ? <span class="pl-s">'monolog.logger'</span> : <span class="pl-s">'monolog.logger.'</span>.<span class="pl-s1"><span class="pl-c1">$</span>channel</span>)
                -&gt;<span class="pl-en">addMethodCall</span>(<span class="pl-s">'pushHandler'</span>, <span class="pl-en">array</span>(<span class="pl-k">new</span> <span class="pl-v">Reference</span>(<span class="pl-s1"><span class="pl-c1">$</span>handlerId</span>)));
        }
    }

    <span class="pl-k">private</span> <span class="pl-k">function</span> <span class="pl-en">createSocketHandler</span>(<span class="pl-smi"><span class="pl-smi">ContainerBuilder</span></span> <span class="pl-s1"><span class="pl-c1">$</span>container</span>, <span class="pl-s1"><span class="pl-c1">$</span>logHekaUrl</span>)
    {
        <span class="pl-s1"><span class="pl-c1">$</span>bufferDefinition</span> = <span class="pl-k">new</span> <span class="pl-v">Definition</span>(<span class="pl-s">'%monolog.handler.buffer.class%'</span>);
        <span class="pl-s1"><span class="pl-c1">$</span>bufferDefinition</span>-&gt;<span class="pl-en">setPublic</span>(<span class="pl-c1">false</span>);
        <span class="pl-s1"><span class="pl-c1">$</span>bufferDefinition</span>-&gt;<span class="pl-en">setArguments</span>(<span class="pl-en">array</span>(
            <span class="pl-k">new</span> <span class="pl-v">Reference</span>(<span class="pl-s">'monolog.handler.heka'</span>),
        ));
        <span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">setDefinition</span>(<span class="pl-s">'monolog.handler.buffer_heka'</span>, <span class="pl-s1"><span class="pl-c1">$</span>bufferDefinition</span>);

        <span class="pl-s1"><span class="pl-c1">$</span>hekaDefinition</span> = <span class="pl-k">new</span> <span class="pl-v">Definition</span>(<span class="pl-s">'SensioLabs\Toolkit\Monolog\Handler\SocketHandler'</span>);
        <span class="pl-s1"><span class="pl-c1">$</span>hekaDefinition</span>-&gt;<span class="pl-en">setPublic</span>(<span class="pl-c1">false</span>);
        <span class="pl-s1"><span class="pl-c1">$</span>hekaDefinition</span>-&gt;<span class="pl-en">setArguments</span>(<span class="pl-en">array</span>(
            <span class="pl-s1"><span class="pl-c1">$</span>logHekaUrl</span>,
        ));
        <span class="pl-s1"><span class="pl-c1">$</span>hekaDefinition</span>-&gt;<span class="pl-en">addMethodCall</span>(<span class="pl-s">'setTimeout'</span>, <span class="pl-en">array</span>(<span class="pl-c1">1</span>));
        <span class="pl-s1"><span class="pl-c1">$</span>hekaDefinition</span>-&gt;<span class="pl-en">addMethodCall</span>(<span class="pl-s">'setConnectionTimeout'</span>, <span class="pl-en">array</span>(<span class="pl-c1">1</span>));
        <span class="pl-s1"><span class="pl-c1">$</span>container</span>-&gt;<span class="pl-en">setDefinition</span>(<span class="pl-s">'monolog.handler.heka'</span>, <span class="pl-s1"><span class="pl-c1">$</span>hekaDefinition</span>);

        <span class="pl-k">return</span> <span class="pl-s">'monolog.handler.buffer_heka'</span>;
    }
}</pre></div>
<p dir="auto">So here, I have to duplicate some code that is already in symfony...</p>
<h3 dir="auto">so ?</h3>
<p dir="auto">Did I miss something? It's weird to have to do that.</p>
<p dir="auto"><strong>Solution 1</strong>: Quick fix that is simple to implement. But we have to put all<br>
config in the <code class="notranslate">config_prod.yml</code>, even if it's not used.</p>
<p dir="auto"><strong>Solution 2</strong>: It seems more powerful than solution 1. But it's a bit harder<br>
to implement (but not so hard).</p>
<p dir="auto"><strong>Solution 3</strong>: It's my favorite, because it allow much more things. The last<br>
time We had to configure "framework/assets/bash_url" for production server, This<br>
feature really miss me. We had to do some voodoo to configure properly symfony.</p>
<p dir="auto"><strong>Solution 4</strong>: It's does not pay well with bundle. I mean, it works for a<br>
project, but I can not ship this code in shared (vendored) bundle.</p>
<p dir="auto"><strong>Solution 5</strong>: It works, but some logic is duplicated from MonologBundle. More<br>
over it's far from perfect, because I miss all logic inside the MonologExtenxion<br>
(channel, processor, nestedHandlers, ...)</p>
<p dir="auto">WDYT?</p>
<p dir="auto">ping <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/Seldaek/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/Seldaek">@Seldaek</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/tucksaun/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/tucksaun">@tucksaun</a></p>
<p dir="auto">Symfony 3.2</p>
<p dir="auto">I have a system of widgets submitted via Ajax. Sometimes I can have the same form rendered twice on the same page by the same embedded controller.</p>
<p dir="auto">When this happens, the profiler doesn't start correctly, throws a js alert with 404 and "Exception in FormDataCollector.php line 274: Serialization of 'Closure' is not allowed" is shown right at the end of the page.</p>
<p dir="auto">For right now fixed it with</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="$this-&gt;get('form.factory')-&gt;createNamed('form'.random_int(1, 100), ...);"><pre class="notranslate"><span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'form.factory'</span>)-&gt;<span class="pl-en">createNamed</span>(<span class="pl-s">'form'</span>.random_int(<span class="pl-c1">1</span>, <span class="pl-c1">100</span>), ...);</pre></div>