MDC+log4j+uuid简单使用

MDC介绍
  MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。某些应用程序采用多线程的方式来处理多个用户的请求。在一个用户的使用过程中,可能有多个不同的线程来进行处理。典型的例子是 Web 应用服务器。当用户访问某个页面时,应用服务器可能会创建一个新的线程来处理该请求,也可能从线程池中复用已有的线程。在一个用户的会话存续期间,可能有多个线程处理过该用户的请求。这使得比较难以区分不同用户所对应的日志。当需要追踪某个用户在系统中的相关日志记录时,就会变得很麻烦。
  一种解决的办法是采用自定义的日志格式,把用户的信息采用某种方式编码在日志记录中。这种方式的问题在于要求在每个使用日志记录器的类中,都可以访问到用户相关的信息。这样才可能在记录日志时使用。这样的条件通常是比较难以满足的。MDC 的作用是解决这个问题。
  MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
maven:

<!-- log start -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>

log配置,添加额外输出项uuid:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <layout class="com.sf.sgs.sflog.log4j.layout.SfPatternLayout">
                         <param name="ConversionPattern" 
                         value="%X{uuid}-%m%n" />  
        </layout>
    </appender>


    <!--针对,request以及reponse的信息配置输出级别,生产线请配置为error-->
    <logger name="com.alibaba.dubbo.rpc.protocol.rest.support" additivity="true">
        <level value="info"/>
        <appender-ref ref="console" />
    </logger>

    <!-- 在开发和测试环境启用,输出sql -->
    <logger name="com.sf.sgs.smp.dao.mapper" additivity="true">
        <level value="DEBUG"/>
        <appender-ref ref="console" />
    </logger>

    <root>
        <priority value="info" />
        <!--开发过程可以开启  <appender-ref ref="console" />,生产要求屏蔽-->
        <appender-ref ref="console" />
        <appender-ref ref="SF.SYSTEM" />
        <appender-ref ref="SF.ERROR" />
        <appender-ref ref="SF.AUDIT" />
    </root>

</log4j:configuration>

自定义log类,加载配置好的log类

public class TraceLogger {  
    //此处的"tranceLog"为log4j中定义的对应的 logger的name  
    private static final Logger TRACE_LOGGER = LoggerFactory.getLogger("com.alibaba.dubbo.rpc.protocol.rest.support");  

    private TraceLogger() {  

    }  

    public static void info(String message){  
        TRACE_LOGGER.info(message);  
    }  

    public static void info(String format,Object... arguments){  
        TRACE_LOGGER.info(format, arguments);  
    }  
    public static void debug(String message){  
        TRACE_LOGGER.debug(message);  
    }  

    public static void debug(String format,Object... arguments){  
        TRACE_LOGGER.debug(format, arguments);  
    }  
}  

在方法中用MDC写入生成的uuid,输出时便可以带uuid输出

    //@Around("execution(* com.sf.dds.znfj.container.service.impl.*.*.*(..))||execution(* com.sf.dds.znfj.task.service.impl.*.*.*(..))")
    @Around("execution(* com.sf.dds.znfj.unmannedStore.service.impl.*.*.*(..))")
    public Result<?> handleLog(ProceedingJoinPoint pjp) throws Throwable {
        String name = pjp.getSignature().getName();
        long startTime = System.currentTimeMillis();
        MDC.clear();
        MDC.put("uuid", StringUtil.getUUID());
        TraceLogger.info("\n{}方法开始执行...", name);
        //logger.info("{}方法开始执行...", name);
        Object[] args = pjp.getArgs();
        for (Object obj : args) {
            //logger.info("{}方法请求参数request:\n{\n{}\n}", name, obj);
            TraceLogger.info("\n{}方法请求参数request:\n{\n{}\n}", name, obj);    
        }
        Result<?> result = new Result<>();
        try {           
            result = (Result<?>) pjp.proceed();
        } catch (ZnfjRuntimeException e1) {
            if (e1.getResult().getObj()!=null) {
                ExceptionSetUtil.setErrorCodeAndMessage(e1.getResult(), e1.getCode(), e1.getMessage(), logger, e1);
                return e1.getResult();
            }
            else {
                ExceptionSetUtil.setErrorCodeAndMessage(result, e1.getCode(), e1.getMessage(), logger, e1);
            }

            //logger.error("error",e1);
            return result;
        }catch (Exception e) {
            // TODO: handle exception
            ExceptionSetUtil.setErrorCodeAndMessage(result, 
                                                    ErrorCodeConstants.TdopCommonConstants.SYSTEM_ERROR_CODE,
                                                    ErrorCodeConstants.TdopCommonConstants.SYSTEM_ERROR_MESSAGE, 
                                                    logger,
                                                    e);
            //logger.error("error",e);
            return result;
        }

        long endTime = System.currentTimeMillis();
        float time = (endTime - startTime) / 1000.0f;
        TraceLogger.info("\n{}方法执行结束,耗时{}s", name, time);
        TraceLogger.debug(
                "\n{}方法返回结果response:\n{\n\"requestId\":{},\n\"success\":{},\n\"business\":{},\n\"errorCode\":{},\n\"errorMessage\":{},\n\"date\":{},\n\"version\":{},\n\"obj\":{}\n}",
                name, result.getRequestId(), result.isSuccess(), result.getBusiness(), result.getErrorCode(),
                result.getErrorMessage(), result.getDate(), result.getVersion(), result.getObj());
        return result;
    }

