Contextualisation de traces Log4J avec le MDC

Cette page a été rédigée il y a fort fort longtemps, et n'a pas tellement été mise à jour.

 

Vous savez, moi je ne crois pas qu'il y ait de bonne ou de mauvaise page. Moi, si je devais résumer mon wiki aujourd'hui avec vous, je dirais que c'est d'abord des rencontres. Des gens qui m'ont tendu la main, peut-être à un moment où je ne pouvais pas, où j'étais seul chez moi. Et c'est assez curieux de se dire que les hasards, les rencontres forgent une destinée... Parce que quand on a le goût de la chose, quand on a le goût de la chose bien faite, le beau geste, parfois on ne trouve pas l'interlocuteur en face je dirais, le miroir qui vous aide à avancer. Alors ça n'est pas mon cas, comme je disais là, puisque moi au contraire, j'ai pu ; et je dis merci au wiki, je lui dis merci, je chante le wiki, je danse le wiki... je ne suis qu'amour ! Et finalement, quand des gens me disent « Mais comment fais-tu pour avoir cette humanité ? », je leur réponds très simplement que c'est ce goût de l'amour, ce goût donc qui m'a poussé aujourd'hui à entreprendre une construction logicielle... mais demain qui sait ? Peut-être simplement à me mettre au service de la communauté, à faire le don, le don de soi.

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.

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