Mapped Diagnostic Context (MDC)
来源:互联网 发布:丽水领导干部网络学校 编辑:程序博客网 时间:2024/05/15 17:39
logback的其中一设计目的就是方便监控和调试复杂的分布式应用。在现实生活中,分布式应用需要同时处理很多客户端请求。一种典型的多线程实现,不同的线程处理不同的客户端。为了区分不同客户端产生的日志输出,一种可行但不推荐的方法是为每一个客户端生成一个logger实例。这种做法会随着客户端的增多可能导致资源殆尽。
一种更机智的技巧是为每一个客户端独立标记 log request(Looging Diagnostic Message)。Logback 利用了这种技术的一种变体叫 Mapped Diagnostic Contexts (MDC)。
为了达到独立标记每一个requst,使用者会将 上下文的一些信息放到 MDC,下面我们看看MDC这个类一些显著的方法,也可以通过MDC javadocs查看所有方法
package org.slf4j;
public class MDC {
//Put a context value as identified by key
//into the current thread's context map.
public static void put(String key, String val);
//Get the context identified by the
public static String get(String key);
//Remove the context identified by the
public static void remove(String key);
//Clear all entries in the MDC.
public static void clear();
}
public class MDC {
//Put a context value as identified by key
//into the current thread's context map.
public static void put(String key, String val);
//Get the context identified by the
key
parameter.public static String get(String key);
//Remove the context identified by the
key
parameter.public static void remove(String key);
//Clear all entries in the MDC.
public static void clear();
}
可以看到MDC类中只包含 静态 方法。这让开发人员可以在其他一些logback 组件中重新引用MDC中的信息。 MDC 是线程独立的。不过子线程会自动继承父线程的MDC。典型的应用:在开始处理客户端请求的时候,开发人员可以将一些相关的上下文信息插入到mdc中,例如:客户端ip地址,请求参数等。
下面看个简单的例子:
package chapters.mdc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.core.ConsoleAppender;
public class SimpleMDC {
static public void main(String[] args) throws Exception {
// You can put values in the MDC at any time. Before anything else
// we put the first name
MDC.put("first", "Dorothy");
Logger logger = LoggerFactory.getLogger(SimpleMDC.class);
// We now put the last name
MDC.put("last", "Parker");
// The most beautiful two words in the English language according
// to Dorothy 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.");
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.core.ConsoleAppender;
public class SimpleMDC {
static public void main(String[] args) throws Exception {
// You can put values in the MDC at any time. Before anything else
// we put the first name
MDC.put("first", "Dorothy");
[ 片段 ]
Logger logger = LoggerFactory.getLogger(SimpleMDC.class);
// We now put the last name
MDC.put("last", "Parker");
// The most beautiful two words in the English language according
// to Dorothy 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.");
}
[ 片段 ]
}
配置:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout>
<Pattern>%X{first} %X{last} - %m%n</Pattern>
</layout>
</appender>
<layout>
<Pattern>%X{first} %X{last} - %m%n</Pattern>
</layout>
</appender>
输出结果:
Dorothy Parker - Check enclosed.
Dorothy Parker - The most beautiful two words in English.
Richard Nixon - I am not a crook.
Dorothy Parker - The most beautiful two words in English.
Richard Nixon - I am not a crook.
Richard Nixon - Attributed to the former US president. 17 Nov 1973.
可以看出,向MDC中插入重复的key值,则旧的value会被覆盖。
Advanced Use
即使,MDC中的方法都是静态的,但是MDC是由线程独立管理的,MDC中的put(), get()仅仅影响当前的线程,以及它的子线程。因此开发人员在编码时不用担心线程安全以及同步的问题。
下面的例子中,采用了远程方法调用的方式(RMI),客户端会调用服务端的 factor()方法。例子很长,不必在意其中的算法
package chapters.mdc;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.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.
* */
public class NumberCruncherServer extends UnicastRemoteObject
implements NumberCruncher {
private static final long serialVersionUID = 1L;
static Logger logger = LoggerFactory.getLogger(NumberCruncherServer.class);
public NumberCruncherServer() throws RemoteException {
}
public int[] factor(int number) throws RemoteException {
// 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) {
throw new IllegalArgumentException(number +
" is not a positive integer.");
} else if (number == 1) {
return new int[] { 1 };
}
Vector<Integer> factors = new Vector<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(new Integer(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(new Integer(n));
}
int len = factors.size();
int[] result = new int[len];
for (int i = 0; i < len; i++) {
result[i] = ((Integer) factors.elementAt(i)).intValue();
}
// clean up
MDC.remove("client");
MDC.remove("number");
return result;
}
static void usage(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);
}
public static void delay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
public static void main(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 = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
configurator.doConfigure(args[0]);
} catch (JoranException je) {
je.printStackTrace();
}
}
NumberCruncherServer ncs;
try {
ncs = new NumberCruncherServer();
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;
}
}
}
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.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.
* */
public class NumberCruncherServer extends UnicastRemoteObject
implements NumberCruncher {
private static final long serialVersionUID = 1L;
static Logger logger = LoggerFactory.getLogger(NumberCruncherServer.class);
public NumberCruncherServer() throws RemoteException {
}
public int[] factor(int number) throws RemoteException {
// 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) {
throw new IllegalArgumentException(number +
" is not a positive integer.");
} else if (number == 1) {
return new int[] { 1 };
}
Vector<Integer> factors = new Vector<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(new Integer(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(new Integer(n));
}
int len = factors.size();
int[] result = new int[len];
for (int i = 0; i < len; i++) {
result[i] = ((Integer) factors.elementAt(i)).intValue();
}
// clean up
MDC.remove("client");
MDC.remove("number");
return result;
}
static void usage(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);
}
public static void delay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
public static void main(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 = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
configurator.doConfigure(args[0]);
} catch (JoranException je) {
je.printStackTrace();
}
}
NumberCruncherServer ncs;
try {
ncs = new NumberCruncherServer();
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;
}
}
}
配置:
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout>
<Pattern>%-4r [%thread] %-5level C:%X{client} N:%X{number} - %msg%n</Pattern>
</layout>
</appender>
<root level="debug">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout>
<Pattern>%-4r [%thread] %-5level C:%X{client} N:%X{number} - %msg%n</Pattern>
</layout>
</appender>
<root level="debug">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
样例输出:
70984 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Beginning to factor.
70984 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 2 as a factor.
71093 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 3 as a factor.
71093 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Found factor 3
71187 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 4 as a factor.
71297 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 5 as a factor.
71390 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 6 as a factor.
71453 [RMI TCP Connection(5)-192.168.1.6] INFO C:orion N:71 - Beginning to factor.
71453 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 2 as a factor.
71484 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 7 as a factor.
71547 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 3 as a factor.
71593 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 8 as a factor.
71656 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 4 as a factor.
71687 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 9 as a factor.
71750 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 5 as a factor.
71797 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 10 as a factor.
71859 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 6 as a factor.
71890 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 11 as a factor.
71953 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 7 as a factor.
72000 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Found factor 43
72062 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 8 as a factor.
70984 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 2 as a factor.
71093 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 3 as a factor.
71093 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Found factor 3
71187 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 4 as a factor.
71297 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 5 as a factor.
71390 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 6 as a factor.
71453 [RMI TCP Connection(5)-192.168.1.6] INFO C:orion N:71 - Beginning to factor.
71453 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 2 as a factor.
71484 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 7 as a factor.
71547 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 3 as a factor.
71593 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 8 as a factor.
71656 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 4 as a factor.
71687 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 9 as a factor.
71750 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 5 as a factor.
71797 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 10 as a factor.
71859 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 6 as a factor.
71890 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 11 as a factor.
71953 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 7 as a factor.
72000 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Found factor 43
72062 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 8 as a factor.
72156 [RMI TCP Connection(5)-192.168.1.6] INFO C:orion N:71 - Found factor 71
在这个例子中,它开始先将client 的 hostname put进了mdc中,key为client,然后把number的值也put进了mdc中,key为number。经过一系列计算后,将结果返回给客户端,在返回客户端前,将mdc中刚才的键值对remove掉。这样做是为了防止保留过期的数据,如果采用了线程池的话,这个过期的数据可能会被重用,导致不必要的错误。所以我们推荐无论何时,都应该保证put 跟 remove应该成对出现,并保证remove一定会被执行。
从样例的输出我们也印证了刚才的理论,MDC是基于线程独享实现的。
Automating access to the
MDC
正如我们所见,MDC在处理多客户端的情况非常有效。为了允许MDC中的信息在任何时候都是正确有效的,我们需要在request被处理之前,就讲相关信息放入mdc,再在处理完后,remove掉。而这里我们就可以通过Servlet Filter实现。
下面看个简单的自己实现的例子:
package chapters.mdc;
import java.io.IOException;
import java.security.Principal;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.slf4j.MDC;
public class UserServletFilter implements Filter {
private final String USER_KEY = "username";
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, 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 name
if (principal != null) {
String username = principal.getName();
successfulRegistration = registerUsername(username);
}
try {
chain.doFilter(request, response);
} finally {
if (successfulRegistration) {
MDC.remove(USER_KEY);
}
}
}
public void init(FilterConfig arg0) throws ServletException {
}
/**
* Register the user in the MDC under USER_KEY.
*
* @param username
* @return true id the user can be successfully registered
*/
private boolean registerUsername(String username) {
if (username != null && username.trim().length() > 0) {
MDC.put(USER_KEY, username);
return true;
}
return false;
}
}
import java.io.IOException;
import java.security.Principal;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.slf4j.MDC;
public class UserServletFilter implements Filter {
private final String USER_KEY = "username";
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, 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 name
if (principal != null) {
String username = principal.getName();
successfulRegistration = registerUsername(username);
}
try {
chain.doFilter(request, response);
} finally {
if (successfulRegistration) {
MDC.remove(USER_KEY);
}
}
}
public void init(FilterConfig arg0) throws ServletException {
}
/**
* Register the user in the MDC under USER_KEY.
*
* @param username
* @return true id the user can be successfully registered
*/
private boolean registerUsername(String username) {
if (username != null && username.trim().length() > 0) {
MDC.put(USER_KEY, username);
return true;
}
return false;
}
}
logback也提供了一个
MDCInsertingServletFilter ,将以下信息放入到了MDC中
req.remoteHost
as returned by the getRemoteHost() methodreq.xForwardedFor
value of the "X-Forwarded-For" headerreq.method
as returned by getMethod() methodreq.requestURI
as returned by getRequestURI() methodreq.requestURL
as returned by getRequestURL() methodreq.queryString
as returned by getQueryString() methodreq.userAgent
value of the "User-Agent" header使用配置:需要保证filter在需要使用的到该MDC的其他filter之前。
<filter>
<filter-name>MDCInsertingServletFilter</filter-name>
<filter-class>
ch.qos.logback.classic.helpers.MDCInsertingServletFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>MDCInsertingServletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-name>MDCInsertingServletFilter</filter-name>
<filter-class>
ch.qos.logback.classic.helpers.MDCInsertingServletFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>MDCInsertingServletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
0 0
- Mapped Diagnostic Context (MDC)
- MDC 日志记录-Effective Logging in Java/JEE – Mapped Diagnostic Context
- logback MDC(Mapped Diagnostic Context)与分布式系统的跟踪系统
- MDC
- activiti学习笔记9--Mapped Diagnostic Contexts--20170412
- 日志mdc
- logback mdc
- AUTOSAR Diagnostic
- clang diagnostic
- There is no Action mapped for namespace [/] and action name [hello] associated with context path [/
- There is no Action mapped for namespace [] and action name [] associated with context path []
- There is no Action mapped for namespace [/] and action name [upload/] associated with context path
- There is no Action mapped for namespace [/] and action name [Login] associated with context path [/e
- There is no Action mapped for namespace [/] and action name [] associated with context path [/struts
- There is no Action mapped for namespace [/] and action name [viewLogAction] associated with context
- There is no Action mapped for namespace [/] and action name [login] associated with context path [/M
- There is no Action mapped for namespace [/] and action name [] associated with context path [/dxm__s
- There is no Action mapped for namespace [/] and action name [hello2] associated with context path [/
- xml签名
- [负载均衡]tengine和nginx
- 220. Contains Duplicate III
- Nginx系统学习篇(2)--基础概念IO
- 加和求不同的组合方式数目(dp)
- Mapped Diagnostic Context (MDC)
- Java设计模式--责任链模式
- HDU-5780 gcd
- Leetcode 35. Search Insert Position (Medium) (cpp)
- HDU 2509 Be the Winner && HDU 1907 John (Nim博弈变形)
- unity优化杂谈1
- 矢量控制——SVPWM
- [编程题]括号匹配问题
- poj-4118-OpenJ_Bailian-开餐馆【贪心】【背包】