Search code examples
javaosgilog4j2apache-karafpax

log4j2 with pax-logging: can't use values from StructuredDataMessage


I'm using pax-logging-api along with pax-logging-log4j2 for logging from my OSGi bundles. I would like to utilize Log4J2's StructuredDataMessage (using EventLogger) to write some messages to a database. However, I'm unable to read the values I put into the StructuredDataMessage from the appenders when using Pax Logging.

The following works in a non-OSGi project using the Log4J2 libraries directly:

log4j2.properties:

appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %m%n

appender.event.type = Console
appender.event.name = event
appender.event.layout.type = PatternLayout
appender.event.layout.pattern = %marker ${sd:id} ${sd:testKey} %n    %m%n

rootLogger.level = debug
rootLogger.appenderRef.console.ref = STDOUT

logger.event.name = EventLogger
logger.event.level = debug
logger.event.appenderRef.console.ref = event
logger.event.additivity = false

Test.java:

public class Test {

    private static final Logger LOGGER = LogManager.getLogger(Test.class);

    public static void main(String[] args) {
        StructuredDataMessage msg = new StructuredDataMessage("1", "message", "event");
        msg.put("testKey", "testValue");

        LOGGER.info(msg);
        EventLogger.logEvent(msg);
    }
}

Output:

 1 testValue event [1 testKey="testValue"] message
EVENT 1 testValue 
    event [1 testKey="testValue"] message

Note that the event appender properly dereferenced the sd keys from the StructuredDataMessage.

However, the following does not work in OSGi with pax-logging:

org.ops4j.pax.logging.cfg:

log4j2.appender.console.type = Console
log4j2.appender.console.name = STDOUT
log4j2.appender.console.layout.type = PatternLayout
log4j2.appender.console.layout.pattern = %m%n

log4j2.appender.event.type = Console
log4j2.appender.event.name = event
log4j2.appender.event.layout.type = PatternLayout
log4j2.appender.event.layout.pattern = %marker \$\\\{sd:id\} \$\\\{sd:testKey\} %n    %m%n

log4j2.rootLogger.level = debug
log4j2.rootLogger.appenderRef.console.ref = STDOUT

log4j2.logger.event.name = EventLogger
log4j2.logger.event.level = debug
log4j2.logger.event.appenderRef.console.ref = event
log4j2.logger.event.additivity = false

Test.java:

public class Test implements BundleActivator {

    private static final Logger LOGGER = LogManager.getLogger(Test.class);

    @Override
    public void start(BundleContext context) throws Exception {
        StructuredDataMessage msg = new StructuredDataMessage("1", "message", "event");
        msg.put("testKey", "testValue");

        LOGGER.info(msg);
        EventLogger.logEvent(msg, Level.INFO);
    }

    @Override
    public void stop(BundleContext context) throws Exception {
    }
}

Output:

event [1 testKey="testValue"] message
EVENT ${sd:id} ${sd:testKey}
    event [1 testKey="testValue"] message

Is there a trick to getting this to work in pax-logging? I am able to access values from the MDC using \$\\\{ctx:key\} when applicable, so I'm assuming the syntax is similar. I've also tried using the lookups in patterns for RoutingAppender, FileAppender, etc. to no avail.

Thanks in advance!

Edit: I'm using the latest version of pax-logging-api and pax-logging-log4j2 (1.11.3)


Solution

  • OK, it's not yet a definitive answer - simply comment is too short to describe what happens.

    The stack trace with your call is:

    "pipe-restart 238@10666" prio=5 tid=0xc3 nid=NA runnable
      java.lang.Thread.State: RUNNABLE
          at org.ops4j.pax.logging.log4j2.internal.PaxLoggerImpl.doLog0(PaxLoggerImpl.java:354)
          at org.ops4j.pax.logging.log4j2.internal.PaxLoggerImpl.doLog(PaxLoggerImpl.java:337)
          at org.ops4j.pax.logging.log4j2.internal.PaxLoggerImpl.inform(PaxLoggerImpl.java:233)
          at org.ops4j.pax.logging.internal.TrackingLogger.inform(TrackingLogger.java:209)
          at org.ops4j.pax.logging.log4jv2.Log4jv2Logger.logMessage(Log4jv2Logger.java:162)
          at org.apache.logging.log4j.spi.AbstractLogger.log(AbstractLogger.java:2102)
          at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2190)
          at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2144)
          at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2127)
          at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1828)
          at org.apache.logging.log4j.EventLogger.logEvent(EventLogger.java:56)
          at grgr.test.ActivatorLogging.start(ActivatorLogging.java:39)
    ...
    

    org.ops4j.pax.logging.log4jv2.Log4jv2Logger.logMessage() is bridge between logging facade and logging backend.

    Remember - with pax-logging, you can, say, use Commons Logging facade with Log4J1 backend, or Log4j2 facade (and that's what you're doing) with e.g., Logback backend.

    That's why org.ops4j.pax.logging.log4jv2.Log4jv2Logger.logMessage() does this:

    } else if (level.intLevel() >= Level.INFO.intLevel()) {
        m_delegate.inform(paxMarker, message.getFormattedMessage(), t, fqcn);
    

    and your structured message is changed into String event [1 testKey="testValue"] message.

    Only then configured appenders are called - and the appender with layout that extract structured data can't find it, because the structured message is already converted to plain String.

    These 3 lines:

      at org.ops4j.pax.logging.log4j2.internal.PaxLoggerImpl.inform(PaxLoggerImpl.java:233)
      at org.ops4j.pax.logging.internal.TrackingLogger.inform(TrackingLogger.java:209)
      at org.ops4j.pax.logging.log4jv2.Log4jv2Logger.logMessage(Log4jv2Logger.java:162)
    

    do the crossing from pax-logging-api (facade) through TrackingLogger (bridge) to pax-logging-log4j2 (backend) losing structured information in between.

    I've created https://ops4j1.jira.com/browse/PAXLOGGING-302 and hope to do something about it soon.

    EDIT1

    The key is that in org.apache.logging.log4j.core.lookup.StructuredDataLookup#lookup(), this condition is true:

    if (event == null || !(event.getMessage() instanceof StructuredDataMessage)) {
        return null;
    }
    

    EDIT2

    I've just fixed https://ops4j1.jira.com/browse/PAXLOGGING-302 and this test proves it works:

    Logger logger = LogManager.getLogger("my.logger");
    
    logger.info(new StructuredDataMessage("1", "hello!", "typeX").with("key1", "sd1"));
    logger.info(new StringMapMessage().with("key1", "map1"));
    
    List<String> lines = readLines();
    assertTrue(lines.contains("my.logger/org.ops4j.pax.logging.it.Log4J2MessagesIntegrationTest typeX/sd1 [INFO] typeX [1 key1=\"sd1\"] hello!"));
    assertTrue(lines.contains("my.logger/org.ops4j.pax.logging.it.Log4J2MessagesIntegrationTest ${sd:type}/map1 [INFO] key1=\"map1\""));
    

    the configuration is:

    log4j2.appender.console.type = Console
    log4j2.appender.console.name = console
    log4j2.appender.console.layout.type = PatternLayout
    log4j2.appender.console.layout.pattern = %c/%C ${sd:type}/${map:key1} [%p] %m%n
    log4j2.rootLogger.level = info
    log4j2.rootLogger.appenderRef.file.ref = console
    

    (you have to use the escape sequences you've used, if configuring via etc/org.ops4j.pax.logging.cfg in Karaf).