这样,每个方法每次访问便可以用唯一的索引搜索到,方便穿插日志定位请求

97e65d8d11d146f8abe9ccd0391ce764-
exceptionUpload方法请求参数request:

f7ae98275f144e9ba3ebeea9b374e734-
destQuery方法请求参数request:

UUID生产类

    /** 
    * 获得指定数目的UUID 
    * @param number int 需要获得的UUID数量 
    * @return String[] UUID数组 
    */ 
    public static ArrayList<String> getUUID(int number){
        ArrayList<String> list=new ArrayList<>();
    if(number < 1){ 
    return null; 
    } 
    for(int i=0;i<number;i++){ 
    list.add(getUUID()); 
    } 
    return list; 
    }

    /** 
    * 获得一个UUID 
    * @return String UUID 
    */ 
    public static String getUUID(){ 
    String uuid = UUID.randomUUID().toString(); 
    //去掉“-”符号 
    return uuid.replaceAll("-", "");
    }

1.UUID 简介

 UUID含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准,也是被开源软件基金会 (Open Software Foundation, OSF) 

的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。

 UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的 UUID。

在这样的情况下,就不需考虑数据库建立时的名称重复问题。目前最广泛应用的 UUID,即是微软的 Microsoft’s Globally Unique Identifiers (GUIDs),而其他重要的应用,
则有 Linux ext2/ext3 档案系统、LUKS 加密分割区、GNOME、KDE、Mac OS X 等等

2.UUID 组成

UUID保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字

