SpringCloud之個性化日誌追蹤

方案背景

日誌跟蹤技術使得查找一次調用所產生的日誌信息變得方便。當須要排查一些問題時,能夠根據報錯的上下文進行分析,給問題診斷帶來方便。在spring cloud微服務中,單體應用的日誌跟蹤技術已經不能知足需求,於是通常採用Spring Cloud Sleuth組件提供的功能來完成分佈式日誌跟蹤。

Spring Cloud Sleuth組件會在zuul網關中,對於每一個請求生成一個日誌id,以後會經過http header的方式將id帶到不一樣的服務應用中。在特定應用中,經過sl4j及特定的日誌輸出架構(好比logback)實施單體應用的日誌跟蹤。

Spring Cloud Sleuth默認的解決方案是在一次請求所生產的每條日誌中添加日誌id,而後經過日誌id來關聯該次請求的日誌信息。在某些業務場景,可能須要對默認的解決方案進行功能的擴展,好比:

業務上對日誌id的格式有要求,好比須要從日誌id中看出請求的時間點等。
出於監控的需求,須要在每條日誌中加入客戶端請求的ip信息等。
本方案即是經過擴展Spring Cloud Sleuth默認的解決方案的功能達到知足以上業務點。

實現分析

服務自己對traceId的處理。
Sleuth經過Filter的方式,在filter中對請求頭的處理來實現traceId的追蹤。

先判斷http頭中是否存在「X-B3-TraceId」,不存在,則生成新的traceId;存在,則以頭X-B3-TraceId的值做爲traceId。最後將X-B3-TraceId的值放到MDC中,以便日誌輸出中帶上X-B3-TraceId。這樣使得在本服務中,用戶請求產生的日誌輸出都會帶有traceId。

服務間對traceId的處理
在微服務中,服務A向服務B發起調用請求。那如何將A中的traceId傳遞給B呢?首先,先要了解服務A是如何調用服務B的。

一、網關接收到進行,進行請求轉發

Sleuth在ZuulFilter(TracePreZuulFilter)中將traceId添加到http頭X-B3-TraceId中,以便所轉發請求對應的服務能從頭中獲取到traceId。同時還將traceId放到MDC中,以便本應用在輸出日誌時帶traceId。

二、服務內部經過feign註解調用另外一個服務

因爲feign註解的實現是經過生成Hystrix的代理類來完成請求的處理,而Hystrix在進行請求發送時是經過異步的方式調用ribbon的組件進行負載均衡,而後經過Feign.Client的execute方法來進行請求的發送。故此時須要解決如下兩個問題:

(1)如何在異步線程中傳遞traceId。

Sluth是經過實現HystrixConcurrencyStrategy接口來解決traceId異步傳遞的問題。Hystrix在實際調用時,會調用HystrixConcurrencyStrategy的wrapCallable方法。所以,經過實現這個接口,在wrapCallable中將traceId存放起來(具體參見SleuthHystrixConcurrencyStrategy

)。

(2)Feign如何在服務中傳遞traceId。

Sluth經過實現Feign.Client,在execute前將traceId存放到X-B3-TraceId頭中來實現(具體參見TraceFeignClient)。

方案實施

網關生成額外的跟蹤信息
實現ZuulFilter接口,在其run方法中將額外的信息放到http頭中及MDC中。以下:



 



源碼:

package com.yao.springcloud.filters;
 
 
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.yao.springcloud.ConstantsName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
 
import java.util.Map;
 
 
public class TracePreFilter extends ZuulFilter {
    private static Logger LOGGER = LoggerFactory.getLogger(TracePreFilter.class);
 
    @Override
    public String filterType() {
        return "pre";
    }
 
