好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

java 如何实现日志追踪MDC

java 日志追踪MDC

MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我们就可以追踪各种线上问题。

但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。

因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。其目的是为了便于我们诊断线上问题而出现的方法工具类。

虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。 MDC

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

package org.slf4j;

import java.io.Closeable;

import java.util.Map;

import org.slf4j.helpers.NOPMDCAdapter;

import org.slf4j.helpers.BasicMDCAdapter;

import org.slf4j.helpers.Util;

import org.slf4j.impl.StaticMDCBinder;

import org.slf4j.spi.MDCAdapter;

public class MDC {

     static final String NULL_MDCA_URL = "http://HdhCmsTestslf4j.org/codes.html#null_MDCA" ;

     static final String NO_STATIC_MDC_BINDER_URL = "http://HdhCmsTestslf4j.org/codes.html#no_static_mdc_binder" ;

     static MDCAdapter mdcAdapter;

 

     public static class MDCCloseable implements Closeable {

         private final String key;

         private MDCCloseable(String key) {

             this .key = key;

         }

         public void close() {

             MDC.remove( this .key);

         }

     }

     private MDC() {

     }

 

     static {

         try {

             mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA();

         } catch (NoClassDefFoundError ncde) {

             mdcAdapter = new NOPMDCAdapter();

             String msg = ncde.getMessage();

             if (msg != null && msg.indexOf( "StaticMDCBinder" ) != - 1 ) {

                 Util.report( "Failed to load class \"org.slf4j.impl.StaticMDCBinder\"." );

                 Util.report( "Defaulting to no-operation MDCAdapter implementation." );

                 Util.report( "See " + NO_STATIC_MDC_BINDER_URL + " for further details." );

             } else {

                 throw ncde;

             }

         } catch (Exception e) {

             // we should never get here

             Util.report( "MDC binding unsuccessful." , e);

         }

     }

 

     public static void put(String key, String val) throws IllegalArgumentException {

         if (key == null ) {

             throw new IllegalArgumentException( "key parameter cannot be null" );

         }

         if (mdcAdapter == null ) {

             throw new IllegalStateException( "MDCAdapter cannot be null. See also " + NULL_MDCA_URL);

         }

         mdcAdapter.put(key, val);

     }

 

     public static MDCCloseable putCloseable(String key, String val) throws IllegalArgumentException {

         put(key, val);

         return new MDCCloseable(key);

     }

 

     public static String get(String key) throws IllegalArgumentException {

         if (key == null ) {

             throw new IllegalArgumentException( "key parameter cannot be null" );

         }

 

         if (mdcAdapter == null ) {

             throw new IllegalStateException( "MDCAdapter cannot be null. See also " + NULL_MDCA_URL);

         }

         return mdcAdapter.get(key);

     }

 

     public static void remove(String key) throws IllegalArgumentException {

         if (key == null ) {

             throw new IllegalArgumentException( "key parameter cannot be null" );

         }

 

         if (mdcAdapter == null ) {

             throw new IllegalStateException( "MDCAdapter cannot be null. See also " + NULL_MDCA_URL);

         }

         mdcAdapter.remove(key);

     }

 

     public static void clear() {

         if (mdcAdapter == null ) {

             throw new IllegalStateException( "MDCAdapter cannot be null. See also " + NULL_MDCA_URL);

         }

         mdcAdapter.clear();

     }

 

     public static Map<String, String> getCopyOfContextMap() {

         if (mdcAdapter == null ) {

             throw new IllegalStateException( "MDCAdapter cannot be null. See also " + NULL_MDCA_URL);

         }

         return mdcAdapter.getCopyOfContextMap();

     }

 

     public static void setContextMap(Map<String, String> contextMap) {

         if (mdcAdapter == null ) {

             throw new IllegalStateException( "MDCAdapter cannot be null. See also " + NULL_MDCA_URL);

         }

         mdcAdapter.setContextMap(contextMap);

     }

     public static MDCAdapter getMDCAdapter() {

         return mdcAdapter;

     }

}

简单的demo

?

1

2

3

4

5

6

7

8

9

10

11

package com.alibaba.otter.canal测试数据mon;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.slf4j.MDC;

public class LogTest {

     private static final Logger logger = LoggerFactory.getLogger(LogTest. class );

     public static void main(String[] args) {

         MDC.put( "THREAD_ID" , String.valueOf(Thread.currentThread().getId()));

         logger.info( "纯字符串信息的info级别日志" );

     }

}

logback.xml 配置

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

< configuration scan = "true" scanPeriod = " 5 seconds" >

 

     < jmxConfigurator />

     < appender name = "STDOUT" class = "ch.qos.logback.core.ConsoleAppender" >

         < encoder >

             < pattern >%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} %X{THREAD_ID} - %msg%n

             </ pattern >

         </ encoder >

     </ appender >

    

     < root level = "INFO" >

         < appender-ref ref = "STDOUT" />

     </ root >

</configuration

对应的输出日志 可以看到输出了THREAD_ID

2016-12-08 14:59:32.855 [main] INFO com.alibaba.otter.canal测试数据mon.LogTest THREAD_ID 1 - 纯字符串信息的info级别日志

slf4j只是起到适配的作用 故查看实现类LogbackMDCAdapter属性

final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();

InheritableThreadLocal 该类扩展了 ThreadLocal,为子线程提供从父线程那里继承的值:在创建子线程时,子线程会接收所有

可继承的线程局部变量的初始值,以获得父线程所具有的值。通常,子线程的值与父线程的值是一致的;但是,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数。

