SrpingBoot3适配TLog

789 阅读3分钟

由于springBoot3修改了 servlet相关包的路径 TLog也要适配

重写TLogServletFilter

package com.yomahub.tlog.web.filter;

import com.yomahub.tlog.constant.TLogConstants;
import com.yomahub.tlog.context.TLogContext;
import com.yomahub.tlog.web.common.TLogWebCommon;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;


import java.io.IOException;

/**
 * 支持servlet
 * @author Bryan.Zhang
 * @since 1.3.5
 */
public class TLogServletFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse){
            try{
                TLogWebCommon.loadInstance().preHandle((HttpServletRequest)request);
                //把traceId放入response的header,为了方便有些人有这样的需求,从前端拿整条链路的traceId
                ((HttpServletResponse)response).addHeader(TLogConstants.TLOG_TRACE_KEY, TLogContext.getTraceId());
                chain.doFilter(request, response);
                return;
            }finally {
                TLogWebCommon.loadInstance().afterCompletion();
            }
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

重写TLogWebCommon

package com.yomahub.tlog.web.common;

import com.yomahub.tlog.constant.TLogConstants;
import com.yomahub.tlog.context.TLogContext;
import com.yomahub.tlog.core.rpc.TLogLabelBean;
import com.yomahub.tlog.core.rpc.TLogRPCHandler;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TLog web这块的逻辑封装类
 *
 * @author Bryan.Zhang
 * @since 1.1.5
 */
public class TLogWebCommon extends TLogRPCHandler {
    
    private final static Logger log = LoggerFactory.getLogger(TLogWebCommon.class);
    
    private static volatile TLogWebCommon tLogWebCommon;
    
    public static TLogWebCommon loadInstance() {
        if (tLogWebCommon == null) {
            synchronized (TLogWebCommon.class) {
                if (tLogWebCommon == null) {
                    tLogWebCommon = new TLogWebCommon();
                }
            }
        }
        return tLogWebCommon;
    }
    
    public void preHandle(HttpServletRequest request) {
        String traceId = request.getHeader(TLogConstants.TLOG_TRACE_KEY);
        String spanId = request.getHeader(TLogConstants.TLOG_SPANID_KEY);
        String preIvkApp = request.getHeader(TLogConstants.PRE_IVK_APP_KEY);
        String preIvkHost = request.getHeader(TLogConstants.PRE_IVK_APP_HOST);
        String preIp = request.getHeader(TLogConstants.PRE_IP_KEY);
        
        TLogLabelBean labelBean = new TLogLabelBean(preIvkApp, preIvkHost, preIp, traceId, spanId);
        
        processProviderSide(labelBean);
    }
    
    public void afterCompletion() {
        cleanThreadLocal();
    }
}

自定义SLF4JServiceProvider

package org.slf4j;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.status.StatusUtil;
import ch.qos.logback.core.util.StatusPrinter;
import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.helpers.Util;
import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider;

/**
 * @date 2024/8/29
 */
public class TLogLogbackSLF4JServiceProvider implements SLF4JServiceProvider {
    final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS";
    
    /**
     * Declare the version of the SLF4J API this implementation is compiled against.
     * The value of this field is modified with each major release.
     */
    // to avoid constant folding by the compiler, this field must *not* be final
    public static String REQUESTED_API_VERSION = "2.0.99"; // !final
    
    private LoggerContext defaultLoggerContext;
    private IMarkerFactory markerFactory;
    private MDCAdapter mdcAdapter;
    // private final ContextSelectorStaticBinder contextSelectorBinder =
    // ContextSelectorStaticBinder.getSingleton();
    //    private static Object KEY = new Object();
    //    private volatile boolean initialized = false;
    
    @Override
    public void initialize() {
        defaultLoggerContext = new LoggerContext();
        defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
        initializeLoggerContext();
        defaultLoggerContext.start();
        markerFactory = new BasicMarkerFactory();
        mdcAdapter = TLogLogbackTTLMdcAdapter.getInstance();
        // set the MDCAdapter for the defaultLoggerContext immediately
        defaultLoggerContext.setMDCAdapter(mdcAdapter);
    }
    
    private void initializeLoggerContext() {
        try {
            try {
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                Util.report("Failed to auto configure default logger context", je);
            }
            // LOGBACK-292
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
            }
            // contextSelectorBinder.init(defaultLoggerContext, KEY);
            
        } catch (Exception t) { // see LOGBACK-1159
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }
    
    @Override
    
    public ILoggerFactory getLoggerFactory() {
        return defaultLoggerContext;
        
        //        if (!initialized) {
        //            return defaultLoggerContext;
        //
        //
        //        if (contextSelectorBinder.getContextSelector() == null) {
        //            throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
        //        }
        //        return contextSelectorBinder.getContextSelector().getLoggerContext();
    }
    
    @Override
    public IMarkerFactory getMarkerFactory() {
        return markerFactory;
    }
    
    @Override
    public MDCAdapter getMDCAdapter() {
        return mdcAdapter;
    }
    
