Logging Best Practices

Logging Frameworks

SLF4J is the preferred logging API

Logback is the preferred logging implementation for Jasig portlets.

Project Setup

The following assumes a project built with Maven and using the Spring Framework.

Webapp Configuration

To allow for the log files to be written to a location relative to the webapp Spring's WebAppRootListener is added to the web.xml. This listener sets the absolute file system path to the webapp as a system property. Because this is a system property the webAppRootKey value needs to be set to the name of your portlet application.

<context-param>
    <param-name>webAppRootKey</param-name>
    <param-value>CHANGE_TO_YOUR_PORTLET_NAME.webapp.root</param-value>
</context-param>
<!-- Must be the first listener so the webAppRootKey gets set for logback -->
<listener>
    <listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
</listener>
<!-- Needed to remove JMX registration and allow for classloader GC -->
<listener>
    <listener-class>ch.qos.logback.classic.selector.servlet.ContextDetachingSCL</listener-class>
</listener>

Note: For uPortal code and BUNDLED portlets, current uPortal code substitutes the log file directory at build time into logback.xml or log4j.properties from the filters file so the webAppRootKey and org.springframework.web.util.WebAppRootListener listener class is not required (though it will not hurt anything either).

Remove entries for Log4j from web.xml:

     <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/classes/log4j.properties</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

 

Logback Configuration

Logback Configuration

Place logback.xml in src/main/resources/ The root of the classpath is the default location for logback configuration.

Change the two instances of the string CHANGE_TO_YOUR_PORTLET_NAME to the name of your portlet application.

Logback Test Configuration

Place logback-test.xml in src/test/resources/ Logback will use the logback-test.xml file from the classpath in preference to logback.xml.

Logback Configuration for Overlay portlets

If the portlet is included in uPortal as an overlay portlet (e.g. in directory uportal-portlets-overlay), copy overlay-logback.xml to uportal-portlets-overlay/PortletName/src/main/resources/logback.xml.

Change the one instance of the string CHANGE_TO_YOUR_PORTLET_NAME to the name of your portlet application.  Also adjust the portlet package name for the DEBUG log statement that's commented out.

Logback configuration for portlets participating in mvn data-import

If the portlet uses the data-import maven goal to import data (Calendar portlet does this for example), copy command-line.logback.xml to src/main/resources and configure the java fork of cernunnos to use this file; e.g.

<java failonerror="true" classname="org.danann.cernunnos.runtime.Main">
    <sysproperty key="logback.configurationFile" value="command-line.logback.xml" />
...
</java>
Remove the now obsolete log4j.properties files

Delete the log4j.properties file from

  • src/main/resources
  • src/test/resources
  • uPortal's uportal-portlets-overlay/PortletName/src/main/resources folder (bundled portlets only)

Maven Configuration

Approach 1 (easiest and strongly recommended): Remove log4j and commons-logging using provided scope

Use provided scope. This has the advantage of being quick and easy, but when running unit tests your IDE, e.g. Eclipse, will still place commons-logging.jar on your projet's class path as seen by your IDE. You would need to make sure that jcl-over-slf4j.jar is visible before commons-logging.jar by your IDE.  See http://www.slf4j.org/faq.html#excludingJCL.

Step 1: Remove all references to commons-logging and log4j from the dependencyManagement section, dependencies section, and from all exclude entries.

Step 2: Add dependencies to dependencyManagement and dependencies (and versions in properties).  Update slf4j to the latest version and logback to the latest version.

 

    <properties>
...
        <logbackVersion>1.0.13</logbackVersion>
        <slf4jVersion>1.7.5</slf4jVersion>
    </properties>

<dependencyManagement>
...
            <!-- For sl4j/logback logging, see https://wiki.jasig.org/display/PLT/Logging+Best+Practices -->
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.1.2</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>${slf4jVersion}</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jul-to-slf4j</artifactId>
                <version>${slf4jVersion}</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>log4j-over-slf4j</artifactId>
                <version>${slf4jVersion}</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jcl-over-slf4j</artifactId>
                <version>${slf4jVersion}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logbackVersion}</version>
                <scope>runtime</scope>
            </dependency>
            <!-- End of logging section -->
</dependencyManagement>
<dependencies>
...
        <!-- Logging section -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <!-- End of logging section -->
</dependencies>
Approach 2 (not preferred)

Use the tree goal of the maven dependency plugin to see where these libraries are pulled in from. The end of the output will have a block that looks something like what you see below. The goal here is to find any common-logging or log4j dependencies, trace up to the root dependency and add an excludes block to it.

$ mvn dependency:tree
....
+- joda-time:joda-time:jar:2.2:compile
+- org.jasig.resourceserver:resource-server-content:war:1.0.29:compile
+- org.jvnet.jaxb2_commons:jaxb2-basics-runtime:jar:0.6.4:compile
+- org.springframework:spring-web:jar:3.2.2.RELEASE:compile
|  +- commons-logging:commons-logging:jar:1.1.1:compile
|  +- aopalliance:aopalliance:jar:1.0:compile
|  +- org.springframework:spring-aop:jar:3.2.2.RELEASE:compile
|  \- org.springframework:spring-context:jar:3.2.2.RELEASE:compile
|     \- org.springframework:spring-expression:jar:3.2.2.RELEASE:compile
+- org.slf4j:slf4j-api:jar:1.7.3:compile
+- org.slf4j:jul-to-slf4j:jar:1.7.3:compile
+- org.slf4j:log4j-over-slf4j:jar:1.7.3:compile
+- org.slf4j:jcl-over-slf4j:jar:1.7.3:compile
+- ch.qos.logback:logback-classic:jar:1.0.10:runtime
|  \- ch.qos.logback:logback-core:jar:1.0.10:runtime
+- javax.servlet:servlet-api:jar:2.5:provided
\- junit:junit:jar:4.11:test
   \- org.hamcrest:hamcrest-core:jar:1.3:test
....

Example excludes:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>3.2.2-RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Setup the enforcer plugin in the build/plugins section to blacklist commons-logging and log4j

 <plugin>
    <artifactId>maven-enforcer-plugin</artifactId>
    <executions>
        <execution>
            <id>enforce-versions</id>
            <goals>
                <goal>enforce</goal>
            </goals>
            <configuration>
                <rules>
                  <bannedDependencies>
                     <excludes>
                        <exclude>commons-logging:commons-logging</exclude>
                        <exclude>commons-logging:commons-logging-api</exclude>
                        <exclude>log4j:log4j</exclude>
                     </excludes>
                  </bannedDependencies>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

Step 2: Add sl4j and logback

Add the following dependencies to the project, update slf4j to the latest version and logback to the latest version.

<!-- For sl4j/logback logging, see https://wiki.jasig.org/display/PLT/Logging+Best+Practices -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.0.10</version>
    <scope>runtime</scope>
</dependency>
<!-- End of logging section -->

Update overlay files

If the portlet is also included in a maven overlay, update the maven overlay project pom.xml as well.