Log4j : changer le niveau de log à chaud via JMX
Par guillaume le mardi 31 janvier 2012, 10:49 - java / j2ee - Lien permanent
Changer le niveau de log directement sans avoir à redéployer l'application est parfois indispensable sur des plateformes critiques où il est impossible d'interrompre le service. C'est aussi un moyen de déboguer directement sur une plateforme qui est la seule à présenter un bug non reproductible ailleurs.
Il existe plusieurs solutions dont :
- relecture de la configuration Log4j à intervalle régulier; il suffit alors de modifier la configuration des niveaux de log directement (soit avec l'API Log4j, soit avec les classes utilitaires de Spring Log4jWebConfigurer)
- changement des niveaux de log à l'aide de JMX
C'est cette dernière solution que je préfère, car il est généralement déconseillé de modifier directement des fichiers à chaud dans un serveur d'applications.
Il faut d'abord créer une classe avec une méthode permettant de modifier le niveau de log de n'importe quel package :
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;
@ManagedResource(description="Log4j debug level modifier")
public class Log4jLevelChanger {
@ManagedOperation(description = "Change log level of a package")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "loggerName", description = "package name"),
@ManagedOperationParameter(name = "level", description = "log level (DEBUG, INFO, etc.)")
})
public void setLogLevel(String loggerName, String level) {
if ("debug".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.DEBUG);
} else if ("info".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.INFO);
} else if ("error".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.ERROR);
} else if ("fatal".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.FATAL);
} else if ("warn".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.WARN);
} else if ("trace".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.TRACE);
} else if ("off".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.OFF);
} else if ("all".equalsIgnoreCase(level)) {
Logger.getLogger(loggerName).setLevel(Level.ALL);
}
}
}
On peut remarquer qu'on a déjà préparé le terrain pour Spring avec les annotations @ManagedXXX. Cette classe sera instanciée par Spring comme singleton et exposée via JMX :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- RECUPERATION DU SERVEUR MBEAN DE LA PLATEFORME -->
<bean id="mbeanServer" class="java.lang.management.ManagementFactory" factory-method="getPlatformMBeanServer" />
<!-- bean Log4jLevelChanger en singleton -->
<bean id="log4jLevelChanger" class="fr.openfarm.util.Log4jLevelChanger" />
<!-- utilisation du MBean exporter de Spring pour exposer la méthode setLogLevel dans JMX -->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="beans">
<map>
<entry key="fr.openfarm.util:name=Log4jLevelChanger" value-ref="log4jLevelChanger" />
</map>
</property>
<property name="assembler" ref="metadataAssembler"/>
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
<bean id="metadataAssembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
On indique que les annotations seront utilisées comme source de données de
configuration pour JMX avec le bean jmxAttributeSource. Ensuite,
on indique que la stratégie de nommage des MBeans JMX sera faite à partir des
annotations (bean namingStrategy) et que les metadata du MBean
également utiliseront les annotations (bean metadataAssembler). Si
les metadata ne sont pas utilisés, les noms de paramètres affichés dans la
console JMX seront alors nommés arbitrairement p1 et p2.
Enfin, les deux beans namingStrategy et
metadataAssembler sont passés en attribut de
l'exporter qui sera chargé d'exposer le ou les MBeans dans
JMX.
Voici le résultat dans JVisualVM (ou jconsole) :
