Don Brinker

Posts written by Don Brinker

November 29

Configuring log4j through Spring

As I mentioned in a previous post, in order to have different log4j settings in different environments without an environment-specific build, you need to allow Spring to manage the log4j configuration. Now there is currently no way to specify that in the Application Context itself, but a post in the Spring Support Forums suggests an alternate approach.

If you define the following custom bean:

package com.molecular.util.logging;
 
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Log4jConfigurer;
 
/**
 * A simple Spring Bean that allows log configuration to be managed in the Application Context
 *
 * Originally by Daniel Rijkhof (daniel.rijkhof@gmail.com)
 */
public class Log4jDirectConfigurer implements InitializingBean {
    private String location;
    private long refreshInterval;
 
    public void afterPropertiesSet() throws Exception {
        if (location == null) {
            return;
        }
 
        if (refreshInterval == 0) {
            Log4jConfigurer.initLogging(location);
        }
        else {
            Log4jConfigurer.initLogging( location, refreshInterval);
        }
    }
 
    // Attribute injectors
    public void setLocation(String location) {
        this.location = location;
    }
 
    public void setRefreshInterval(long refreshInterval){
        this.refreshInterval = refreshInterval;
    }
}

You can then define the location of the log4j configuration in the Spring Context. Remembering that in the earlier discussion we were using the system property env to denote the environment we’re running:

<bean id="log4jDirectConfigurer" class="com.molecular.util.logging.Log4jDirectConfigurer">
  <property name="location" value="classpath:log4j-${env}.properties"/>
  <property name="refreshInterval" value="0"/>
</bean>

Alternately, you can store the name of the log file in the system properties file. This is useful when the file name will vary wildly between installations:

<bean id="log4jDirectConfigurer" class="com.molecular.util.logging.Log4jDirectConfigurer">
  <property name="location" value="${logging.location}"/>
  <property name="refreshInterval" value="0"/>
</bean>

In either case, there’s no need to call the code directly, since Spring will execute afterPropertiesSet automatically during the setup phase.Now as with everything, this has limitations. This only handles information that is logged following bean initialization. Anything that happens during initialization (either in Spring or in your own code) will not be logged to these files. If your application does very little at initialization (which is fairly common) this may still work for you.

Edit: As Ken pointed out below, I’d removed a log directory from the underlying code, but forgotten to remove it from the configs. That’s fixed now.

November 26

Simple environmental independence with Spring

One of the basic problems most projects run into sooner or later is handling multiple environments. Database connections, web service hosts, and other parameters will vary between development, testing, and production environments. Beyond a certain point, editing each of the configuration files becomes too time-consuming and error-prone, and an automatic approach to solve the problem is needed.

A number of approaches have already been well documented, including using multiple Ant targets and using differing Maven profiles for each environment. These all have one basic problem, however – they result in one output (WAR/EAR/JAR file) per environment. Anyone who’s accidentally shipped a test WAR to the production environment (and watched everything break as a result) can attest to how nice it would be to have a single output that can be used in any environment.

For those using the Spring Framework, a simple solution exists to this problem. A fairly commonly used part of the framework is the PropertyPlaceholderConfigurator, which will allow property names to be specified in lieu of values in the Spring configuration files. These property names are replaced with the contents of specified property files at runtime. For example:

<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-2.0.xsd">
  <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <value>classpath:jdbc.properties</value>
      </list>
    </property>
  </bean></beans>
  <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
    <property name="driverClassName" value="${jdbc.connection.driver_class}"/>
    <property name="url" value="${jdbc.connection.url}"/>
    <property name="username" value="${jdbc.connection.username}"/>
    <property name="password" value="${jdbc.connection.password}"/>
    <property name="defaultAutoCommit" value="false"/>
  </bean>

Nothing really new here. As specified above, we still need a seperate jdbc.properties file for each environment. This doesn’t really get us any closer to our goal.

However, there’s a little-documented feature of the PropertyPlaceholderConfigurer: system properties can be used when specifying the property file names. When this is the case, the system properties will be evaluated prior to the property files being loaded. This then allows us to create a single definition for all environments:

<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-2.0.xsd">
  <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <value>classpath:jdbc-${env}.properties</value>
      </list>
    </property>
  </bean></beans>
  <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
    <property name="driverClassName" value="${jdbc.connection.driver_class}"/>
    <property name="url" value="${jdbc.connection.url}"/>
    <property name="username" value="${jdbc.connection.username}"/>
    <property name="password" value="${jdbc.connection.password}"/>
    <property name="defaultAutoCommit" value="false"/>
  </bean>

And voila, we have what we’re looking for. The output WAR can ship with configuration files for each environment – jdbc-dev.properties, jdbc-test.properties, and so forth. As long as the env system property is set prior to starting the application, the same output file can be used in any environment.

Now this isn’t a perfect solution. Configuration files outside the Spring context (such as those used for log4j or ehcache) can not be managed directly by this approach – in order to have separate log settings for each environment, you’ll need to either use one of the above approaches to build multiple outputs, or manage the configuration in the Spring context. More about that some other time…

Technorati Profile

Browse posts by month

Browse by author

We're always looking for rockstars

Come take a look at careers with Molecular