log4j2

Do Route-keys of Routing appender of Log4j2 need to use "$$" or "$" for unresolved expressions?


The docs contain the following warning, with the emphasizes taken from the official docs:

Lookups in the children of the Route component are not evaluated at configuration time. The substitution is delayed until the Route element is evaluated. This means that ${...} expression should not be escaped as $${...}.

https://logging.apache.org/log4j/2.x/manual/appenders/delegating.html#Route

I have the following two configs, which seem to behave the same:

<Routing name="[...]">
    <Routes pattern="$${ctx:foo}">
        <Route key="true"        ref="RollingFileFoo" />
        <Route key="$${ctx:foo}" ref="RollingFile"    />
    </Routes>
</Routing>

vs.

<Routing name="[...]">
    <Routes pattern="$${ctx:foo}">
        <Route key="true"       ref="RollingFileFoo" />
        <Route key="${ctx:foo}" ref="RollingFile"    />
    </Routes>
</Routing>

The behaviour from my understanding is that pattern gets an expression configured and that expression is interpolated at config time, why $$ is needed for escaping reasons. So at runtime the pattern actually is ${ctx:foo} and evaluated for each and every log statement passing the appender.

Step 2: If the expression doesn't find a key of the given name foo, then the result of the pattern is kept as the pattern itself. That's why there's an additional key with the pattern itself as a value to match against. And this is where things become interesting:

Is that result now expected to be with $$ like configured in the file or $ only like taken at runtime?

I'm a bit confused which the correct value is because both seem to work exactly the same. Which shouldn't happen if the key attribute implements an equals comparison to it's value.

    public Route getRoute(final String key) {
        for (final Route route : routes) {
            if (Objects.equals(route.getKey(), key)) {
                return route;
            }
        }
        return null;
    }

https://github.com/apache/logging-log4j2/blob/9176b44193b107b1a6c1ca9efda22f79fa20db3a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java#L198C1-L205C6

From my understanding, the escaping using $$ in pattern is necessary because that pattern otherwise gets resolved already at config time, but it needs to be forwarded into runtime. If the difference is that key is simply NEVER resolved at all at config time and really only taken as string as-is, then I would have expected $$ to fail, because that value is not available at runtime?


Solution

  • I've received an answer at the mailing list, thanks Piotr! So $$ at both places is correct in my case and children really refers to the body of the Route only.

    There is subtle difference between the two configurations:
    
      * In the first case the value of `key` will always be a literal
        `${ctx:foo}`.
      * In the second case Log4j Core will expand `${ctx:foo}` at
        configuration time. Since the configuration of Log4j Core happens
        very early in the startup sequence, `foo` will almost certainly be
        undefined, so `key` will be a literal `${ctx:foo}`. In the very
        unlikely case `foo` is defined at configuration time, however, the
        value of `foo` will be substituted and your configuration will not
        work as expected.
    
    To see the difference, try adding:
    
    static {
    ThreadContext.put("foo", "true");
    }
    
    private static final Logger logger = LogManager.getLogger();
    
    at the very beginning of the main class of your application.
    
    Piotr
    

    https://lists.apache.org/thread/jvj5s5txdknyx8b8o0tqxtvyrf0zzyfd