一种轻量级的技术是给每个为客户端服务的 logger 打一个标记。Neil Harrison 在 Patterns for Logging Diagnostic Messages in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 这本书中描述了这种方法。logback 在 SLF4J API 利用了这种技术的变体:诊断上下文映射 (MDC)。
packagechapters.mdc;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.slf4j.MDC;importch.qos.logback.classic.PatternLayout;importch.qos.logback.core.ConsoleAppender;publicclassSimpleMDC {staticpublicvoidmain(String[] args) throwsException {// 你可以选择在任何时候将值放入 MDC 中 MDC.put("first","Dorothy"); [ SNIP ]Logger logger =LoggerFactory.getLogger(SimpleMDC.class);MDC.put("last","Parker");logger.info("Check enclosed.");logger.debug("The most beautiful two words in English.");MDC.put("first","Richard");MDC.put("last","Nixon");logger.info("I am not a crook.");logger.info("Attributed to the former US president. 17 Nov 1973."); } [ SNIP ]}
main 方法在启动的时候在 MDC 中将 Dorothy 关联到 first 上。你可以在 MDC 放置尽可能多的键值对,反正你开心就好。多次插入同一个 key,新值会覆盖旧值。然后代码继续配置 logback。
Dorothy Parker -Check enclosed.Dorothy Parker -The most beautiful two words in English.Richard Nixon -I am not a crook.Richard Nixon -Attributed to the former USpresident. 17 Nov 1973.
packagechapters.mdc;importjava.rmi.RemoteException;importjava.rmi.registry.LocateRegistry;importjava.rmi.registry.Registry;importjava.rmi.server.UnicastRemoteObject;importjava.util.Vector;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.slf4j.MDC;importch.qos.logback.classic.LoggerContext;importch.qos.logback.classic.joran.JoranConfigurator;importch.qos.logback.core.joran.spi.JoranException;/** * A simple NumberCruncher implementation that logs its progress when * factoring numbers. The purpose of the whole exercise is to show the * use of mapped diagnostic contexts in order to distinguish the log * output from different client requests. * */publicclassNumberCruncherServerextendsUnicastRemoteObjectimplementsNumberCruncher {privatestaticfinallong serialVersionUID =1L;staticLogger logger =LoggerFactory.getLogger(NumberCruncherServer.class);publicNumberCruncherServer() throwsRemoteException { }publicint[] factor(int number) throwsRemoteException {// The client's host is an important source of information.try {MDC.put("client",NumberCruncherServer.getClientHost()); } catch (java.rmi.server.ServerNotActiveException e) {logger.warn("Caught unexpected ServerNotActiveException.", e); }// The information contained within the request is another source// of distinctive information. It might reveal the users name,// date of request, request ID etc. In servlet type environments,// useful information is contained in the HttpRequest or in the // HttpSession.MDC.put("number",String.valueOf(number));logger.info("Beginning to factor.");if (number <=0) {thrownewIllegalArgumentException(number +" is not a positive integer."); } elseif (number ==1) {returnnewint[] { 1 }; }Vector<Integer> factors =newVector<Integer>();int n = number;for (int i =2; (i <= n) && ((i * i) <= number); i++) {// It is bad practice to place log requests within tight loops.// It is done here to show interleaved log output from// different requests. logger.debug("Trying "+ i +" as a factor.");if ((n % i) ==0) {logger.info("Found factor "+ i);factors.addElement(newInteger(i));do { n /= i; } while ((n % i) ==0); }// Placing artificial delays in tight loops will also lead to// sub-optimal results. :-)delay(100); }if (n !=1) {logger.info("Found factor "+ n);factors.addElement(newInteger(n)); }int len =factors.size();int[] result =newint[len];for (int i =0; i < len; i++) { result[i] = ((Integer) factors.elementAt(i)).intValue(); }// clean upMDC.remove("client");MDC.remove("number");return result; }staticvoidusage(String msg) {System.err.println(msg);System.err.println("Usage: java chapters.mdc.NumberCruncherServer configFile\n"+" where configFile is a logback configuration file.");System.exit(1); }publicstaticvoiddelay(int millis) {try {Thread.sleep(millis); } catch (InterruptedException e) { } }publicstaticvoidmain(String[] args) {if (args.length!=1) {usage("Wrong number of arguments."); }String configFile = args[0];if (configFile.endsWith(".xml")) {try {LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();JoranConfigurator configurator =newJoranConfigurator();configurator.setContext(lc);lc.reset();configurator.doConfigure(args[0]); } catch (JoranException je) {je.printStackTrace(); } }NumberCruncherServer ncs;try { ncs =newNumberCruncherServer();logger.info("Creating registry.");Registry registry =LocateRegistry.createRegistry(Registry.REGISTRY_PORT);registry.rebind("Factor", ncs);logger.info("NumberCruncherServer bound and ready."); } catch (Exception e) {logger.error("Could not bind NumberCruncherServer.", e);return; } }}
70984 [RMITCPConnection(4)-192.168.1.6] INFO C:orion N:129-Beginning to factor.70984 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 2 as a factor.71093 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 3 as a factor.71093 [RMITCPConnection(4)-192.168.1.6] INFO C:orion N:129-Found factor 371187 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 4 as a factor.71297 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 5 as a factor.71390 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 6 as a factor.71453 [RMITCPConnection(5)-192.168.1.6] INFO C:orion N:71-Beginning to factor.71453 [RMITCPConnection(5)-192.168.1.6] DEBUG C:orion N:71- Trying 2 as a factor.71484 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 7 as a factor.71547 [RMITCPConnection(5)-192.168.1.6] DEBUG C:orion N:71- Trying 3 as a factor.71593 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 8 as a factor.71656 [RMITCPConnection(5)-192.168.1.6] DEBUG C:orion N:71- Trying 4 as a factor.71687 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 9 as a factor.71750 [RMITCPConnection(5)-192.168.1.6] DEBUG C:orion N:71- Trying 5 as a factor.71797 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 10 as a factor.71859 [RMITCPConnection(5)-192.168.1.6] DEBUG C:orion N:71- Trying 6 as a factor.71890 [RMITCPConnection(4)-192.168.1.6] DEBUG C:orion N:129- Trying 11 as a factor.71953 [RMITCPConnection(5)-192.168.1.6] DEBUG C:orion N:71- Trying 7 as a factor.72000 [RMITCPConnection(4)-192.168.1.6] INFO C:orion N:129-Found factor 4372062 [RMITCPConnection(5)-192.168.1.6] DEBUG C:orion N:71- Trying 8 as a factor.72156 [RMITCPConnection(5)-192.168.1.6] INFO C:orion N:71-Found factor 71
从上面的输出可以看出客户端运行在一个叫做 orion 的主机上。尽管服务器在独立的线程中几乎同时处理客户端的请求,但是可以通过 MDC 来区分每个客户端的日志输出。示例中的是通过 number 来进行标识。
packagechapters.mdc;importjava.io.IOException;importjava.security.Principal;importjavax.servlet.Filter;importjavax.servlet.FilterChain;importjavax.servlet.FilterConfig;importjavax.servlet.ServletException;importjavax.servlet.ServletRequest;importjavax.servlet.ServletResponse;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpSession;importorg.slf4j.MDC;publicclassUserServletFilterimplementsFilter {privatefinalString USER_KEY ="username";publicvoiddestroy() { }publicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain chain) throwsIOException,ServletException {boolean successfulRegistration =false;HttpServletRequest req = (HttpServletRequest) request; Principal principal =req.getUserPrincipal();// Please note that we could have also used a cookie to // retrieve the user nameif (principal !=null) {String username =principal.getName(); successfulRegistration =registerUsername(username); } try {chain.doFilter(request, response); } finally {if (successfulRegistration) {MDC.remove(USER_KEY); } } }publicvoidinit(FilterConfig arg0) throwsServletException { } /** * Register the user in the MDC under USER_KEY. * * @param username * @return true id the user can be successfully registered */privatebooleanregisterUsername(String username) {if (username !=null&&username.trim().length() >0) {MDC.put(USER_KEY, username);returntrue; }returnfalse; }}