6
votes

I am trying to use the new RoutingAppender of Log4j2 to route the different logs based on the MDC (ThreadContext in Log4j2). What I want to do is the following:

  • If MDC map has $contextId -> Append to $contextId appender (specific log)
  • If MDC does not have $contextId -> Append to main appender (general log)

I want to achieve this using a wildcard pattern in the tag and then filter using the key parameter in the for contextId (${ctx:contextId}) and using the default (without key paramenter) for the main appender, however I don't know which value is that wildcard.

Any help is appreciated, maybe I am approaching this from the wrong path. I have been reading about Filters but don't seem to work as I want.

Thanks!

4

4 Answers

2
votes

I was unhappy with the solution to define the fallback route with the trick described in https://issues.apache.org/jira/browse/LOG4J2-326 and http://logging.apache.org/log4j/2.x/faq.html#separate_log_files, because this forces me to duplicate the appender configuration contained in the routes. I do not need different appender configuration for the default route, but just a proper file name for the general log.

Given that the default property map is looked up for an property which is undefined in its context, see https://logging.apache.org/log4j/2.x/manual/configuration.html#PropertySubstitution, I think the most straightforward way is just to define the default, e.g.

<Properties>
    <Property name="fruits">any_fruit</Property>
</Properties>

and in case the thread context does not have ${ctx:fruits} the "any_fruit" is taken.

1
votes

Thanks for the link Remko, I have found a temporary solution until that feature gets improved from the guys of Log4j2. The solution is using both RoutingAppender and Filters. This is how my log4j2 config looks like (I have properties defined but I am not showing them here):

<appenders>
    <appender name="applicationAppender" type="RollingFile" fileName="${logFileName}" filePattern="${logFileNamePattern}" bufferedIO="true" immediateFlush="true" append="true">
        <layout type="PatternLayout" pattern="${logPattern}" />
        <Policies>
            <TimeBasedTriggeringPolicy />
            <SizeBasedTriggeringPolicy size="${logFileSize}" />
        </Policies>
        <DefaultRolloverStrategy max="${logFileCount}" />
    </appender>

    <Routing name="contextSpecificAppender">
        <Routes pattern="$${ctx:contextId}">
            <Route>
                <appender name="Rolling-${ctx:contextId}" type="RollingFile" fileName="logs/${ctx:contextId}.log" filePattern="${logFileNamePattern}" bufferedIO="true" immediateFlush="true" append="true">
                    <layout type="PatternLayout" pattern="${logPattern}" />
                    <Policies>
                        <TimeBasedTriggeringPolicy />
                        <SizeBasedTriggeringPolicy size="${logFileSize}" />
                    </Policies>
                    <DefaultRolloverStrategy max="${logFileCount}" />
                </appender>
            </Route>
        </Routes>
    </Routing>
</appenders>

<loggers>
    <root level="info">
        <appender-ref ref="contextSpecificAppender">
            <ThreadContextMapFilter onMatch="DENY" onMismatch="ACCEPT">
                <KeyValuePair key="contextId" value="" />
            </ThreadContextMapFilter>
        </appender-ref>
        <appender-ref ref="applicationAppender">
            <ThreadContextMapFilter onMatch="ACCEPT" onMismatch="DENY">
                <KeyValuePair key="contextId" value="" />
            </ThreadContextMapFilter>
        </appender-ref>
    </root>
</loggers>

What I do it is calling ThreadContext.put("contextId", "") or ThreadContext.put("contextId", "something") depending on what appender I want to log. I hope the wildward feature gets implemented soon, but for the meantime, this solution is enough for me.

Thanks!

1
votes

Thanks hveiga for following up and posting your solution, it was helpful. I wanted to say you can avoid your filter solution by adding a second 'route' that routes all messages with no value for your routing key as explained here: http://logging.apache.org/log4j/2.x/faq.html#separate_log_files

So your updated log4j config would look like this.

<appenders>
    <appender name="applicationAppender" type="RollingFile" fileName="${logFileName}" filePattern="${logFileNamePattern}" bufferedIO="true" immediateFlush="true" append="true">
        <layout type="PatternLayout" pattern="${logPattern}" />
        <Policies>
            <TimeBasedTriggeringPolicy />
            <SizeBasedTriggeringPolicy size="${logFileSize}" />
        </Policies>
        <DefaultRolloverStrategy max="${logFileCount}" />
    </appender>

    <Routing name="contextSpecificAppender">
        <Routes pattern="$${ctx:contextId}">
            <Route>
                <appender name="Rolling-${ctx:contextId}" type="RollingFile" fileName="logs/${ctx:contextId}.log" filePattern="${logFileNamePattern}" bufferedIO="true" immediateFlush="true" append="true">
                    <layout type="PatternLayout" pattern="${logPattern}" />
                    <Policies>
                        <TimeBasedTriggeringPolicy />
                        <SizeBasedTriggeringPolicy size="${logFileSize}" />
                    </Policies>
                    <DefaultRolloverStrategy max="${logFileCount}" />
                </appender>
            </Route>
            <Route ref="applicationAppender" key="$${ctx:contextId}">
            </Route>
        </Routes>
    </Routing>
</appenders>

<loggers>
    <root level="info">
        <appender-ref ref="contextSpecificAppender"/>
    </root>
</loggers>

And in your application, you can just set the ThreadContext by calling ThreadContext.put("contextId", "something") and clear it when you are done by calling ThreadContext.clear() OR ThreadContext.remove("contextId")

Lastly, I used the

<RollingFile>

element (like the examples linked above) instead of

<appender type="RollingFile"> 

element you used. I believe this is preferred when you migrate to log4j2 from log4j.