Voici un exemple de configuration Log4j utilisée :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="true" xmlns:log4j="http://jakarta.apache.org/log4j/">

<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %p [%c] - %m%n" />
</layout>
</appender>

<appender name="logFile" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="logs/${app.ctx}.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd"/>
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %p [%c] - %m%n" />
</layout>
</appender>

<appender name="monitoring" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="logs/${app.ctx}_monitoring.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd"/>
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %p - %m%n" />
</layout>
</appender>

<appender name="errors" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="logs/${app.ctx}_errors.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd"/>
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %p [%c] - %m%n" />
</layout>
</appender>

<appender name="requests" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="logs/${app.ctx}_requests.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd"/>
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %p [%c] - %m%n" />
</layout>
</appender>

<logger name="org.apache.catalina" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.apache.jasper" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.springframework" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.hibernate" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.hibernate.cfg" additivity="false">
<level value="WARN" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.codehaus.xfire" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping" additivity="false">
<level value="FATAL" />
<appender-ref ref="errors" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.codehaus.xfire.handler.DefaultFaultHandler" additivity="false">
<level value="INFO" />
<appender-ref ref="errors" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="net.sf.ehcache" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.apache.commons.httpclient" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.apache.commons.beanutils.BeanUtils" additivity="false">
<level value="WARN" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.apache.commons.beanutils.PropertyUtils" additivity="false">
<level value="WARN" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.apache.commons.digester" additivity="false">
<level value="WARN" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="org.hibernate.cache" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="net.sf.ehcache.hibernate" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="freemarker.cache" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="freemarker.bean" additivity="false">
<level value="WARN" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="fr.openfarm" additivity="false">
<level value="DEBUG" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="fr.openfarm.service.impl.ServiceImpl" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="fr.openfarm.service.impl.ServiceImpl2" additivity="false">
<level value="INFO" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="fr.openfarm.domain.model.HibernateSchemaInterceptor" additivity="false">
<level value="DEBUG" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="fr.openfarm.service.exception" additivity="false">
<level value="INFO" />
<appender-ref ref="errors" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="fr.openfarm.ws.fault" additivity="false">
<level value="INFO" />
<appender-ref ref="errors" />
<appender-ref ref="logFile" />
<appender-ref ref="console" />
</logger>

<logger name="fr.openfarm.aop.PerformanceProfilerAspect" additivity="false">
<level value="INFO" />
<appender-ref ref="console" />
<appender-ref ref="logFile" />
<appender-ref ref="requests"/>
<appender-ref ref="monitoring"/>
</logger>

<root>
<priority value="INFO" />
<appender-ref ref="console" />
<appender-ref ref="logFile" />
</root>

</log4j:configuration>

La configuration est ensuite activée par Spring au niveau du fichier web.xml grâce au org.springframework.web.util.Log4jConfigListener pour lequel il faut ajouter le paramètre log4jConfigLocation qui indique où se trouve le fichier de configuration log4j. Dans notre cas, ce nom de fichier est paramétré grâce à une variable d'environnement java "env" que l'on passera à la JVM (ex: -Denv=preprod pour choisir le fichier log4j-preprod.xml ) et l'on considère que les niveaux de logs n'auront pas à être différenciés. Les deux applications étant à l'intérieur du même serveur Tomcat, donc la même JVM, partagerons donc le même fichier de log log4j-${env}.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">

<display-name>OPENFARM WebServices</display-name>

<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j-${env}.xml</param-value>
</context-param>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml,
classpath:applicationContext-hibernate.xml,
/WEB-INF/xfire-servlet.xml
</param-value>
</context-param>

<listener>
<listener-class>
org.springframework.web.util.Log4jConfigListener
</listener-class>
</listener>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<!-- XFire SOAP servlets -->
<servlet>
<servlet-name>xfire</servlet-name>
<servlet-class>
org.codehaus.xfire.spring.XFireSpringServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>xfire</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>

<resource-ref>
<description>SYCAR database</description>
<res-ref-name>jdbc/mydb</res-ref-name>
<res-ref-type>javax.sql.DataSource</res-ref-type>
<res-auth>Container</res-auth>
</resource-ref>

