logback使用MDC打印租戶code

HttpRequestMDCFilter攔截器統一處理

import com.****.config.AuthManager;
import com.****.constant.MDCConstants;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;

/**
 * 在logback日誌輸出中增加MDC參數選項
 * 注意,此Filter儘可能的放在其他Filter之前
 * <p>
 * 我們可以在logback.xml文件的layout部分,通過%X{key}的方式使用MDC中的變量
 */
@Order(1)
@Component
public class HttpRequestMDCFilter implements Filter {

    @Resource
    private AuthManager authManager;
    private String localIp;//本機IP

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //getLocalIp
        localIp = getLocalIp();
    }

    private String getLocalIp() {
        try {
            //一個主機有多個網絡接口
            Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
            while (netInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = netInterfaces.nextElement();
                //每個網絡接口,都會有多個"網絡地址",比如一定會有loopback地址,會有siteLocal地址等.以及IPV4或者IPV6    .
                Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    //get only :172.*,192.*,10.*
                    if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) {
                        return address.getHostAddress();
                    }
                }
            }
        } catch (Exception e) {
            //
        }
        return null;
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest hsr = (HttpServletRequest) request;
        try {
            mdc(hsr);
        } catch (Exception e) {
            //
        }

        try {
            chain.doFilter(request, response);
        } finally {
            MDC.clear();//must be,threadLocal
        }

    }

    private void mdc(HttpServletRequest hsr) {
        MDC.put(MDCConstants.LOCAL_IP_MDC_KEY, localIp);
//        MDC.put(MDCConstants.TIMESTAMP, "" + System.currentTimeMillis());
        MDC.put(MDCConstants.URI_MDC_KEY, hsr.getRequestURI());
        MDC.put(MDCConstants.tenant, authManager.getTenantCode());
    }

    @Override
    public void destroy() {

    }
}

MDCConstants固定值

public interface MDCConstants {

    String tenant = "tenant";

    String REQUEST_ID_HEADER = "X-Request-ID";
    String REQUEST_SEQ_HEADER = "X-Request-Seq";
    String REQUEST_ID_MDC_KEY = "requestId";
    String REQUEST_SEQ_MDC_KEY = "requestSeq";
    String NEXT_REQUEST_SEQ_MDC_KEY = "nextRequestSeq";
    String LOCAL_IP_MDC_KEY = "localIp";
    String URI_MDC_KEY = "uri";
    String TIMESTAMP = "_timestamp_";//進入filter的時間戳
    String COOKIE_KEY_PREFIX = "_C_";
    String HEADER_KEY_PREFIX = "_H_";
    String PARAMETER_KEY_PREFIX = "_P_";
}

logback-spring.xml配置

    <property name="MONITOR_PATTERN"
              value="%d [%thread] %-5p [%c] [%F:%L] [trace=%X{X-B3-TraceId:-},span=%X{X-B3-SpanId:-},parent=%X{X-B3-ParentSpanId:-}][localIp=%X{localIp:-},tenant=%X{tenant:-},url=%X{uri:-}] - %msg%n"/>

新增了localIp,tenant,uri三個配置

MDC異步線程問題

使用上面的方式,發現異步線程,線程池,定時任務都無法打印,定時任務不說,沒有這些參數,但是其他應該可以打印

兼容@Async

MdcTaskDecorator

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;

import java.util.Map;

/**
 * @author qhong
 * @date 2022/4/18 11:13
 **/
@Slf4j
public class MdcTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        // Right now: Web thread context !
        // (Grab the current thread MDC data)
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return () -> {
            try {
                // Right now: @Async thread context !
                // (Restore the Web thread context's MDC data) when schedule use async,contextMap is null
                if (contextMap != null && !contextMap.isEmpty()) {
                    MDC.setContextMap(contextMap);
                }
                runnable.run();
            } catch (Exception e) {
                throw e;
            } finally {
                MDC.clear();
            }
        };
    }
}

MyAsyncUncaughtExceptionHandler

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;

import java.lang.reflect.Method;

/**
 * @author qhong
 * @date 2022/4/18 14:14
 **/
@Slf4j
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        log.info("class#method: " + method.getDeclaringClass().getName() + "#" + method.getName());
        log.info("type        : " + ex.getClass().getName());
        log.info("exception   : " + ex.getMessage());
    }
}

AsyncConfigHandler

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

/**
 * @author qhong
 * @date 2022/4/18 14:13
 **/
@Configuration
@EnableAsync
public class AsyncConfigHandler extends AsyncConfigurerSupport {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(16);
        executor.setTaskDecorator(new MdcTaskDecorator());
        //等待任務在關機時完成--表明等待所有線程執行完
        //executor.setWaitForTasksToCompleteOnShutdown(true);
        //等待時間 (默認爲0,此時立即停止),並沒等待xx秒後強制停止
        //executor.setAwaitTerminationSeconds(60 * 15);
        //線程名稱前綴
        executor.setThreadNamePrefix("MyAsync-");
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncUncaughtExceptionHandler();
    }
}

參考

logback日誌與MDC機制

Java異步線程池中處理logback MDC

AsyncConfigurerSupport 自定義異步線程池

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章