java+springboot do log link tracking

1. Why do log link tracking

  • Log Path Tracing is a key function of the Spring Boot project. It realizes visual tracking of log data by associating the source of the log message with its corresponding request or response path.
  • Tracking and managing log data becomes increasingly important as projects grow in size and complexity. By implementing log link tracking, we can better understand various events that occur in the project, identify bottlenecks and quickly locate problems. This can not only improve development efficiency, but also ensure project quality.

2. Implementation method

  1. This article uses the MDC scheme to implement log link tracking
  2. DC (Mapped Diagnostic Context) is a function provided by log4j and logback to facilitate logging under multi-threaded conditions.
  3. Show results:

3. Implementation steps

  1. Prepare dependencies (you can modify the version of springboot according to your own project)
<dependency>
<groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>8.15.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.7.10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>
  1. Configure logback log printing
    • Create logback-spring.xml in the project resources directory
    • The configuration is as follows
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <contextName>${APP_NAME}</contextName>
    <springProperty name="APP_NAME" scope="context" source="spring.application.name"/>
    <springProperty name="LOG_FILE" scope="context" source="logging.file" defaultValue="../logs/application/${APP_NAME}"/>
    <springProperty name="LOG_MAXFILESIZE" scope="context" source="logback.filesize" defaultValue="50MB"/>
    <springProperty name="LOG_FILEMAXDAY" scope="context" source="logback. filemaxday" defaultValue="30"/>
    <springProperty name="ServerIP" scope="context" source="spring.cloud.client.ip-address" defaultValue="0.0.0.0"/>
    <springProperty name="ServerPort" scope="context" source="server.port" defaultValue="0000"/>
    <springProperty name="LOG_POINT_FILE" scope="context" source="logging.file" defaultValue="../logs/point"/>
    <springProperty name="LOG_AUDIT_FILE" scope="context" source="logging.file" defaultValue="../logs/audit"/>

    <!-- color log -->
    <!-- The rendering class that the color log depends on -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />

    <!-- Color log format -->
    <property name="CONSOLE_LOG_PATTERN"
              value="[${APP_NAME}:${ServerIP}:${ServerPort}] %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%level){ blue} %clr(${PID}){magenta} %clr([%X{traceId}]){yellow} %clr([%thread]){orange} %clr(%logger){cyan} %m% n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" />
    <property name="CONSOLE_LOG_PATTERN_NO_COLOR" value="[${APP_NAME}:${ServerIP}:${ServerPort}] %d{yyyy-MM-dd HH:mm:ss.SSS} %level ${PID } [%X{traceId}] [%thread] %logger %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" />

    <!-- console log -->
    <appender name="StdoutAppender" class="ch.qos.logback.core.ConsoleAppender">
        <withJansi>true</withJansi>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            </layout>
        </encoder>
    </appender>
    <!-- ************************* Generate regular log files every day**************** *********** -->
    <appender name="FileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}/${APP_NAME}.log</file>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN_NO_COLOR}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--Time-based subcontracting strategy -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}/${APP_NAME}.%d{<!-- -->yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--Retention time, unit: day-->
            <MaxHistory>${LOG_FILEMAXDAY}</MaxHistory>
            <MaxFileSize>${LOG_MAXFILESIZE}</MaxFileSize>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
    <appender name="file_async" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="FileAppender"/>
    </appender>
    <!-- ************************* Generate regular log files every day end************** *********** -->

    <!-- *************************************** Buried log start *** *************************************** -->
    <appender name="point_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_POINT_FILE}/point.${APP_NAME}.log</file>
        <encoder>
            <pattern>%d{<!-- -->yyyy-MM-dd HH:mm:ss.SSS}|${APP_NAME}|%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--Time-based subcontracting strategy -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_POINT_FILE}/point.${APP_NAME}.%d{<!-- -->yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--Retention time, unit: day-->
            <MaxHistory>${LOG_FILEMAXDAY}</MaxHistory>
            <MaxFileSize>${LOG_MAXFILESIZE}</MaxFileSize>
        </rollingPolicy>
    </appender>
    <appender name="point_log_async" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="point_log"/>
    </appender>
    <!-- *************************************** Buried log end *** *************************************** -->

    <appender name="audit_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_AUDIT_FILE}/audit.log</file>
        <encoder>
            <pattern>%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- Time-based subcontracting strategy -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_AUDIT_FILE}/audit.%d{<!-- -->yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--Retention time, unit: day-->
            <maxHistory>${LOG_FILEMAXDAY}</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${LOG_MAXFILESIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender>

    <appender name="audit_log_async" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="audit_log"/>
    </appender>

    <!-- *************************************** logger configuration start **** ************************************* -->
    <logger name="cn.onehome.dubbocore.log.monitor" level="INFO" addtivity="false">
        <appender-ref ref="point_log_async" />
    </logger>
    <logger name="cn.onehome.dubbocore.log.service.impl.LoggerAuditServiceImpl" level="INFO" addtivity="false">
        <appender-ref ref="audit_log_async" />
    </logger>

    <!-- Multi-environment custom logger configuration (activate a configuration according to the value of spring.profiles.active) -->
    <springProfile name="dev">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <springProfile name="test">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <springProfile name="pre">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="StdoutAppender"/>
            <appender-ref ref="file_async"/>
        </root>
    </springProfile>
    <!-- *************************************** logger configuration end **** ************************************* -->
</configuration>

  1. Configure Interceptor to add trace log parameters
package cn.wje.common.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.UUID;

/**
 * @author luoqifeng
 * @date 2023/05/18 10:13
 */
@Slf4j
@Component
@Order(Ordered. HIGHEST_PRECEDENCE)
public class AbstractTraceIdFilter implements Filter {<!-- -->

    /**
     * Place traceId
     */
    private void putTraceId(String traceId) {<!-- -->
        // Put in the log request field
        MDC.put("traceId", traceId);
    }

    /**
     * Clear traceId
     */
    private void clearTraceId() {<!-- -->
        try {<!-- -->
            MDC. remove("traceId");
        } catch (Exception e) {<!-- -->
            log.warn("clear traceId error. " + e.getMessage());
            // ignore
        }
    }

    /**
     * Generate traceId
     *
     * @param request
     *            ask
     * @return traceId
     */
    @NotNull
    public static String generateTraceId(HttpServletRequest request) {<!-- -->
        String traceId = UUID.randomUUID().toString().replace("-", "");
        try {<!-- -->
            if (request != null) {<!-- -->
                String requestUri = request. getRequestURI();
                if (requestUri != null) {<!-- -->
                    if (requestUri. startsWith("/")) {<!-- -->
                        requestUri = requestUri.substring(1);
                    }
                    if (requestUri. endsWith("/")) {<!-- -->
                        requestUri = requestUri.substring(0, requestUri.length() - 1);
                    }
                    requestUri = requestUri.replace("/", ".");
                }

                // TODO If you need to add user tracking, you can put the user ID in traceId here
                // String accountId = null;
                // TODO get user ID logic
                // traceId = String.join("_", traceId, accountId == null ? "%s" : accountId, requestUri);
                traceId = String.join("_", traceId, requestUri);
            }
        } catch (Exception e) {<!-- -->
            // ignore
        }
        return traceId;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {<!-- -->
        try {<!-- -->
            HttpServletRequest httpServletRequest = ((HttpServletRequest)request);
            String traceId = httpServletRequest. getHeader("traceId");
            if (StringUtils.isBlank(traceId)) {<!-- -->
                traceId = generateTraceId(httpServletRequest);
                log.debug("generate traceId [{}].", traceId);
            } else {<!-- -->
                log.debug("obtain traceId [{}] from request header. ", traceId);
            }
            // put
            putTraceId(traceId);
            chain.doFilter(httpServletRequest, response);
        } finally {<!-- -->
            //clear
            clearTraceId();
        }
    }
}
  1. Write the test interface and run the test
package cn.wjr.towerJJ.controller;

import cn.wje.common.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author luoqifeng
 * @date 2023/05/18 10:13
 */
@RestController
@RequestMapping("test")
@Slf4j
public class testController {<!-- -->

    @RequestMapping("test001")
    public Result<String> test001(){<!-- -->
        log.info("test001");
        return Result.success("test001");
    }
}
  1. View returns and results:
    response result

  2. Paste the return body packaged by myself, you can package the return body by yourself according to the project situation

package cn.wje.common.util;

import cn.wje.common.constants.CommonErrorCode;
import org.slf4j.MDC;

import java.io.Serializable;

/**
 * @author luoqifeng
 * @date 2023/05/18 10:13
 */
public class Result<T> implements Serializable {<!-- -->

    private static final long serialVersionUID = -2234571636295254776L;

    /**coding**/
    private String code;
    /** The reason for the failure when it fails**/
    private String msg;
    /**Return data on success**/
    private T data;

    /**
     * traceId
     * */
    private String traceId;

    public Result() {<!-- -->

    }

    /**
     * return success
     *
     * @param data
     * @return
     */
    public static <T> Result<T> success(T data) {<!-- -->
        Result<T> result = new Result<>();
        result.setCode(CommonErrorCode.SUCCESS.getCode());
        if (data != null) {<!-- -->
            result. setData(data);
        }
        result.setTraceId(MDC.get("traceId"));
        return result;
    }

    /**
     * return success
     *
     * @return
     */
    public static <T> Result<T> success() {<!-- -->
        return Result. success(null);
    }

    /**
     * return failed
     *
     * @return
     */
    public static <T> Result<T> fail(String code,String msg) {<!-- -->
        Result<T> result = new Result<>();
        result. setCode(code);
        result. setMsg(msg);
        result.setTraceId(MDC.get("traceId"));
        return result;
    }

    /**
     * return failed
     *
     * @return
     */
    public static <T> Result<T> fail(CommonErrorCode code) {<!-- -->
        return Result.fail(code.getCode(), code.getErrorMsg());
    }

    /**
     * Build Result
     *
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static <T> Result<T> build(String code,String msg,T data) {<!-- -->
        Result<T> result = new Result<>();
        result. setCode(code);
        result. setMsg(msg);
        result. setData(data);
        result.setTraceId("traceId");
        return result;
    }

    /**
     * Build Result
     *
     * @param code
     * @param data
     * @return
     */
    public static <T> Result<T> build(String code,T data) {<!-- -->
        return Result. build(code, null, data);
    }

    /**
     * Build Result
     *
     * @param code
     * @param msg
     * @return
     */
    public static <T> Result<T> build(String code,String msg) {<!-- -->
        return Result. build(code, msg, null);
    }

    /**
     * whether succeed
     *
     * @return
     */
    public boolean isOk() {<!-- -->
        return CommonErrorCode.SUCCESS.getCode().equals(code);
    }

    public String getCode() {<!-- -->
        return code;
    }
    public void setCode(String code) {<!-- -->
        this.code = code;
    }
    public String getMsg() {<!-- -->
        return msg;
    }
    public void setMsg(String msg) {<!-- -->
        this.msg = msg;
    }
    public T getData() {<!-- -->
        return data;
    }
    public void setData(T data) {<!-- -->
        this.data = data;
    }

    public String getTraceId() {<!-- -->
        return traceId;
    }

    public void setTraceId(String traceId) {<!-- -->
        this.traceId = traceId;
    }
}

  1. If it does not take effect, please breakpoint to debug whether the interceptor (AbstractTraceIdFilter) is successfully intercepted