使用log4j MDC实现日志追踪

目录
  • log4j MDC实现日志追踪
    • 1、新建线程处理类 ThreadContext
    • 2、添加工具类TraceUtil
    • 3、添加ContextFilter
    • 4、在webConfiguriation注册filter
    • 5、修改log4j日志配置文件,设置日志traceId
  • log4j2实现日志跟踪
    • 日志跟踪
    • 我们可以通过过滤器实现以上的功能

log4j MDC实现日志追踪

MDC 中包含的可以被同一线程中执行的代码所访问内容。当前线程的子线程会继承其父线程中的 MDC 的内容。记录日志时,只需要从 MDC 中获取所需的信息即可。

作用:

使用MDC来记录日志,可以规范多开发下日志格式。

1、新建线程处理类 ThreadContext

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

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

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

    /**
     * 请求上下文,应该在最外层
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean requestContextRepositoryFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new ContextFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }

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

<?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

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

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"/>

<?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>

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

(0)

相关推荐

  • log4j2 项目日志组件的实例代码

    在项目运行过程中,常常需要进行功能调试以及用户行为的跟踪和记录,部分人习惯使用System.out,但这并不建议,它仅仅是使用方便但不便于维护也无扩展性.相比log4j的话,log4j可以控制日志信息的输送目的地.输出格式以及级别等等,使我们能够更加细致地控制日志的生成过程. Log4j2是对Log4j1的升级,在性能和功能上有显著的改进,包括多线程中吞吐量的增强.占位符的支持.配置文件自动重新加载等 一.入门介绍 1.下载jar包 pox.xml <dependencies> <dep

  • SpringBoot+slf4j实现全链路调用日志跟踪的方法(一)

    SpringBoot中除了常见的分布式链路跟踪系统zipkin.skywalking等,如果需要快速定位一次请求的所有日志,那么该如何实现?实际slf4j提供了MDC(Mapped Diagnostic Contexts)功能,支持用户定义和修改日志的输出格式以及内容.本文将介绍 Tracer集成的slf4j MDC功能,方便用户在只简单修改日志配置文件的前提下输出当前 Tracer 上下文 TraceId. MDC介绍 MDC(Mapped Diagnostic Context,映射调试上下文

  • SpringBoot整合log4j2日志的实现

    关于日志级别 共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF. All: 最低等级的,用于打开所有日志记录. Trace: 是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出. Debug: 指出细粒度信息事件对调试应用程序是非常有帮助的. Info: 消息在粗粒度级别上突出强调应用程序的运行过

  • 使用log4j MDC实现日志追踪

    目录 log4j MDC实现日志追踪 1.新建线程处理类 ThreadContext 2.添加工具类TraceUtil 3.添加ContextFilter 4.在webConfiguriation注册filter 5.修改log4j日志配置文件,设置日志traceId log4j2实现日志跟踪 日志跟踪 我们可以通过过滤器实现以上的功能 log4j MDC实现日志追踪 MDC 中包含的可以被同一线程中执行的代码所访问内容.当前线程的子线程会继承其父线程中的 MDC 的内容.记录日志时,只需要从

  • java 如何实现日志追踪MDC

    目录 java 日志追踪MDC 简单的demo MDC的介绍及使用 1.MDC是什么? 2.MDC的原理 3.MDC的使用 java 日志追踪MDC MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我们就可以追踪各种线上问题. 但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程. 因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等:如此,我们可以从大量日志信息中grep出某个用户的操作流程

  • 利用Spring boot+LogBack+MDC实现链路追踪

    目录 MDC介绍 API说明 MDC使用 1.拦截器 2.工具类 MDC 存在的问题 子线程日志打印丢失traceId HTTP调用丢失traceId 1.接口调用方 2.第三方服务需要添加拦截器 MDC介绍 MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j .logback及log4j2 提供的一种方便在多线程条件下记录日志的功能.MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对.MDC 中包含的内容可以被同一线程中执行的代码所访问.

  • Springboot+MDC+traceId日志中打印唯一traceId

    目录 1. 为什么需要这个traceId 2.通过MDC设置traceId 2.1 使用filter过滤器设置traceId 2.2 使用JWT token过滤器的项目 2.3 使用Interceptor拦截器设置traceId 3.logback.xml中配置traceId 4.补充异步方法带入上下文的traceId 5.在接口放回中,增加traceId返回 先看一张图: 有同学问:日志中[]中类似uuid的这个traceId是怎么实现的,这边文章就介绍下如何在springboot工程下用MD

  • 使用MDC实现日志链路跟踪

    目录 1.原理 2.实现 3.过滤器 4.logback.xml 5.返回体 6.效果日志 前言: 在微服务环境中,我们经常使用Skywalking.CAT等去实现整体请求链路的追踪,但是这个整体运维成本高,架构复杂,我们来使用MDC通过Log来实现一个轻量级的会话事务跟踪功能. 1.原理 MDC org.sl4j.MDC其实内部就是ThreadLocal,MDC提供了put/get/clear等几个核心接口,用于操作ThreadLocal中的数据:ThreadLocal中的K-V,可以在log

  • Log4j定时打印日志及添加模块名配置的Java代码实例

    配置间隔时间,定时打印日志  接到个需求,通过log4j定时打印日志,需求描述如下:需要能够定时打印日志,时间间隔可配.说到定时,首先想到了DailyRollingFileAppender类,各种定时,根据datePattern,这个可以参考类SimpleDateFormat类,常见的一些定时设置如下: '.'yyyy-MM: 每月 '.'yyyy-ww: 每周 '.'yyyy-MM-dd: 每天 '.'yyyy-MM-dd-a: 每天两次 '.'yyyy-MM-dd-HH: 每小时 '.'yy

  • springboot整合dubbo设置全局唯一ID进行日志追踪的示例代码

    1.新建项目 利用idea创建一个父项目,三个子项目,其中一个项目为生产者,一个项目为消费者,一个为接口等公共服务项目,生产者和消费者需要有web依赖,可以作为tomcat容器启动. 2.项目依赖 <dependencies> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <v

  • springboot+log4j.yml配置日志文件的方法

    一,Maven 依赖 pom.xml配置 1, 去掉默认日志,以便切换到log4j2的日志依赖 2, 然后添加如下两个日志依赖 二,在工程根目录下添加 lo4g2.yml 配置文件 2, 配置文件内容 <!-- 配置 log4j2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artif

  • Spring Boot mybatis-config 和 log4j 输出sql 日志的方式

    依赖 <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> 两种配置log4j的方式: 一定要新建一个log4j.properties文件 在yaml中直接配置 在mybatis-config中配置 mapper-locations 貌似不管用依旧需

  • log4j配置失效日志中打印Debug信息问题

    目录 log4j配置失效日志中打印Debug信息 去除依赖的方法 log4j日志的配置--Debug log4j配置失效日志中打印Debug信息 最近发布项目的时候发现控制台打印的日志较往常多了很多,仔细一看,debug和info信息也赫然在列,打开log4j.xml配置文件看一下: <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http

随机推荐