    @Override
    public String getRequestedApiVersion() {
        return REQUESTED_API_VERSION;
    }
}

自定义MDCAdapter

package org.slf4j;

import com.alibaba.ttl.TransmittableThreadLocal;
import org.slf4j.helpers.ThreadLocalMapOfStacks;
import org.slf4j.spi.MDCAdapter;

import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;

/**
 * 针对于Logback日志的mdc adapter。
 *
 * @author Bryan.Zhang
 * @since 1.3.7
 */
public class TLogLogbackTTLMdcAdapter implements MDCAdapter {
    
    final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new TransmittableThreadLocal<>();
    private final ThreadLocalMapOfStacks threadLocalMapOfDeques = new ThreadLocalMapOfStacks();
    
    private static final int WRITE_OPERATION = 1;
    private static final int READ_OPERATION = 2;
    
    private static TLogLogbackTTLMdcAdapter mdcMDCAdapter;
    
    // keeps track of the last operation performed
    final ThreadLocal<Integer> lastOperation = new ThreadLocal<>();
    
    static {
        mdcMDCAdapter = new TLogLogbackTTLMdcAdapter();
        MDC.mdcAdapter = mdcMDCAdapter;
    }
    
    public static MDCAdapter getInstance() {
        return mdcMDCAdapter;
    }
    
    private Integer getAndSetLastOperation(int op) {
        Integer lastOp = lastOperation.get();
        lastOperation.set(op);
        return lastOp;
    }
    
    private static boolean wasLastOpReadOrNull(Integer lastOp) {
        return lastOp == null || lastOp == READ_OPERATION;
    }
    
    private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
        Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
        if (oldMap != null) {
            // we don't want the parent thread modifying oldMap while we are
            // iterating over it
            synchronized (oldMap) {
                newMap.putAll(oldMap);
            }
        }
        
        copyOnInheritThreadLocal.set(newMap);
        return newMap;
    }
    
    @Override
    public void put(String key, String val) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }
        
        Map<String, String> oldMap = copyOnInheritThreadLocal.get();
        Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
        
        if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
            Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
            newMap.put(key, val);
        } else {
            oldMap.put(key, val);
        }
    }
    
    @Override
    public void remove(String key) {
        if (key == null) {
            return;
        }
        Map<String, String> oldMap = copyOnInheritThreadLocal.get();
        if (oldMap == null) {
            return;
        }
        
        Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
        
        if (wasLastOpReadOrNull(lastOp)) {
            Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
            newMap.remove(key);
        } else {
            oldMap.remove(key);
        }
        
    }
    
    
    @Override
    public void clear() {
        lastOperation.set(WRITE_OPERATION);
        copyOnInheritThreadLocal.remove();
    }
    
    @Override
    public String get(String key) {
        Map<String, String> map = getPropertyMap();
        if ((map != null) && (key != null)) {
            return map.get(key);
        } else {
            return null;
        }
    }
    
    public Map<String, String> getPropertyMap() {
        lastOperation.set(READ_OPERATION);
        return copyOnInheritThreadLocal.get();
    }
    
    
    @Override
    public Map getCopyOfContextMap() {
        lastOperation.set(READ_OPERATION);
        Map<String, String> hashMap = copyOnInheritThreadLocal.get();
        if (hashMap == null) {
            return null;
        } else {
            return new HashMap<>(hashMap);
        }
    }
    
    @Override
    public void pushByKey(String key, String value) {
        threadLocalMapOfDeques.pushByKey(key, value);
    }
    
    @Override
    public String popByKey(String key) {
        return threadLocalMapOfDeques.popByKey(key);
    }
    
    @Override
    public Deque<String> getCopyOfDequeByKey(String key) {
        return threadLocalMapOfDeques.getCopyOfDequeByKey(key);
    }
    
    @Override
    public void clearDequeByKey(String key) {
        threadLocalMapOfDeques.clearDequeByKey(key);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public void setContextMap(Map contextMap) {
        lastOperation.set(WRITE_OPERATION);
        
        Map<String, String> newMap = Collections.synchronizedMap(new HashMap<>());
        newMap.putAll(contextMap);
        
        // the newMap replaces the old one for serialisation's sake
        copyOnInheritThreadLocal.set(newMap);
        
        
    }
}

加载TLogLogbackSLF4JServiceProvider

spi配置,services下添加文件:org.slf4j.spi.SLF4JServiceProvider,文件内容如下:

org.slf4j.TLogLogbackSLF4JServiceProvider

配置LogConfig

package com.forge.starter.log.config;

import com.yomahub.tlog.web.filter.TLogServletFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @date 2024/8/29
 */
@Configuration
@ComponentScan(value = "com.yomahub.tlog")
public class LogConfig {
    
    @Bean
    public FilterRegistrationBean<TLogServletFilter> loggingFilter() {
        FilterRegistrationBean<TLogServletFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new TLogServletFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

完整项目结构

按照上述修改就可以完成SpringBoot3的适配了,其它TLog配置不需要修改