    @Override
    public int filterOrder() {
        return 1;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
 
    @Override
    public Object run() {
        LOGGER.info("TracePreFilter run");
        RequestContext ctx = RequestContext.getCurrentContext();
        Map<String, String> requestHeaders = ctx.getZuulRequestHeaders();
 
        String clientIp = ctx.getRequest().getRemoteAddr();
        //設置頭信息
        setHeader(requestHeaders, ConstantsName.CLIENT_IP, clientIp);
        //添加到MDC中
        MDC.put(ConstantsName.CLIENT_IP, clientIp);
        LOGGER.info("TracePreFilter END");
 
        return null;
    }
 
 
    /**
     * 設置請求頭
     *
     * @param request 請求頭
     * @param name    參數名
     * @param value   參數值
     */
    public void setHeader(Map<String, String> request, String name, String
            value) {
        if (value != null) {
            request.put(name, value);
        }
    }
 
}
web filter中將額外的跟蹤信息保存到MDC中
web filter將額外的跟蹤信息從http header中取出來,而後放到MDC中。該步確保在一個服務中能夠從MDC中獲取到相關信息。



 源碼:

package com.yao.springcloud.web;
 
import com.yao.springcloud.ConstantsName;
import org.apache.commons.lang.StringUtils;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.OncePerRequestFilter;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@Order(ConstantsName.TRACE_FILTERORDER)
public class WebTraceFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        String clientIp = httpServletRequest.getHeader(ConstantsName.CLIENT_IP);
        if(StringUtils.isNotEmpty(clientIp)){//若是爲空,則表示第一次訪問,即網關端的請求
            MDC.put(ConstantsName.CLIENT_IP, clientIp);
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
        //確保每一個應用處理完後 清除相關MDC的內容
        MDC.remove(ConstantsName.CLIENT_IP);
 
    }
}
 

 在hystrix異步線程中添加額外的跟蹤信息
該步驟主要是將MDC中添加的額外信息放到異步線程中,解決經過hystrix進行異步調用時沒法在異步線程中取得所須要信息的問題。





源碼:

package com.yao.springcloud.hystrix;
 
 
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.yao.springcloud.ConstantsName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.MDC;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixConcurrencyStrategy;
 
import java.lang.invoke.MethodHandles;
import java.util.concurrent.Callable;
 
 
public class TraceSleuthHystrixConcurrencyStrategy extends SleuthHystrixConcurrencyStrategy {
    private static final Log log = LogFactory.getLog(TraceSleuthHystrixConcurrencyStrategy.class);
    private static final String HYSTRIX_COMPONENT = "hystrix";
    private final Tracer tracer;
    private final TraceKeys traceKeys;
    private HystrixConcurrencyStrategy delegate;
 
 
 
    public HystrixConcurrencyStrategy getDelegate() {
        return delegate;
    }
 
    public void setDelegate(HystrixConcurrencyStrategy delegate) {
        this.delegate = delegate;
    }
 
    public Tracer getTracer() {
        return tracer;
    }
 
    public TraceKeys getTraceKeys() {
        return traceKeys;
    }
 
