玩個錘子,兩小時擼完日誌鏈路串連方案

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

背景

最近接手了個專案,由於專案沒人維護,又需要對功能進行大改,開發過程中對介面進行自測,在啟動專案Dedug時,我一看控制檯日誌,蒙了,日誌的列印沒有上下文關係,完全沒法清晰地看整個請求鏈路的日誌。
看了下專案依賴,好在只有rest和mq相關的模組,如果多了rpc,還得把rpc的也串起來。雖然不是俺們的專案,基建不搞後期維護起來也挺難受的,腦袋一拍,也就兩小時的活。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

方案

不同模組之間的日誌想要串聯,需要有一個唯一標識:暫時把這個鏈路標識定為traceId,所以如果一個請求或者一個事物入口就生成一個唯一的traceId沿著鏈路一直傳遞給下游,列印日誌的時候把這個traceId打印出來,那麼上下游的日誌都能清晰可見了。好,開幹。

一、Rest模組

這個比較好做,只需要在log模組輸出日誌時獲取到上游或者前端傳過來的traceId資訊並列印即可。這裡可以選擇透過AOP的方式或者透過一些日誌實現自帶的Convert來實現,我這裡用log4j2的LogEventPatternConvert來做,比較簡單:
日誌配置輸出格式(已經配置列印traceId引數):

[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level][%C:%L][%traceId] %m%n

1、先定義一個TraceMessage
@Getter
@Setter
publicclassTraceMessageextendsParameterizedMessage

{

private

 String traceId;

publicTraceMessage(String traceId, String spanId, String messagePattern, Object... arguments)

{

super

(messagePattern, arguments);

this

.traceId = traceId;

    }

}

2、TraceMessageFactory繼承Log4j的MessageFactory工廠類,重寫newMessage方法
publicclassTraceMessageFactoryextendsAbstractMessageFactory

{

publicTraceMessageFactory()

{

    }

@Override
public Message newMessage(String message, Object... params)

{

//..這裡透過你的方式獲取從上游傳過來的那個traceId引數, 生成一個自定義的TraceMessage

        String traceId = 

"..."
returnnew

 TraceMessage(traceId,  message, params);

    }

@Override
public Message newMessage(CharSequence message)

{

return

 newMessage(message);

    }

@Override
public Message newMessage(Object message)

{

returnsuper

.newMessage(message);

    }

@Override
public Message newMessage(String message)

{

return

 newMessage(message, 

null

);

    }

}

3、再實現一個Log4j的Convert外掛就可以了
@Plugin

(name = 

"TraceIdPatternConverter"

, category = PatternConverter.CATEGORY)

@ConverterKeys

({

"traceId"

})

publicclassTraceIdPatternConverterextendsLogEventPatternConverter

{

privateTraceIdPatternConverter(String name, String style)

{

super

(name, style);

    }

publicstatic TraceIdPatternConverter newInstance()

{

returnnew

 TraceIdPatternConverter(

"TraceIdPatternConverter"

"TraceIdPatternConverter"

);

    }

@Override
publicvoidformat(LogEvent event, StringBuilder toAppendTo)

{

        Message message = event.getMessage();

if

 (message 

instanceof

 TraceMessage) {

            TraceMessage traceMessage = (TraceMessage) message;

            toAppendTo.append(

"["

 + ObjectUtil.defaultIfBlank(traceMessage.getTraceId(), 

""

) + 

"]"

)

return

;

        }

        toAppendTo.append(

"~"

);

    }

}

二、MQ模組

mq處理起來也比較簡單,以rocketMq為例,作為mq的消費端,因為mq訊息過來時有自帶的msgId,日誌列印的時候也把msgId打印出來方便與mq管理後臺關聯,因為mq訊息透傳traceId比較麻煩,因此這裡直接把traceId替換成mq的msgId即可。這裡加了個切面,為了在mq訊息消費之前列印msgId
這裡使用MDC儲存traceId,以便傳遞給log4j,當然也可以用LogContext物件傳遞值
@Slf

4j

@Aspect
@Component
publicclassLogRocketMQAspect

{

@Pointcut

(

"execution(* org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently.consumeMessage(..))"

)

publicvoidpointCut()

{

    }

@Around

(

"pointCut()"

)

public Object injectTraceId(ProceedingJoinPoint proceedingJoinPoint)throws Throwable 

{

try

 {

if

 (proceedingJoinPoint.getSignature().getName().equals(

"consumeMessage"

)) {

                List<MessageExt> messageExtList = (List<MessageExt>) proceedingJoinPoint.getArgs()[

0

];

                String messageId = messageExtList.stream().map(MessageExt::getMsgId).collect(Collectors.joining(

"-"

));

                MDC.put(

"msgId"

, messageId);

            }

return

 proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());

        } 

finally

 {

            MDC.clear();

        }

    }

}

這裡先獲取msgId,然後作為traceId的值
@Plugin

(name = 

"TraceIdPatternConverter"

, category = PatternConverter.CATEGORY)

@ConverterKeys

({

"traceId"

})

publicclassTraceIdPatternConverterextendsLogEventPatternConverter

{

privateTraceIdPatternConverter(String name, String style)

{

super

(name, style);

    }

publicstatic TraceIdPatternConverter newInstance()

{

returnnew

 TraceIdPatternConverter(

"TraceIdPatternConverter"

"TraceIdPatternConverter"

);

    }

@Override
publicvoidformat(LogEvent event, StringBuilder toAppendTo)

{

        Message message = event.getMessage();

if

 (message 

instanceof

 TraceMessage) {

            TraceMessage traceMessage = (TraceMessage) message;

            toAppendTo.append(StringUtils.isBlank(msgId) ? 

"["

 + ObjectUtil.defaultIfBlank(traceMessage.getTraceId(), 

""

) + 

"]"

 : 

"["

 + msgId + 

"]"

)

return

;

        }

        toAppendTo.append(

"~"

);

    }

}

三、RPC模組

雖然該專案中沒有RPC模組,這裡提供dubbo接入的參考,主要是透過把traceId放入dubbo的RCPContextattachment
@Activate

(order = 

99

, group = {Constants.PROVIDER_PROTOCOL, Constants.CONSUMER_PROTOCOL})

publicclassLogAttachmentFilterimplementsFilter

{

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation)throws RpcException 

{

        RpcContext context = RpcContext.getContext();

if

 (context.isConsumerSide()) {

//這裡是從上游獲取的已經設定好的traceId,透過你的方式拿到

            String traceId = 

"..."

;

if

 (StringUtils.isBlank(traceId)) {

                traceId = UuidUtils.getUuid();

            }

            context.setAttachment(

"traceId"

, traceId);

        } 

elseif

 (context.isProviderSide()) {

//此處透過LogContext或者MDC都可設定traceId

            LogContext.setTraceId(context.getAttachment(

"traceId"

));

        }

return

 invoker.invoke(invocation);

    }

}

基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

結尾

一波整頓後,鏈路日誌就被刷刷的串聯起來了。終於舒服了~~~~

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章