当必须将变量(如用户 ID 和 事务 ID)中维护的每线程属性(per-thread-attribute)自动传送给创建的所有子线程时,应尽可能地采用可继承的线程局部变量,而不是采用普通的线程局部变量

验证一下

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

package com.alibaba.otter.canal.parse.driver.mysql;

import org.junit.Test;

public class TestInheritableThreadLocal {

     @Test

     public void testThreadLocal() {

         final ThreadLocal<String> local = new ThreadLocal<String>(); 

         work(local); 

     }

 

     @Test

     public void testInheritableThreadLocal() {

         final ThreadLocal<String> local = new InheritableThreadLocal<String>(); 

         work(local);

     }

     private void work( final ThreadLocal<String> local) { 

         local.set( "a" ); 

         System.out.println(Thread.currentThread() + "," + local.get()); 

         Thread t = new Thread( new Runnable() { 

              

             @Override

             public void run() { 

                 System.out.println(Thread.currentThread() + "," + local.get()); 

                 local.set( "b" ); 

                 System.out.println(Thread.currentThread() + "," + local.get()); 

             } 

         }); 

          

         t.start(); 

         try { 

             t.join(); 

         } catch (InterruptedException e) { 

             e.printStackTrace(); 

         }           

         System.out.println(Thread.currentThread() + "," + local.get()); 

     } 

}

分别运行得到的输出结果

ThreadLocal 存贮输出结果
Thread[main,5,main],a
Thread[Thread-0,5,main],null
Thread[Thread-0,5,main],b
Thread[main,5,main],a
InheritableThreadLocal存贮输出结果
Thread[main,5,main],a
Thread[Thread-0,5,main],a
Thread[Thread-0,5,main],b
Thread[main,5,main],a

输出结果说明一切 对于参数传递十分有用 我知道 canal的源码中用到了MDC

在 CanalServerWithEmbedded 中的 start 和stop等方法中都有用到

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

public void start( final String destination) {

     final CanalInstance canalInstance = canalInstances.get(destination);

     if (!canalInstance.isStart()) {

         try {

             MDC.put( "destination" , destination);

             canalInstance.start();

             logger.info( "start CanalInstances[{}] successfully" , destination);

         } finally {

             MDC.remove( "destination" );

         }

     }

}

 

public void stop(String destination) {

     CanalInstance canalInstance = canalInstances.remove(destination);

     if (canalInstance != null ) {

         if (canalInstance.isStart()) {

             try {

                 MDC.put( "destination" , destination);

                 canalInstance.stop();

                 logger.info( "stop CanalInstances[{}] successfully" , destination);

             } finally {

                 MDC.remove( "destination" );

             }

         }

     }

}

MDC的介绍及使用

1、MDC是什么?

MDC是(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 支持的一种方便在多线程条件下记录追踪日志的功能。通常打印出的日志会有线程号等信息来标志当前日志属于哪个线程,然而由于线程是可以重复使用的,所以并不能很清晰的确认一个请求的日志范围。处理这种情况一般有两种处理方式:

1)手动生成一个唯一序列号打印在日志中;

2)使用日志控件提供的MDC功能,生成一个唯一序列标记一个线程的日志;

两种方法的区别在于:

方法一只能标记一条日志,线程内其他日志需要人肉去筛选;

方法二标记整个线程的所有日志,方便grep命令查询;

对比可见,使用MDC功能更好。

2、MDC的原理

MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

@RunWith (SpringRunner. class )

@SpringBootTest (classes=CreditAppApplication. class )

publicclassMDCTest{

 

@Test

publicvoidmdcTest1(){

MDC.put( "first" , "thefirst1" );

 

Loggerlogger=LoggerFactory.getLogger(MDCTest. class );

MDC.put( "last" , "thelast1" );

 

logger.info( "checkenclosed." );

logger.debug( "themostbeautifultwowordsinenglish." );

 

MDC.put( "first" , "thefirst2" );

MDC.put( "last" , "thelast2" );

 

logger.info( "iamnotacrook." );

logger.info( "AttributedtotheformerUSpresident.17Nov1973." );

}

}

logback的配置:

3、MDC的使用

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

@Component

@Order (Ordered.HIGHEST_PRECEDENCE)

publicclassGlobalLogTagConfigextendsOncePerRequestFilter{

privatestaticfinalStringGLOBAL_LOG_TAG= "GLOG_TAG" ;

privatestaticStringgenerateSeqNo(){

returnUUID.randomUUID().toString().replace( "-" , "" ).substring( 0 , 12 );

}

 

@Override

protectedvoiddoFilterInternal(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,FilterChainfilterChain)throwsServletException,IOException{

try {

StringseqNo;

if (httpServletRequest!= null ){

seqNo=httpServletRequest.getHeader(GLOBAL_LOG_TAG);

 

if (StringUtils.isEmpty(seqNo)){

seqNo=generateSeqNo();

}

} else {

seqNo=generateSeqNo();

}

MDC.put(GLOBAL_LOG_TAG,seqNo);

filterChain.doFilter(httpServletRequest,httpServletResponse);

} finally {

MDC.remove(GLOBAL_LOG_TAG);

}

}

}

注意:

OncePerRequestFilter的作用是为了让每个请求只经过这个过滤器一次(因为web container的不同,有些过滤器可能被多次执行)

logback配置:

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://HdhCmsTestjianshu测试数据/p/06b1d35526c2

查看更多关于java 如何实现日志追踪MDC的详细内容...

  阅读:22次