MDC+log4j+uuid简单使用

xiaoxiao2021-02-28  42

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。
转载请注明原文地址: https://www.6miu.com/read-2620971.html

最新回复(0)