Initializing Log4j MDC in Hippo CMS

October 5th 2018 Hippo CMS Log4J Java Spring

In Log4j, Mapped Diagnostic Context (MDC) can be used to provide additional context information for every log message. In server applications it will usually be initialized for every request in a filter. In Hippo CMS, a custom valve must be injected into the request pipeline for that purpose.

The valve must extend the AbstractOrderableValve abstract class and implement the invoke method:

public class DiagnosisContextInitValve extends AbstractOrderableValve {

    @Override
    public void invoke(ValveContext valveContext) throws ContainerException {
        try {
            MDC.put("hostname", getHostName(valveContext));
        } finally {
            valveContext.invokeNext();
        }
    }
}

In the above sample, we want to put the hostname value into the context. We can obtain this information using the code from the official documentation:

private String getHostName(ValveContext valveContext) {
    final HstRequestContext requestContext = valveContext.getRequestContext();
    if (requestContext != null) {
        final HstContainerURL baseURL = requestContext.getBaseURL();
        if (baseURL != null) {
            return baseURL.getHostName();
        }
    }
    return "";
}

The valve can be injected into the pipeline with a Spring bean configuration XML file that needs to be placed in site/src/main/resources/META-INF/hst-assembly/overrides:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="diagnosisContextInitValve"
        class="com.damirscorner.blog.samples.valves.DiagnosisContextInitValve">
    <property name="valveName" value="diagnosisContextInitValve" />
    <property name="afterValves" value="securityValve"/>
    <property name="beforeValves" value="pageCachingValve"/>
  </bean>
  <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject">
      <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetObject" ref="org.hippoecm.hst.core.container.Pipelines" />
        <property name="targetMethod" value="getPipeline"/>
        <property name="arguments">
          <value>DefaultSitePipeline</value>
        </property>
      </bean>
    </property>
    <property name="targetMethod" value="addProcessingValve"/>
    <property name="arguments">
      <ref bean="diagnosisContextInitValve" />
    </property>
  </bean>
</beans>

The MDC information is stored per thread. Since we don't know how threads are reused in Hippo CMS, it's a good practice to clean up the context when a request is completed. We need another custom valve to do that:

public class DiagnosisContextCleanupValve extends AbstractOrderableValve {

    @Override
    public void invoke(ValveContext valveContext) throws ContainerException {
        try {
            MDC.clear();
        } finally {
            valveContext.invokeNext();
        }
    }
}

This one must be placed into the cleanup valves collection instead of into the processing valves collection:

<bean id="diagnosisContextCleanupValve"
      class="com.damirscorner.blog.samples.valves.DiagnosisContextCleanupValve">
  <property name="valveName" value="diagnosisContextCleanupValve" />
  <property name="afterValves" value="diagnosticReportingValve"/>
  <property name="beforeValves" value=""/>
</bean>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetObject">
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
      <property name="targetObject" ref="org.hippoecm.hst.core.container.Pipelines" />
      <property name="targetMethod" value="getPipeline"/>
      <property name="arguments">
        <value>DefaultSitePipeline</value>
      </property>
    </bean>
  </property>
  <property name="targetMethod" value="addCleanupValve"/>
  <property name="arguments">
    <ref bean="diagnosisContextCleanupValve" />
  </property>
</bean>

To include a value from MDC into the log, the %X{key} pattern can be used in a pattern layout. To test the valves, you can replace the existing PatternLayout elements in conf/log4j2-dev.xml and conf/log4j2-dist.xml with the following one:

<PatternLayout pattern="%d{dd.MM.yyyy HH:mm:ss} %X{hostname} %-5p %t [%C{1}.%M:%L] %m%n"/>

If you've configured Hippo CMS to use JSON layout instead, all the values from MDC will automatically be included in contextMap.

Copyright
Creative Commons License