日志追蹤:log增加traceId

開發(fā)中經(jīng)常需要根據(jù)日志排查問題或跟蹤調(diào)用流程磷脯,很多業(yè)務(wù)日志并沒有考慮排查問題時的便利性柠衅,看似都記錄了日志氛赐,但同一個請求鏈路的日志無法對應(yīng)筷登,特別是當(dāng)日志跨服務(wù)時候剃根,或者同一個業(yè)務(wù)邏輯同一時刻有多條日志,根本無法對應(yīng)起來前方,如果日志記可以追蹤的話狈醉,可以根據(jù)全局唯一id搜索得出一條調(diào)用鏈的日志,順著這個日志鏈條就可以看出程序的執(zhí)行全過程惠险,進(jìn)而有利于排查出問題苗傅。

這里我們用兩種方式實現(xiàn):

自己寫代碼實現(xiàn):輕量級,靈活班巩,可任意修改渣慕,適合小項目;目前已實現(xiàn)支持已spring為基礎(chǔ)的springboot,springcloud,dubbo且使用logback日志框架的項目。
使用開源工具:安裝費點事抱慌,功能強大逊桦,適合大項目;支持的開發(fā)語言和框架較多抑进。

1. 代碼實現(xiàn)

不想一步步的自己實現(xiàn)的話强经,有已經(jīng)封裝好的jar(建議www.search.maven.org搜最新版本):

<dependency>
    <groupId>com.wuyunonline.tracelog</groupId>
    <artifactId>tracelog-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

使用說明和源碼地址:https://gitee.com/jwb-wuyun/traclog

下面是自己實現(xiàn)的步驟

1.1 springmvc

先寫一個攔截器:

import com.tracelog.common.constant.TraceLogConstant;
import com.tracelog.common.util.TraceIdUtil;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TraceLogWebMvcInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String method = request.getMethod();
        // 放行跨域預(yù)請求
        if (method.equals("OPTIONS")) {
            return true;
        }
        String traceId = request.getHeader(TraceLogConstant.TRACE_ID);
        if (StringUtils.isEmpty(traceId)) {
            traceId = TraceIdUtil.uuid_timestamp();
        }
        MDC.put(TraceLogConstant.TRACE_ID, traceId);
        return true;
    }
}

再配置攔截器:

import com.tracelog.interceptor.TraceLogWebMvcInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class TraceLogWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(mvcInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public TraceLogWebMvcInterceptor mvcInterceptor() {
        return new TraceLogWebMvcInterceptor();
    }

}

1.2 springcloud

springcloud一般都是用feign,所以我們需要攔截feign寺渗。
先寫攔截器:

import com.tracelog.common.constant.TraceLogConstant;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.MDC;

public class TraceLogFeignInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header(TraceLogConstant.TRACE_ID, MDC.get(TraceLogConstant.TRACE_ID));

    }
}

再配置攔截器:

import com.tracelog.interceptor.TraceLogFeignInterceptor;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TraceLogFeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return new TraceLogFeignInterceptor();
    }
}

1.3 dubbo

dubbo必須是Apache的dubbo(老版的是阿里的匿情,阿里已將dubbo加入apache)
先寫過濾器:

import com.tracelog.common.constant.TraceLogConstant;
import com.tracelog.common.util.TraceIdUtil;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;

@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER})
public class TraceLogDubboFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 獲取dubbo上下文中的traceId
        String traceId = invocation.getAttachment(TraceLogConstant.TRACE_ID);
        if (StringUtils.isEmpty(traceId)) {
            // customer 獲取上游來的traceId,并設(shè)置到dubbo的上下文信殊,如果沒有則生成一個
            traceId = MDC.get(TraceLogConstant.TRACE_ID);
            if (StringUtils.isEmpty(traceId)) {
                traceId = TraceIdUtil.uuid_timestamp();
                MDC.put(TraceLogConstant.TRACE_ID, traceId);
            }
            // provider 設(shè)置traceId到日志到上下文
            invocation.setAttachment(TraceLogConstant.TRACE_ID, traceId);
        } else {
            MDC.put(TraceLogConstant.TRACE_ID, traceId);
        }
        Result result = invoker.invoke(invocation);
        return result;
    }
}

聲明過濾器:
在resource下創(chuàng)建Filter文件炬称,里面內(nèi)容:traceLogDubboFilter=.TraceLogDubboFilter,是你的實際路徑涡拘,如下圖:

3.png

1.4 注解

比如定時任務(wù)等利用AOP加切面:@Scheduled,@PostConstruct

import com.tracelog.common.constant.TraceLogConstant;
import com.tracelog.common.util.TraceIdUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TracelogAspect {

    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled) || @annotation(javax.annotation.PostConstruct)")
    public void tracelogPointCut() {
    }

    @Around("tracelogPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MDC.put(TraceLogConstant.TRACE_ID, TraceIdUtil.uuid_timestamp());
        return point.proceed();
    }

}

1.5 異步線程獲取主線程traceId

針對注解@Async转砖,需要重寫線程池,其他線程的處理可以參考這種方式。
先實現(xiàn)修飾類:

import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;

import java.util.Map;