<!-- Compress SOAP servlets -->
<filter>
<filter-name>CompressingFilter</filter-name>
<filter-class>
com.planetj.servlet.filter.compression.CompressingFilter
</filter-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>statsEnabled</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CompressingFilter</filter-name>
<url-pattern>/services/*</url-pattern>
</filter-mapping>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>

Cette configuration appliquée aux deux applications présente un premier problème : les noms des fichier de log des appenders ne peuvent pas être identiques si l'on veut que les logs soient bien séparées pour chaque application. On remarquera d'ailleurs que nous avons mis logs/${app.ctx}_*.log pour les noms de fichier. En effet, la seule possibilité est de générer un fichier log4j différent selon le contexte de l'application à la compilation. Pour cela nous utilisons les filtres maven qui permettent d'effectuer des remplacements dans les fichiers textes. Il faut ajouter quelques éléments de configuration à plusieurs endroits dans notre fichier pom.xml :

D'abord au niveau du plugin war pour qu'il applique le filtrage et fasse le remplacement des properties / variables passées à maven dans le fichier web.xml :

             <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<!-- filtrage du fichier web.xml pour injecter la valeur app.ctx -->
<filteringDeploymentDescriptors>true</filteringDeploymentDescriptors>
</configuration>
</plugin>

Ensuite au niveau de la gestion des fichiers ressources pendant la phase de compilation pour qu'il applique le filtrage et fasse le remplacement des properties / variables passées à maven dans les fichiers xml  ou properties. On notera d'ailleurs que le tag <finalName> prend aussi la valeur ${app.ctx} puisque c'est le nom du contexte web de l'appli.

     <build>
<!-- le nom du contexte de l'application est passé en paramètre -->
<finalName>${app.ctx}</finalName>
<resources>
<!-- filtrage des fichiers ressources log4j.xml et *.properties -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<!-- pas de filtrage pour les autres fichiers notamment binaires -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.xml</exclude>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>
[...]

</build>

ATTENTION : il faut prendre quelques précautions avec les filtres maven, notamment en ajoutant une section ne filtrant pas les autres fichiers binaires sous peine de corrompre ceux-ci puisque maven recopie ces fichiers en les considérant comme des fichiers textes!

Il suffit alors de compiler les deux applications comme suit :


$ mvn -Dapp.ctx=my-app1 clean package
$ mvn -Dapp.ctx=my-app2 clean package

On peut alors constater qu'il y a bien eu modification des noms des fichiers de log pour chacune des applications my-app1 et my-app2. Cependant lors de l'exécution, on s'aperçoit qu'une seule des applications est logguée et que l'erreur suivante apparait :


Exception lors de l'envoi de l'événement contexte initialisé (context initialized) à  l'instance de classe d'écoute (listener) org.springframework.web.util.Log4jConfigListener
java.lang.IllegalStateException: Web app root system property already set to different value: 'webapp.root' = [/tomcat-webapps/my-app2/] instead of [/tomcat-webapps/my-app1/] - Choose unique values for the 'webAppRootKey' context-param in your web.xml files!
at org.springframework.web.util.WebUtils.setWebAppRootSystemProperty(WebUtils.java:132)
at org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:117)
at org.springframework.web.util.Log4jConfigListener.contextInitialized(Log4jConfigListener.java:51)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3729)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4187)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:759)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:739)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:524)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:608)
at org.apache.catalina.startup.HostConfig.deployDescriptors(HostConfig.java:535)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:470)
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1122)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:310)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1021)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:718)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1013)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:442)
at org.apache.catalina.core.StandardService.start(StandardService.java:450)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:709)
at org.apache.catalina.startup.Catalina.start(Catalina.java:551)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:294)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:432)

En regardant la documentation Spring sur Log4jWebConfigurer, on peut lire la mise en garde suivante :

WARNING: Some containers (like Tomcat) do not keep system properties separate per web app. You have to use unique "webAppRootKey" context-params per web app then, to avoid clashes. Other containers like Resin do isolate each web app's system properties: Here you can use the default key (i.e. no "webAppRootKey" context-param at all) without worrying

Il faut donc ajouter ce paramètre dans le web.xml en y mettant le nom du contexte applicatif ${app.ctx} de façon à ce que le paramètre webAppRootKey soit unique :

     <context-param>
<param-name>webAppRootKey</param-name>
<param-value>${app.ctx}</param-value>
</context-param>

D'autre part, il ne faut pas non plus oublier de configurer le mode de gestion des properties dans Spring et notamment celles des system properties. En effet, il faut indiquer à Spring si les properties du système doivent écraser les valeurs pouvant être définies dans le contexte Spring. Ceci ce fait grâce au PropertyPlaceholderConfigurer avec la propriété systemPropertiesModeName qui peut prendre plusieurs valeurs :

     <bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/jdbc.properties</value>
<value>classpath:/hib3.properties</value>
<value>classpath:/myapp.properties</value>
</list>
</property>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
</bean>

En utilisant la valeur SYSTEM_PROPERTIES_MODE_OVERRIDE , Spring vérifiera d'abord les system properties en premier avant tout autre property spécifiée par ailleurs.

Sources :

http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/util/Log4jWebConfigurer.html

http://tai-dev.blog.co.uk/2008/08/07/glassfish-spring-multiple-applications-p-4555617/

http://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html

http://maven.apache.org/plugins/maven-war-plugin/faq.html#filtering