一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 使用log4j MDC實現日志追蹤

使用log4j MDC實現日志追蹤

2022-01-13 00:47houshiqun689 Java教程

這篇文章主要介紹了使用log4j MDC實現日志追蹤方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

log4j MDC實現日志追蹤

MDC 中包含的可以被同一線程中執行的代碼所訪問內容。當前線程的子線程會繼承其父線程中的 MDC 的內容。記錄日志時,只需要從 MDC 中獲取所需的信息即可。

作用:

使用MDC來記錄日志,可以規范多開發下日志格式。

1、新建線程處理類 ThreadContext

?
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
 * 線程上下文
 *
 * @date 2017年3月1日
 * @since 1.0.0
 */
public class ThreadContext {
    /**
     * 線程上下文變量的持有者
     */
    private final static ThreadLocal<Map<String, Object>> CTX_HOLDER = new ThreadLocal<Map<String, Object>>();
    static {
        CTX_HOLDER.set(new HashMap<String, Object>());
    }
 
    /**
     * traceID
     */
    private final static String TRACE_ID_KEY = "traceId";
    /**
     * 會話ID
     */
    private final static String SESSION_KEY = "token";
    /**
     * 操作用戶ID
     */
    private final static String VISITOR_ID_KEY = "userId";
    /**
     * 操作用戶名
     */
    private final static String VISITOR_NAME_KEY = "userName";
    /**
     * 客戶端IP
     */
    private static final String CLIENT_IP_KEY = "clientIp";
    /**
     * 添加內容到線程上下文中
     *
     * @param key
     * @param value
     */
    public final static void putContext(String key, Object value) {
        Map<String, Object> ctx = CTX_HOLDER.get();
        if (ctx == null) {
            return;
        }
        ctx.put(key, value);
    }
    /**
     * 從線程上下文中獲取內容
     *
     * @param key
     */
    @SuppressWarnings("unchecked")
    public final static <T extends Object> T getContext(String key) {
        Map<String, Object> ctx = CTX_HOLDER.get();
        if (ctx == null) {
            return null;
        }
        return (T) ctx.get(key);
    }
    /**
     * 獲取線程上下文
     */
    public final static Map<String, Object> getContext() {
        Map<String, Object> ctx = CTX_HOLDER.get();
        if (ctx == null) {
            return null;
        }
        return ctx;
    }
    /**
     * 刪除上下文中的key
     *
     * @param key
     */
    public final static void remove(String key) {
        Map<String, Object> ctx = CTX_HOLDER.get();
        if (ctx != null) {
            ctx.remove(key);
        }
    }
    /**
     * 上下文中是否包含此key
     *
     * @param key
     * @return
     */
    public final static boolean contains(String key) {
        Map<String, Object> ctx = CTX_HOLDER.get();
        if (ctx != null) {
            return ctx.containsKey(key);
        }
        return false;
    }
    /**
     * 清空線程上下文
     */
    public final static void clean() {
        CTX_HOLDER.remove();
    }
    /**
     * 初始化線程上下文
     */
    public final static void init() {
        CTX_HOLDER.set(new HashMap<String, Object>());
    }
    /**
     * 設置traceID數據
     */
    public final static void putTraceId(String traceId) {
        putContext(TRACE_ID_KEY, traceId);
    }
    /**
     * 獲取traceID數據
     */
    public final static String getTraceId() {
        return getContext(TRACE_ID_KEY);
    }
    /**
     * 設置會話的用戶ID
     */
    public final static void putUserId(Integer userId) {
        putContext(VISITOR_ID_KEY, userId);
    }
    /**
     * 設置會話的用戶ID
     */
    public final static int getUserId() {
        Integer val = getContext(VISITOR_ID_KEY);
        return val == null ? 0 : val;
    }
    /**
     * 設置會話的用戶名
     */
    public final static void putUserName(String userName) {
        putContext(VISITOR_NAME_KEY, userName);
    }
    /**
     * 獲取會話的用戶名稱
     */
    public final static String getUserName() {
        return Optional.ofNullable(getContext(VISITOR_NAME_KEY))
                .map(name -> String.valueOf(name))
                .orElse("");
    }
    /**
     * 取出IP
     *
     * @return
     */
    public static final String getClientIp() {
        return getContext(CLIENT_IP_KEY);
    }
    /**
     * 設置IP
     *
     * @param ip
     */
    public static final void putClientIp(String ip) {
        putContext(CLIENT_IP_KEY, ip);
    }
    /**
     * 設置會話ID
     *
     * @param token
     */
    public static void putSessionId(String token) {
        putContext(SESSION_KEY, token);
    }
    /**
     * 獲取會話ID
     *
     * @param token
     */
    public static String getSessionId(String token) {
        return getContext(SESSION_KEY);
    }
    /**
     * 清空會話數據
     */
    public final static void removeSessionId() {
        remove(SESSION_KEY);
    }
}