    public TraceSleuthHystrixConcurrencyStrategy(Tracer tracer, TraceKeys traceKeys) {
        super(tracer, traceKeys);
        this.tracer = tracer;
        this.traceKeys = traceKeys;
    }
 
 
    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        if (callable instanceof TraceHystrixTraceCallable) {
            return callable;
        }
        Callable<T> wrappedCallable = this.delegate != null ? this.delegate.wrapCallable(callable) : callable;
        if (wrappedCallable instanceof TraceHystrixTraceCallable) {
            return wrappedCallable;
        }
        return new TraceHystrixTraceCallable<>(this.tracer, this.traceKeys, wrappedCallable);
    }
 
    private void logCurrentStateOfHysrixPlugins(HystrixEventNotifier eventNotifier,
            HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
        if (log.isDebugEnabled()) {
            log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + this.delegate
                    + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher [" + metricsPublisher
                    + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
            log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
        }
    }
 
    // Visible for testing
    static class TraceHystrixTraceCallable<S> implements Callable<S> {
 
        private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
 
        private Tracer tracer;
        private TraceKeys traceKeys;
        private Callable<S> callable;
        private String userIp;
        private String accessPath;
        private String userName;
        private Span parent;
        private String domain;
        private String clientIp;
 
        public Tracer getTracer() {
            return tracer;
        }
 
        public void setTracer(Tracer tracer) {
            this.tracer = tracer;
        }
 
        public TraceKeys getTraceKeys() {
            return traceKeys;
        }
 
        public void setTraceKeys(TraceKeys traceKeys) {
            this.traceKeys = traceKeys;
        }
 
        public Callable<S> getCallable() {
            return callable;
        }
 
        public void setCallable(Callable<S> callable) {
            this.callable = callable;
        }
 
        public String getUserIp() {
            return userIp;
        }
 
        public void setUserIp(String userIp) {
            this.userIp = userIp;
        }
 
        public String getAccessPath() {
            return accessPath;
        }
 
        public void setAccessPath(String accessPath) {
            this.accessPath = accessPath;
        }
 
        public String getUserName() {
            return userName;
        }
 
        public void setUserName(String userName) {
            this.userName = userName;
        }
 
        public Span getParent() {
            return parent;
        }
 
        public void setParent(Span parent) {
            this.parent = parent;
        }
 
        public TraceHystrixTraceCallable(Tracer tracer, TraceKeys traceKeys, Callable<S> callable) {
            this.tracer = tracer;
            this.traceKeys = traceKeys;
            this.callable = callable;
            this.parent = tracer.getCurrentSpan();
            this.clientIp = MDC.get(ConstantsName.CLIENT_IP);
        }
 
        @Override
        public S call() throws Exception  {
            Span span = this.parent;
            boolean created = false;
            MDC.put(ConstantsName.CLIENT_IP,this.clientIp);
            if (span != null) {
                span = this.tracer.continueSpan(span);
                if (log.isDebugEnabled()) {
                    log.debug("Continuing span " + span);
                }
            } else {
                span = this.tracer.createSpan(HYSTRIX_COMPONENT);
                created = true;
                if (log.isDebugEnabled()) {
                    log.debug("Creating new span " + span);
                }
            }
            if (!span.tags().containsKey(Span.SPAN_LOCAL_COMPONENT_TAG_NAME)) {
                this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT);
            }
            String asyncKey = this.traceKeys.getAsync().getPrefix()
                    + this.traceKeys.getAsync().getThreadNameKey();
            if (!span.tags().containsKey(asyncKey)) {
                this.tracer.addTag(asyncKey, Thread.currentThread().getName());
            }
            try {
                return this.callable.call();
            } finally {
                MDC.remove(ConstantsName.CLIENT_IP);
                if (created) {
                    if (log.isDebugEnabled()) {
                        log.debug("Closing span since it was created" + span);
                    }
                    this.tracer.close(span);
                } else if (this.tracer.isTracing()) {
                    if (log.isDebugEnabled()) {
                        log.debug("Detaching span since it was continued " + span);
                    }
                    this.tracer.detach(span);
                }
            }
        }
 
    }
 
}
 在異步線程中發起http請求前,將信息添加到header中
hystrix在進行發送前會經過RequestInterceptor對請求信息進行預處理(見SynchronousMethodHandler->executeAndDecode->targetRequest方法)。所以,除了重寫Feign.Client方法外,最簡單的方式就是添加一個RequestInterceptor,在RequestInterceptor中將額外的跟蹤信息添加到http頭中。





源碼:

package com.yao.springcloud.feign;
 
import feign.RequestInterceptor;
import feign.RequestTemplate;
 
/**
 * Created by micat707 on 2017/6/5.
 */
public class TraceFeignRequestInterceptor implements RequestInterceptor  {
 
    /**
     *
     * apply:
     *
     * @return void
     * @author:micat707
     * @date:2017年11月4日 下午4:53:59
     */
    @Override
    public void apply(RequestTemplate template) {
        TraceFeignRequestTemplateInjector.inject(template);
    }
}
package com.yao.springcloud.feign;
 
import com.yao.springcloud.ConstantsName;
import feign.RequestTemplate;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
 
 
public class TraceFeignRequestTemplateInjector {
 
 
    public static void inject(RequestTemplate carrier) {
        String clientIp = MDC.get("CLIENT_IP");
        setHeader(carrier, ConstantsName.CLIENT_IP, clientIp);
    }
 
 
    protected static void setHeader(RequestTemplate request, String name,
                                    String value) {
        if (StringUtils.hasText(value) && !request.headers().containsKey(name)) {
            request.header(name, value);
        }
    }
}
 


源碼地址:https://github.com/micat707/myprojects/tree/master/spring-cloud-solutions

java

相關文章
相關標籤/搜索