public class TraceLogMdcTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        Map<String, String> map = MDC.getCopyOfContextMap();
        return () -> {
            try {
                if (map != null) {
                    MDC.setContextMap(map);
                }
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

再配置:

import com.tracelog.interceptor.TraceLogMdcTaskDecorator;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
public class TraceLogAsyncConfig extends AsyncConfigurerSupport {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(50);
        threadPoolTaskExecutor.setMaxPoolSize(100);
        threadPoolTaskExecutor.setQueueCapacity(1000);
        threadPoolTaskExecutor.setThreadNamePrefix("Async-");
        threadPoolTaskExecutor.setTaskDecorator(new TraceLogMdcTaskDecorator());
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }

}

1.6 擴展支持httpclinet府蔗,okhttp

使用Okhttp或HttpClient調(diào)用時候如果想傳遞traceId晋控,必須在自己的Okhttp或HttpClient中加入traclog 攔截器,如下:
Okhttp
實現(xiàn)攔截器:

import com.tracelog.common.constant.TraceLogConstant;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;

import java.io.IOException;

public class TraceLogOkhttpInterceptor implements Interceptor {

    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request request = chain.request().newBuilder()
                .addHeader(TraceLogConstant.TRACE_ID, MDC.get(TraceLogConstant.TRACE_ID))
                .build();
        return chain.proceed(request);
    }
}

使用攔截器

import com.tracelog.interceptor.TraceLogOkhttpInterceptor;
...
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new TraceLogOkhttpInterceptor()).build();

HttpClient
實現(xiàn)攔截器:

import com.tracelog.common.constant.TraceLogConstant;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.protocol.HttpContext;
import org.slf4j.MDC;

public class TraceLogHttpClientInterceptor implements HttpRequestInterceptor {

    @Override
    public void process(HttpRequest httpRequest, HttpContext httpContext){
        httpRequest.addHeader(TraceLogConstant.TRACE_ID, MDC.get(TraceLogConstant.TRACE_ID));
    }
}

使用攔截器

import com.tracelog.interceptor.TraceLogHttpClientInterceptor;
...
CloseableHttpClient httpClient = HttpClientBuilder.create().addInterceptorFirst(new TraceLogHttpClientInterceptor()).build();

1.7 配置logback.xml

項目中找到logback.xml或logback-spring.xml姓赤, 在日志格式中加入[traceId:%X{traceId}] 例如:


image.png

啟動項目赡译,查看效果,類似下圖說明成功不铆。


image.png

2. 采用第三方工具

推薦 skywalking蝌焚。

2.1 配置pom.xml

<!--skywalking traceId 記錄到logback日志,請與安裝的服務(wù)器版本對應(yīng)-->
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-trace</artifactId>
    <version>8.9.0</version>
</dependency>
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>8.9.0</version>
</dependency>

2.2 配置logback.xml

    <!-- 日志輸出格式 -->
    <property name="log.pattern" value="[%tid] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%method,%line] - %msg%n"/>

    <!-- 系統(tǒng)日志輸出 -->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <pattern>${log.pattern}</pattern>
            </layout>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

<!-- 日志收集 skywalking 8.4.0版本開始支持 -->
    <appender name="grpcLog" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
                <pattern>${log.pattern}</pattern>
            </layout>
        </encoder>
    </appender>

日志收集不是必須的誓斥,如果覺得不需要集中收集在一起只洒,那么不需要加GRPCLogClientAppender
注意下面圖中的紅色標(biāo)注

4.jpg

5.jpg

2.3 skywalking安裝和使用

教程:http://www.reibang.com/p/b69bc629f476

2.4 效果展示

確保項目中已配置,skywalking已安裝劳坑,探針也已配置毕谴。
log文件和skywalking都可以通過TID(traceId)追蹤日志鏈。
在控制臺或log文件中會打印帶TID(traceId)的log:

6.jpg

skywalking服務(wù)端會收集到帶TID(traceId)的log:


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末距芬,一起剝皮案震驚了整個濱河市涝开,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌框仔,老刑警劉巖舀武,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異离斩,居然都是意外死亡银舱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門跛梗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寻馏,“玉大人,你說我怎么就攤上這事茄袖〔偃恚” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵宪祥,是天一觀的道長聂薪。 經(jīng)常有香客問我,道長蝗羊,這世上最難降的妖魔是什么藏澳? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮耀找,結(jié)果婚禮上翔悠,老公的妹妹穿的比我還像新娘业崖。我一直安慰自己,他們只是感情好蓄愁,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布双炕。 她就那樣靜靜地躺著,像睡著了一般撮抓。 火紅的嫁衣襯著肌膚如雪妇斤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天丹拯,我揣著相機與錄音站超,去河邊找鬼。 笑死乖酬,一個胖子當(dāng)著我的面吹牛死相,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咬像,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼算撮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了施掏?” 一聲冷哼從身側(cè)響起钮惠,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤茅糜,失蹤者是張志新(化名)和其女友劉穎七芭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔑赘,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡狸驳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缩赛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耙箍。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酥馍,靈堂內(nèi)的尸體忽然破棺而出辩昆,到底是詐尸還是另有隱情,我是刑警寧澤旨袒,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布汁针,位于F島的核電站,受9級特大地震影響砚尽,放射性物質(zhì)發(fā)生泄漏施无。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一必孤、第九天 我趴在偏房一處隱蔽的房頂上張望猾骡。 院中可真熱鬧,春花似錦、人聲如沸兴想。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫂便。三九已至嘱么,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顽悼,已是汗流浹背曼振。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蔚龙,地道東北人冰评。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像木羹,于是被迫代替她去往敵國和親甲雅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容