2、添加工具類TraceUtil

?
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
import java.util.UUID;
import org.slf4j.MDC;
import ThreadContext;
/**
 * trace工具
 *
 * @date 2017年3月10日
 * @since 1.0.0
 */
public class TraceUtil {
    public static void traceStart() {
        ThreadContext.init();
        String traceId = generateTraceId();
        MDC.put('traceId', traceId);
        ThreadContext.putTraceId(traceId);
    }
    public static void traceEnd() {
        MDC.clear();
        ThreadContext.clean();
    }
    /**
     * 生成跟蹤ID
     *
     * @return
     */
    private static String generateTraceId() {
        return UUID.randomUUID().toString();
    }
}

3、添加ContextFilter

對于每個請求隨機生成RequestID并放入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
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.OncePerRequestFilter;
import com.pingan.manpan.common.util.TraceUtil;
import com.pingan.manpan.user.dto.ThreadContext;
import com.pingan.manpan.web.common.surpport.IpUtils;
/**
 * 上下文Filter
 *
 * @date 2017/3/10
 * @since 1.0.0
 */
//@Order 標記組件的加載順序
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContextFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            ThreadContext.putClientIp(IpUtils.getClientIp(request));
            TraceUtil.traceStart();
            filterChain.doFilter(request, response);
        } finally {
            TraceUtil.traceEnd();
        }
    }
}

4、在webConfiguriation注冊filter

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 請求上下文,應該在最外層
 *
 * @return
 */
@Bean
public FilterRegistrationBean requestContextRepositoryFilterRegistrationBean() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new ContextFilter());
    filterRegistrationBean.addUrlPatterns("/*");
    return filterRegistrationBean;
}

5、修改log4j日志配置文件,設置日志traceId

?
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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <jmxConfigurator/>
    <property name="LOG_LEVEL_PATTERN" value="%X{traceId:-} %5p"/>
    <property name="LOG_FILE" value="${LOG_PATH}/web.logx"/>
    <property name="LOG_FILE_SUFFIX" value=".logx"/>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_FILE}${LOG_FILE_SUFFIX}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}${LOG_FILE_SUFFIX}</fileNamePattern>
        </rollingPolicy>
    </appender>
    <appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
        <syslogHost>127.0.0.1</syslogHost>
        <facility>local6</facility>
        <port>514</port>
        <suffixPattern>${FILE_LOG_PATTERN}</suffixPattern>
    </appender>
    
    <logger name="druid.sql" level="DEBUG" />
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="SYSLOG"/>
    </root>
</configuration>

log4j2實現日志跟蹤

日志跟蹤

在每條日志前添加一個隨機字符串并且確保同一個請求的字符串相同。如下:c6019df137174d2b98631474db4156b7為此次請求的標識。通過次標識可以查詢到所有該請求的日志信息

[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:204]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:204]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:205]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:205]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:209]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:214]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:223]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:224]-[http-nio-8803-exec-4]-

同時可以將此標識返回給前端,便于問題查詢。traceID: c6019df137174d2b98631474db4156b7

?
1
2
3
4
5
6
7
8
9
10
11
12
13
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://test.page.qingin.cn
Cache-Control: max-age=30
Connection: keep-alive
Content-Type: application/json;charset=UTF-8
Date: Tue, 11 Aug 2020 12:02:19 GMT
Expires: Tue, 11 Aug 2020 12:02:49 GMT
Server: nginx/1.16.1
traceID: c6019df137174d2b98631474db4156b7
Transfer-Encoding: chunked
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers

我們可以通過過濾器實現以上的功能

Log4j2Filter.java

?
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
package com.generator.admin.filter;
import org.apache.logging.log4j.ThreadContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
/**
 * @calssName Log4j2Filter
 * @Description 對用戶的請求添加日志編號,并將此編號返回給前端,便于日志查詢
 */
