Using JSONLayout for Log4j with Hippo CMS

September 28th 2018 Hippo CMS Log4J Maven

If you want to post-process the application logs, using the JSON format will usually make it easier. In Log4j, there's the JSON Layout available for that purpose. Making it work in Hippo CMS took me longer than expected.

Hippo CMS is configured to use Log4j by default. You can find the configuration in the conf/log4j2-dev.xml and conf/log4j2-dist.xml files for the development and distribution builds respectively. To use the JSON format instead of plain text you should replace both PatternLayout elements in each file with JSONLayout elements:

<JSONLayout complete="false" compact="true" eventEol="true"
            charset="UTF-8" properties="true"/>

Since JSON Layout has additional runtime dependencies on Jackson databind, this change will break Hippo CMS. The application will fail to start. You'll find the error logged in target/tomcat8x/logs/localhost.{date}.log:

org.apache.catalina.core.StandardContext.filterStart Exception starting filter [CMS]
java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ser/impl/SimpleFilterProvider
at org.apache.logging.log4j.core.layout.JacksonFactory.newWriter(JacksonFactory.java:212)

To resolve the issue, the dependencies need to be deployed to Tomcat's shared/lib folder.

During development, Cargo Maven plugin is used for deployment. Therefore, the dependencies must be specified in its configuration:

<plugin>
  <groupId>org.codehaus.cargo</groupId>
  <artifactId>cargo-maven2-plugin</artifactId>
  <configuration>
    <!-- ... -->
    <container>
      <dependencies>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <classpath>shared</classpath>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <classpath>shared</classpath>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <classpath>shared</classpath>
        </dependency>
      </dependencies>
      <!-- ... -->
    </container>
  </configuration>
</plugin>

Transitive dependencies are listed explicitly because the plugin does not include them automatically (jackson-annotations and jackson-core).

Of course, for this to work, the dependency must also be added at the project level:

<dependencyManagement>
  <dependencies>
    <!-- ... -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

And also at the profile level:

<profile>
  <id>cargo.run</id>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
    </dependency>
  </dependencies>
  <!-- ... -->
</profile>

With all that in place, the application will start again. However, you might encounter Jackson version conflicts at run time if you specify a version different from the one that's already distributed with Hippo CMS. Check the version in target/tomcat8x/webapps/site.war/WEB-INF/lib and set jackson.version accordingly:

<jackson.version>2.8.8</jackson.version>

There are some additional changes required to also make everything work in distribution builds. First, the dependency needs to be added to both distribution build profiles (dist and dist-with-development-data):

<profile>
  <id>dist</id>
  <dependencies>
    <!-- ... -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <!-- ... -->
</profile>
<profile>
  <id>dist-with-development-data</id>
  <dependencies>
    <!-- ... -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <!-- ... -->
</profile>

The Maven Assembly Plugin is used to create the distribution archive. For the shared/lib folder, the component is already configured in src/main/assembly/shared-lib-component.xml. The three dependencies (direct and transitive) must be added to the existing dependencySet:

<dependencySet>
  <useProjectArtifact>false</useProjectArtifact>
  <outputDirectory>shared/lib</outputDirectory>
  <scope>provided</scope>
  <includes>
    <!-- ... -->
    <include>com.fasterxml.jackson.core:jackson-annotations</include>
    <include>com.fasterxml.jackson.core:jackson-core</include>
    <include>com.fasterxml.jackson.core:jackson-databind</include>
  </includes>
</dependencySet>

You can validate the configuration by checking the generated .tar.gz distribution archive in the target folder. The shared/lib folder inside it must contain the required dependencies.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License