UUID由以下几部分的组合:
(1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
(2)时钟序列。
(3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。在ColdFusion中可以用CreateUUID()函数很简单地生成UUID,
其格式为:xxxxxxxx-xxxx- xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。而标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12);

log4j:

图示 1. Logger的层次结构图
这里写图片描述

og4j有三个主要的组件:Logger、Appender和Layout。这三个组件相互配合使得我们可以获得非常强大的日志记录的能力。

**Logger**

Logger的名称是区分大小写的,依据名称可以确定其层次结构(即父子关系),规则如下:

如果Logger A的名称后跟一个点(.)是Logger B的名称的前缀就认为Logger A是LoggerB的祖先。
如果在Logger A和Logger B之间,Logger B没有任何其它的祖先就认为Logger A是LoggerB的父亲。
在Logger的层次结构的最顶层是root logger,它会永远存在,而且不能通过名字取到。

上面文字的描述可能不好的理解,为此我们给出了一张图,Logger的层次结构图,从中可以非常直观的看出三种主要组件的关系和各自所起的作用。



 Log4j的设计原理和使用方法

Loger x.y是Logger x.y.z的祖先,因为x.y.是x.y.z的前缀,这符合规则的前一条。另外在Loggerx.y和Logger x.y.z之间,Logger x.y.z没有其它的祖先,因此Logger x.y是Loggerx.y.z的父亲,这符合规则的后一条。这样我们依据上面的规则就可以构造出如图1所示的Logger的层次结构。

从图1中我们还可以看到每一个Logger都有一个Level,根据该Level的值Logger决定是否处理对应的日志请求。如果Level没有被设置,就象图1中的Loggerx.y一样,又该怎么办呢?答案是可以从祖先那里继承。

如果Logger C没有被设置Level,那么它将沿着它的层次结构向上查找,如果找到就继承并结束,否则会一直查找到rootlogger结束。因为log4j在设计时保证rootlogger会被设置一个默认的Level,所以任何logger都可以继承到Level。

图1中的Logger x.y没有被设置Level,但是根据上面的继承规则,Logger x.y继承了rootlogger的Level。

我们在来看看Logger选择日志记录请求(log request)的规则:

假设Logger M具有q级的Level,这个Level可能是设置的也可能是继承到的。

如果向LoggerM发出一个Level为p的日志记录请求,那么只有满足p>=q时这个日志记录请求才会被处理。

org.apache.log4j.Logger中的不同方法发出不同Level的日志记录请求,如下:

public void debug(Object message),发出Level为DEBUG的日志记录请求
public void info(Object message),发出Level为INFO的日志记录请求
public void warn(Object message),发出Level为WARN的日志记录请求
public void error(Object message),发出Level为ERROR日志记录请求
public void fatal(Object message),发出Level为FATAL的日志请求
public void log(Level l, Object message),发出指定Level的日志记录请求
其中的静态常量DEBUG、INFO、WARN、ERROR、FATAL是在org.apache.log4j.Level中定义的,除了使用这些预定义的Level之外,Log4j还支持自定义Level。

注:org.apache.log4j.Level中还预定义了一些其它的Level。

Loggers节点,常见的有两种:Root和Logger.
Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出
level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender.

Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。
level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.
AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。


**Appender**

在Log4j中,Appender指的是日志记录输出的目的地。当前支持的Appender(目的地)有文件(file)、控制台(console)、java.io.OutputStream、java.io.Writer、远程服务器、远程UnixSyslog守护者、远程JMS监听者、NTEventLog或者发送e-mail。如果您在上面没有找到适合的Appender,那就需要考虑实现自己的自定义Appender了。

每个Logger可以有多个Appender,但是相同的Appender只会被添加一次。

**Appender的附加性意味着Logger C会将日志记录发给它的和它祖先的所有Appender。在图1中Loggera会将日志记录发给它自己的JDBCAppender和它的祖先rootlogger的ConsoleAppender和FileAppender。Loggerx.y.z自己没有Appender,它将把日志记录发给它的祖先rootlogger的ConsoleAppender和FileAppender,如果Loggerx.y也含有Appender,那么它们也会包括在内。**

上述配置,如果不指定logger,那么uuid将在root下所有ref中输出

Appender的附加性是可以被中断的。假设Logger C的一个祖先为Logger P,如果LoggerP的附加性标志(additivity flag)设置为假,那么Logger C会将日志记录只发给它的和在它和LoggerP之间的祖先(包括Logger P)的Appender,而不会发给LoggerP的祖先的Appender。Logger的附加性标志(additivity flag)默认值为ture。

在图1中如果没有设置Logger a的附加性标志(additivity flag),而是使用默认值true,那么Loggera会将日志记录发给它自己的JDBCAppender和它祖先rootlogger的ConsoleAppender和FileAppender,这和上面的描述相同。如果设置Loggera的附加性标志(additivity flag)的值false,那么Loggera会将日志记录发给它自己的JDBCAppender而不会在发给它祖先rootlogger的ConsoleAppender和FileAppender了。

**Layout**

Appender定制了输出目的地,通常我们还需要定制日志记录的输出格式,在Log4j中是通过将Layout和Appender关联到一起来实现的。Layout依据用户的要求来格式化日志记录。PatternLayout(标准Log4j组件)让用户依据类似于C语言printf函数的转换模式来指定输出格式。

例如,转换模式(conversion pattern)为"%r [%t] %-5p %c -%m%n"的PatternLayout将生成类似于以下内容的输出:

176 [main] INFO  org.foo.Bar - Located nearest gas station.   
在上面的输出中:

第一个字段表示自程序开始到发出日志记录请求时所消耗的毫秒数
第二个字段表示发出日志记录请求的线程
第三个字段表示日志记录请求的Level
第四个字段表示发出日志记录请求的Logger的名称
第五个字段(-后的文本)表示日志记录请求的消息
Log4j中还提到了一些其它的Layout,包括HTMLLayout、SimpleLayout、XMLLayout、TTCCLayout和DateLayout。如果这些不能满足您的要求,还可以自定义自己的Layout。

猜你喜欢

转载自blog.csdn.net/qq_31443653/article/details/79572607