@WebFilter(filterName = "Log4j2Filter", urlPatterns = "/*", initParams = {@WebInitParam(name = "DESCRIPTION", value = "這是Log4j2Filter過濾器")})
public class Log4j2Filter implements Filter {
    private String description;
    public static final String TRACE_ID = "traceID";
    private static final Logger logger = LoggerFactory.getLogger(Log4j2Filter.class);
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        description = filterConfig.getInitParameter("DESCRIPTION");
        System.out.println("過濾器初始化:"+ description);
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException,ServletException {
            
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
        
        // 生成一個隨機數給到前端
        String traceId = UUID.randomUUID().toString().replace("-", "");
        // 隨機數放到此線程的上下文中,可以在每條日志前加入。具體看下面log4j2.xml
        ThreadContext.put(TRACE_ID, traceId);
        // 隨機數放到Header中,在Response Headers中可查看到此數據
        resp.addHeader(TRACE_ID, traceId); 
        filterChain.doFilter(req, resp);
        ThreadContext.clearAll();
    }
    @Override
    public void destroy() {
        System.out.println("過濾器,被銷毀:"+ description);
    }
}

log4j2.xml <PatternLayout pattern="[traceID:%X{traceID}]-[%d{yyyy-MM-dd HH:mm:ss:SSS}]-[%t]-[%p]-[%l]-%m%n"/>

?
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
<?xml version="1.0" encoding="UTF-8"?>
<!--設置log4j2的自身log級別為warn-->
<!--日志級別以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,這個用于設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時,你會看到log4j2內部各種詳細輸出-->
<!--monitorInterval:Log4j能夠自動檢測修改配置 文件和重新配置本身,設置間隔秒數-->
<configuration status="warn" monitorInterval="30">
    
    <!--全局參數-->
    <Properties>
        <Property name="logPath">logs</Property>
    </Properties>
    
    <!--先定義所有的appender-->
    <appenders>
        <!--這個輸出控制臺的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!-- traceID:就是在過濾器中生成的隨機數 -->
            <PatternLayout pattern="[traceID:%X{traceID}]-[%d{yyyy-MM-dd HH:mm:ss:SSS}]-[%t]-[%p]-[%l]-%m%n"/>
        </console>
    </appenders>
    <!--然后定義logger,只有定義了logger并引入的appender,appender才會生效-->
    <loggers>
        <!--過濾掉spring和mybatis的一些無用的debug信息-->
        <logger name="org.springframework" level="INFO"></logger>
        <logger name="org.mybatis" level="INFO"></logger>
        <logger name="com.zaxxer" level="WARN"></logger>
        <!-- com.generator開發/測試環境用DEBUG,并用控制臺輸出即可 -->
        <logger name="com.generator" level="DEBUG" additivity="false">
            <appender-ref ref="Console"/>
        </logger>
        <!-- 未指定的包都按此 level 打印日志 -->
        <root level="DEBUG">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。

原文鏈接:https://segmentfault.com/a/1190000014236769

延伸 · 閱讀

精彩推薦
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
主站蜘蛛池模板: 欧美精品黑人巨大在线播放 | 欧美贵妇vs高跟办公室 | 国产成人影院一区二区 | 日日操美女 | 草草草视频在线观看 | 亚洲午夜性春猛交xxxx | 午夜片无码区在线观看 | 久久精品无码一区二区日韩av | 国产中文字幕 | 亚洲 综合 欧美在线视频 | 国内精品久久久久久中文字幕 | 日本免费一二区 | a v在线男人的天堂观看免费 | 99热视频 | 日本高清二三四本2021 | 色中文字幕 | 欧美视频一区二区三区四区 | 亚洲高清在线天堂精品 | 肠交女王magnet | 国产福利在线免费观看 | 男同互操 | 奇米影视在线视频8888 | hd在线观看免费高清视频 | 美女林柏欣21p人体之仓之梦 | 亚洲欧美另类综合 | 国产高清日韩 | 精品国产麻豆免费人成网站 | 色哟哟精品| 国内精品视频九九九九 | 免费观看无人区完整版 | 国产在线欧美日韩精品一区二区 | 被黑人同学彻底征服全文小说阅读 | 国产成人亚洲精品一区二区在线看 | 我半夜摸妺妺的奶C了她 | 日韩在线二区 | fulao在线观看的 | 欧美日韩1区| 亚洲精品久久啪啪网站成年 | 72张让男人一看就硬的图片 | 91亚洲成人 | 色欲麻豆国产福利精品 |