Contextualisation de traces Log4J avec le MDC

Un article de JTips.

Log4J propose un mécanisme de contextualisation des traces par le biais de NDC (Nested Diagnostic Context) et du MDC (Mapped Diagnostic Context). Ces 2 mécanismes se basent sur le même principe : on ajoute des informations, via des méthodes statiques ; ces informations étant ratachées au ThreadLocal. Le NDC empile les informations dans un stack, alors que MDC les enregistre sous forme de Map.

Contextualisation de traces Log4J avec le MDC
Auteur : Alexis Hassler
Formation(s) sur le sujet :
N/A
Sewatech.png


Sortie des informations

Les informations stockées dans le NDC peuvent être ajoutées dans des traces de type texte (console, fichier) grâce au caractère de conversion %x. Toutes les informations du NDC sont sorties en même temps. Pour le MDC, la caractère de conversion %X doit être associé à la clé d'une information stockée.

 <param name="ConversionPattern" value="%d %-5p [%c] (%x) %m%n"/>

ou

 <param name="ConversionPattern" value="%d %-5p [%c] (%X{RemoteAddr} - %X{RequestURL}) %m%n"/>


Enregistrement des informations

Dans une application Web, l'endroit le plus approprié pour enregistrer des informations dans le NDC ou le MDC est généralement un filtre. Par exemple, on peut déployer un filtre qui enregistre des informations sur la requête, comme l'URL de la requête, l'adresse IP du client ou le nom utilisé pour l'authentification.

Dans l'exemple ci-dessous, j'ai utilisé un MDC, qui me semble plus pratique et, surtout, plus modulaire. J'y ai enregistré les informations citées ci-dessus, ainsi que des paramètres d'initialisation présents dans le fichier web.xml.

 package fr.sewatech.util.web;
 
 import java.io.IOException;
 import java.util.*;
 import javax.servlet.*;
 import org.apache.commons.beanutils.PropertyUtils;
 import org.apache.commons.logging.*;
 import org.apache.log4j.MDC;
 
 /**
  * Filtre d'initialisation pour Log4J ; ajoute des informations dans le MDC
  * @author alexis
  *
  */
 public class Log4jFilter implements Filter {
 	private static Log logger = LogFactory.getLog(Log4jFilter.class);
 	
 	private FilterConfig config;
 
 	/**
 	 * Initialisation du filtre
 	 */
 	public void init(FilterConfig config) throws ServletException {
 		logger.debug("Création du filtre Log4jFilter");
 		this.config = config;
 	}
 
 	/**
 	 * DEstruction du filtre
 	 */
 	public void destroy() {
 		logger.debug("Destruction du filtre Log4jFilter");
 	}
 
 	/**
 	 * Execution du filtre ; enregistre les informations prevues
 	 */
 	public void doFilter(ServletRequest request, ServletResponse response,
 			FilterChain chain) throws IOException, ServletException {
 		try {
 			// Paramètres d'initialisation du filtre
 			Enumeration<String> initParameterNames = config.getInitParameterNames();
 			while (initParameterNames.hasMoreElements()) {
 				String name = (String) initParameterNames.nextElement();
 				MDC.put(name, config.getInitParameter(name));
 			}
 			
 			putProperty(request, "RemoteAddr");
 			putProperty(request, "Locale");
 			putProperty(request, "PathInfo");
 			putProperty(request, "RequestURL");
 			putProperty(request, "ServletPath");
 			putProperty(request, "UserPrincipal");
 			putProperty(((HttpServletRequest) request).getSession(false), "SessionID");
 
 			chain.doFilter(request, response);
 		} finally {
 			Set<String> propertyNames = MDC.getContext().keySet();
 			for (String name : propertyNames) {
 				//MDC.remove(name);
 			}
 		}
 	}
 
 	/**
 	 * Enregistre une propriete d'un objet dans le MDC ; cet objet peut typiquement etre la requete ou la session
 	 * @param object
 	 * @param propertyName
 	 */
 	private void putProperty(Object object, String propertyName) {
 		try {
 			if (object != null) {
 				String name = propertyName.substring(0,1).toLowerCase() + propertyName.substring(1);
 				MDC.put(propertyName, PropertyUtils.getProperty(object, name));
 			}
 		} catch (Exception e) {
 		}
 	}
 }

Ce filtre doit être paramétré dans le fichier WEB-INF/web.xml de l'application.

 <filter>
   <filter-name>Log4jFilter</filter-name>
   <filter-class>fr.sewatech.util.web.Log4jFilter</filter-class>
   <init-param>
     <param-name>Application</param-name>
     <param-value>Hello</param-value>
   </init-param>
 </filter>
 <filter-mapping>
   <filter-name>Log4jFilter</filter-name>
   <url-pattern>/*</url-pattern>
 </filter-mapping>

Résultat

En associant ce filtre et la configuration du PatternLayout, les traces sortent avec les informations suivantes :

 01:38:48,328 INFO  [fr.sewatech.hello.web.PageFilter] (127.0.0.1 - Hello) Appel de /hi
 01:38:48,328 INFO  [fr.sewatech.hello.web.HelloServlet] (127.0.0.1 - Hello) HelloServlet.doGet()
 01:38:49,187 INFO  [fr.sewatech.hello.web.PageFilter] (127.0.0.1 - Hello) Appel de /hello-count.jsp


Quelques autres articles sur JavaSE
Quelques autres articles sur Log4J