动态

详情 返回 返回

研發排查問題的利器:一款方法調用棧跟蹤工具 - 动态 详情

導語

本文從日常值班問題排查痛點出發,分析方法複用的調用鏈路和上下文業務邏輯,通過思考分析,藉助棧幀開發了一個方法調用棧的鏈式跟蹤工具,便於展示一次請求的方法串行調用鏈,有助於快速定位代碼來源和流量入口,有效提升研發和運維排查定位效率。期望在大家面臨類似痛點時可以提供一些實踐經驗和參考,也歡迎大家合適的場景下接入使用。



現狀分析

在系統值班時,經常會有人拿着報錯截圖前來諮詢,作為值班研發,我們則需要獲取儘可能多的信息,幫助我們分析報錯場景,便於排查識別問題。

例如,下圖就是一個常見的的報錯信息截圖示例。

從圖中,我們可以初步獲取到一些信息:

•菜單名稱:變更單下架,我們這是變更單下架操作時的一個報錯提醒。

•報錯信息:序列號狀態為離庫狀態,請檢查。

•其他輔助信息:例如用户掃描或輸入的86開頭編碼,SKU、商品名稱、儲位等。

這時會有一些常見的排查思路:

1、根據提示,將用户輸入的86編碼,按照提示文案去檢查用户數據,即作為序列號編碼,去看一下序列號是否存在,是否真的是離庫了。

2、如果86編碼確實是序列號,而且真的是離庫了,那麼基本上可以快速結案了,這個86編碼確實是序列號並且是已離庫,正如提示文案所示,這時跟提問人做好解釋答疑即可。

3、如果第2步排查完,發現86編碼不是序列號編碼,或並非離庫狀態,即與提示文案不符,這時就要定位報錯文案的代碼來源,繼續查看代碼邏輯來進行判案了。(這種也比較常見,一種是報錯場景較多,但提示文案比較單一,不便於在提示文案中覆蓋所有報錯場景;另一種提示文案陳舊未跟隨需求演變而更新。這兩點可以通過細分場景細化對應的報錯文案,或更新報錯文案,使得報錯文案更優更新,但不是本文討論的重點。)

4、如何根據報錯文案快速找到代碼來源呢?一般我們會在代碼庫中搜索提示文案,或者在日誌中檢索報錯信息,輔助定位代碼來源,後者依賴於代碼中打印了該報錯信息,且日誌級別配置能夠確保該信息打印到日誌文件中。

5、倘若我們根據提示文案搜索代碼時,發現該提示文案有多處代碼出現,此時就較為複雜了,我們需要進一步識別,哪個才與本次報錯直接有關。

在這裏插入圖片描述

每個方法向上追溯,又發現調用來源眾多:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在業務複雜的系統中,方法複用比較常見,不同的上下文和參數傳遞,也有着不同的業務邏輯判斷和走向。

這時,基本上進入到本文要討論的痛點:如何根據有限的提示信息快速定位代碼來源?以便於分析報錯業務場景,答疑解惑或快速處理問題。

屏幕前的小夥伴,如果你也經常值班排查問題,應該也會有類似的痛點所在。

啓發

這是我想到了Exception異常機制,作為一名Coder,我們對異常堆棧再熟悉不過了,異常堆棧是一個“可愛”又“可恨”的東西,“可愛”在於異常堆棧確實可以幫助我們快速定位問題所在,“可恨”在於有異常基本上就是有問題,堆棧讓我們審美疲勞,累覺不愛。

下面是一個Java語言的異常堆棧信息示例:

在這裏插入圖片描述



異常類體系和異常處理機制在本文中不是重點,不做過多贅述,本文重點希望能從異常堆棧中獲取一些啓發。

讓我們近距離再觀察一下我們的老朋友。

在異常堆棧信息中,主要有四類信息:

•全限定類名

•方法名

•文件名

•代碼行號

這四類信息可以幫助我們有效定位代碼來源,而且堆棧中記錄行先後順序,也表示着異常發生的第一現場、第二現場、第三現場、……,以此傳遞。

這讓我想起了JVM方法棧中的棧幀。

每當一個方法被調用時,JVM會為該方法創建一個新的棧幀,並將其壓入當前線程的棧(也稱為調用棧或執行棧)中。棧幀包含了方法執行所需的所有信息,包括局部變量、操作數棧、常量池引用等。

在這裏插入圖片描述

思路

從Java中的Throwable中,可以看到staceTrace的get和set,這個StackTraceElement數組裏面存放的信息就是我們在異常堆棧中經常看到的信息。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述



再次放一下這張圖,方便對照着看。

在這裏插入圖片描述



StackTraceElement類的註釋中赫然寫着:

StackTraceElement represents a stack frame.

對,StackTraceElement代表着一個棧幀。

這個StackTraceElement就是我要找的東西,即使非異常情況下,每個線程在執行方法調用時都會記錄棧幀信息。

在這裏插入圖片描述

按照方法調用先後順序,將調用棧中方法依次串聯起來,就像糖葫蘆一樣,就可以得到我想要的方法調用鏈了。

NEXT,我可以動工寫個工具了。

工具開發

工具的核心代碼並不複雜,StackTraceElement 也是 Java JDK 中現成的,我所做的工作主要是從中過濾出必要的信息,加工簡化成,按照順序整理成鏈式信息,方便我們一眼就可以看出來方法的調用鏈。

在這裏插入圖片描述

入參介紹

pretty: 表示是隻拼接類和方法,不拼接文件名和行號,非 pretty 是四個都會拼接。

simple: 表示會過濾一些我們代碼中場景的代理增強出來的方法的信息輸出。

specifiedPrefix: 指定保留相應的包路徑堆棧信息,去掉一些過多的中間件信息。

其他還會過濾一些常見代理的堆棧信息:

•FastClassBySpringCGLIB

•EnhancerBySpringCGLIB

•lambda$

•Aspect

•Interceptor

對此,還封裝了一些默認參數的方法,使用起來更為方便。

在這裏插入圖片描述



還有一些其他工具方法也可以使用:


在這裏插入圖片描述

使用效果

1、不過濾中間件、代理增強方法的調用棧信息

Thread#run ==> ThreadPoolExecutor$Worker#run ==> ThreadPoolExecutor#runWorker ==> BaseTask#run ==> JSFTask#doRun ==> ProviderProxyInvoker#invoke ==> FilterChain#invoke ==> SystemTimeCheckFilter#invoke ==> ProviderExceptionFilter#invoke ==> ProviderContextFilter#invoke ==> InstMethodsInter#intercept ==> ProviderContextFilter$eone$auxiliary$9f9kd21#call ==> ProviderContextFilter#eone$original$invoke$p882ot3$accessor$eone$pclcbe2 ==> ProviderContextFilter#eone$original$invoke$p882ot3 ==> ProviderGenericFilter#invoke ==> ProviderUnitValidationFilter#invoke ==> ProviderHttpGWFilter#invoke ==> ProviderInvokeLimitFilter#invoke ==> ProviderMethodCheckFilter#invoke ==> ProviderTimeoutFilter#invoke ==> ValidationFilter#invoke ==> ProviderConcurrentsFilter#invoke ==> ProviderSecurityFilter#invoke ==> WmsRpcExceptionFilter#invoke ==> WmsRpcExceptionFilter#invoke4provider ==> AdmissionControlJsfFilter#invoke ==> AdmissionControlJsfFilter#providerSide ==> AdmissionControlJsfFilter#processRequest ==> ChainedDeadlineJsfFilter#invoke ==> ChainedDeadlineJsfFilter#providerSide ==> JsfPerformanceMonitor#invoke ==> AbstractMiddlewarePerformanceMonitor#doExecute ==> PerformanceMonitorTemplateComposite#execute ==> PerformanceMonitorTemplateComposite#lambda$execute$0 ==> PerformanceMonitorTemplateUmp#execute ==> PerformanceMonitorTemplateComposite#lambda$execute$0 ==> PerformanceMonitorTemplatePayload#execute ==> JsfPerformanceMonitor#lambda$invoke$0 ==> JsfPerformanceMonitor#doInvoke ==> ProviderInvokeFilter#invoke ==> ProviderInvokeFilter#reflectInvoke ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor1704#invoke ==> CglibAopProxy$DynamicAdvisedInterceptor#intercept ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> ExposeInvocationInterceptor#invoke ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor344#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor1276#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor868#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor869#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor1642#invoke ==> MagicAspect#magic ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> CglibAopProxy$CglibMethodInvocation#invokeJoinpoint ==> MethodProxy#invoke ==> CglibAopProxy$DynamicAdvisedInterceptor#intercept ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> ExposeInvocationInterceptor#invoke ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor868#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor869#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> CglibAopProxy$CglibMethodInvocation#invokeJoinpoint ==> MethodProxy#invoke ==> CglibAopProxy$DynamicAdvisedInterceptor#intercept ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor1295#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> ExposeInvocationInterceptor#invoke ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor868#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> CglibAopProxy$CglibMethodInvocation#invokeJoinpoint ==> MethodProxy#invoke ==> CglibAopProxy$DynamicAdvisedInterceptor#intercept ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> ExposeInvocationInterceptor#invoke ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> CglibAopProxy$CglibMethodInvocation#invokeJoinpoint ==> MethodProxy#invoke ==> CglibAopProxy$DynamicAdvisedInterceptor#intercept ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AnnotationAwareRetryOperationsInterceptor#invoke ==> RetryOperationsInterceptor#invoke ==> RetryTemplate#execute ==> RetryTemplate#doExecute ==> RetryOperationsInterceptor$1#doWithRetry ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> CglibAopProxy$CglibMethodInvocation#invokeJoinpoint ==> MethodProxy#invoke ==> CglibAopProxy$DynamicAdvisedInterceptor#intercept ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> ExposeInvocationInterceptor#invoke ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor1276#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> TransactionInterceptor#invoke ==> TransactionAspectSupport#invokeWithinTransaction ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> AspectJAroundAdvice#invoke ==> AbstractAspectJAdvice#invokeAdviceMethod ==> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs ==> Method#invoke ==> DelegatingMethodAccessorImpl#invoke ==> GeneratedMethodAccessor869#invoke ==> MethodInvocationProceedingJoinPoint#proceed ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> PersistenceExceptionTranslationInterceptor#invoke ==> CglibAopProxy$CglibMethodInvocation#proceed ==> ReflectiveMethodInvocation#proceed ==> CglibAopProxy$CglibMethodInvocation#invokeJoinpoint ==> MethodProxy#invoke ==> StackTraceUtils#trace

2、指定包路徑過濾中間件後的調用棧棧信息

LockAspect#lock ==> StockTransferAppServiceImpl#increaseStock ==> MonitorAspect#monitor ==> StockRetryExecutor#operateStock ==> StockRetryExecutor$FastClassBySpringCGLIB$5188d6e#invoke ==> BaseStockOperation$FastClassBySpringCGLIB$9d76cd9a#invoke ==> StockTransferServiceImpl$FastClassBySpringCGLIB$85bb181e#invoke ==> ValidationAspect#logAndReturn ==> LogAspect#log ==> ThreadLocalRemovalAspect#removal ==> ValidationAspect#validate ==> BaseStockOperation#go ==> StockRepositoryImpl$EnhancerBySpringCGLIB$1388ef12#operateStock ==> StockTransferAppServiceImpl$EnhancerBySpringCGLIB$1095eafa#increaseStock ==> StockRepositoryImpl$FastClassBySpringCGLIB$a1b4dae4#invoke ==> StockTransferServiceImpl#increaseStock ==> DataBaseExecutor#execute ==> StockRetryExecutor$EnhancerBySpringCGLIB$b42789a#operateStock ==> StockInitializer$EnhancerBySpringCGLIB$85faf510#go ==> StockTransferServiceImpl$EnhancerBySpringCGLIB$afc21975#increaseStock ==> StockRepositoryImpl#operateStock ==> DataBaseExecutor#operate ==> StockTransferAppServiceImpl$FastClassBySpringCGLIB$e348d8e1#invoke

3、去掉Spring代理增強之後的調用棧信息

LogAspect#log ==> LockAspect#lock ==> ValidationAspect#validate ==> ValidationAspect#logAndReturn ==> MonitorAspect#monitor ==> StockTransferAppServiceImpl#decreaseStock ==> ThreadLocalRemovalAspect#removal ==> StockTransferServiceImpl#decreaseStock ==> StockOperationLoader#go ==> BaseStockOperation#go ==> DataBaseExecutor#operate ==> DataBaseExecutor#execute ==> StockRetryExecutor#operateStock ==> StockRepositoryImpl#operateStock

4、去掉一些自定義代理之後的調用棧棧信息

StockTransferAppServiceImpl#increaseStock ==> StockTransferServiceImpl#increaseStock ==> BaseStockOperation#go ==> DataBaseExecutor#operate ==> DataBaseExecutor#execute ==> StockRetryExecutor#operateStock ==> StockRepositoryImpl#operateStock

5、如果帶上文件名和行號後的調用棧棧信息

StockTransferAppServiceImpl#increaseStock(StockTransferAppServiceImpl.java:103) ==> StockTransferServiceImpl#increaseStock(StockTransferServiceImpl.java:168) ==> BaseStockOperation#go(BaseStockOperation.java:152) ==> BaseStockOperation#go(BaseStockOperation.java:181) ==> BaseStockOperation#go(BaseStockOperation.java:172) ==> DataBaseExecutor#operate(DataBaseExecutor.java:34) ==> DataBaseExecutor#operate(DataBaseExecutor.java:64) ==> DataBaseExecutor#execute(DataBaseExecutor.java:79) ==> StockRetryExecutor#operateStock(StockRetryExecutor.java:64) ==> StockRepositoryImpl#operateStock(StockRepositoryImpl.java:303)



線上應用實踐

在這裏插入圖片描述

接入方法調用棧跟蹤工具後,根據報錯提示詞,可以檢索到對應日誌,從 ImmediateTransferController#offShelf ==> AopConfig#pointApiExpression ==> TransferOffShelfAppServiceImpl#offShelf ==> TransferOffShelfAppServiceImpl#doOffShelf 中順藤摸瓜可以快速找到流量入口的代碼位置。

在這裏插入圖片描述
在這裏插入圖片描述



適用場景

該方法調用棧工具類,可以在一些堆棧信息進行輔助排查分析的地方進行預埋,例如:

•業務異常時輸出堆棧到日誌信息中。

•業務監控告警信息中加入調用棧信息。

•一些複用方法調用複雜場景下,打印調用棧信息,展示調用鏈,方便分析。

•其他一些場景等。

延伸

在《如何一眼定位SQL的代碼來源:一款SQL染色標記的簡易MyBatis插件》一文中,我發佈了一款SQL染色插件,該插件目前已有statementId信息,還支持通過SQLMarkingThreadLocal傳遞自定義附加信息。其他BGBU的技術小夥伴,也有呼聲,希望在statementId基礎上可以繼續追溯入口方法。通過本文引入的方法調用棧跟蹤工具,我在SQL染色插件中增加了方法調用棧染色信息。

SQL染色工具新版特性,歡迎大家先在TEST和UAT環境嚐鮮試用,TEST和UAT環境驗證沒問題後,再逐步推廣正式環境。

升級方法:

1、sword-mybatis-plugins版本升級至1.0.8-SNAPSHOT。

2、同時新引入本文的工具依賴

<!-- http://sd.jd.com/article/45616?shareId=105168&isHideShareButton=1 -->
<dependency>
    <groupId>com.jd.sword</groupId>
    <artifactId>sword-utils-common</artifactId>
    <version>1.0.3-SNAPSHOT</version>
</dependency>

3、mybatis config xml 配置文件按最新配置調整

<!-- http://sd.jd.com/article/42942?shareId=105168&isHideShareButton=1 -->
<!-- SQLMarking Plugin,放在第一個Plugin的位置,不影響其他組件,但不強要求位置,也可以靈活調整順序位置 -->
<plugin interceptor="com.jd.sword.mybatis.plugin.sql.SQLMarkingInterceptor">
    <!-- 是否開啓SQL染色標記插件 -->
    <property name="enabled" value="true"/>
    <!-- 是否開啓方法調用棧跟蹤 -->
    <property name="stackTraceEnabled" value="true"/>
    <!-- 指定需要方法調用棧跟蹤的package,減少信息量,value配置為自己工程的package路徑,多個路徑用英文逗號分割 -->
    <property name="specifiedStackTracePackages" value="com.jdwl.wms.stock"/>
    <!-- 忽略而不進行方法堆棧跟蹤的類名列表,多個用英文逗號分割,減少信息量 -->
    <property name="ignoredStackTraceClassNames" value=""/>
    <!-- 結合CPU利用率和性能考慮,方法調用棧跟蹤採集率配置採集率,配置示例: m/n,表示n個裏面抽m個進行採集跟蹤 -->
    <!-- 預發環境和測試環境可以配置全採集,例如配置1/1,生產環境可以結合CPU利用率和性能考慮按需配置採集率 -->
    <property name="stackTraceSamplingRate" value="1/2"/>
    <!-- 是否允許SQL染色標記作為前綴,默認false表示僅作為後綴 -->
    <property name="startsWithMarkingAllowed" value="false"/>
    <!-- 方法調用棧跟蹤最大深度,減少信息量 -->
    <property name="maxStackDepth" value="10"/>
</plugin>



或代碼配置方式

/**
     * SQLMarking Plugin
     * http://sd.jd.com/article/42942?shareId=105168&isHideShareButton=1
     *
     * @return
     */
    @Bean
    public SQLMarkingInterceptor sQLMarkingInterceptor() {
        SQLMarkingInterceptor sQLMarkingInterceptor = new SQLMarkingInterceptor();
        Properties properties = new Properties();
        // 是否開啓SQL染色標記插件
        properties.setProperty("enabled", "true");
        // 是否開啓方法調用棧跟蹤
        properties.setProperty("stackTraceEnabled", "true");
        // 指定需要方法調用棧跟蹤的package,減少信息量,value配置為自己工程的package路徑,多個路徑用英文逗號分割
        properties.setProperty("specifiedStackTracePackages", "com.jdwl.wms.picking");
        // 結合CPU利用率和性能考慮,方法調用棧跟蹤採集率配置採集率,配置示例: m/n,表示n個裏面抽m個進行採集跟蹤
        // 預發環境和測試環境可以配置全採集,例如配置1/1,生產環境可以結合CPU利用率和性能考慮按需配置採集率
        properties.setProperty("stackTraceSamplingRate", "1/2");
        // 是否允許SQL染色標記作為前綴,默認false表示僅作為後綴
        properties.setProperty("startsWithMarkingAllowed", "false");
        sQLMarkingInterceptor.setProperties(properties);
        return sQLMarkingInterceptor;
    }

接入效果

SELECT
    id,
    tenant_code,
    warehouse_no,
    sku,
    location_no,
    container_level_1,
    container_level_2,
    lot_no,
    sku_level,
    owner_no,
    pack_code,
    conversion_rate,
    stock_qty,
    prepicked_qty,
    premoved_qty,
    frozen_qty,
    diff_qty,
    broken_qty,
    status,
    md5_value,
    version,
    create_user,
    update_user,
    create_time,
    update_time,
    extend_content
FROM
    st_stock
WHERE
    deleted = 0
    AND warehouse_no = ?
    AND location_no IN(?)
    AND container_level_1 IN(?)
    AND container_level_2 IN(?)
    AND sku IN(?)
    /* [SQLMarking] statementId: com.jdwl.wms.stock.infrastructure.jdbc.main.dao.StockQueryDao.selectExtendedStockByLocation, stackTrace: BaseJmqConsumer#onMessage ==> StockInfoConsumer#handle ==> StockInfoConsumer#handleEvent ==> StockExtendContentFiller#fillExtendContent ==> StockInitializer#queryStockByWarehouse ==> StockInitializer#batchQueryStockByWarehouse ==> StockInitializer#queryByLocationAndSku ==> StockQueryRepositoryImpl#queryExtendedStockByLocationAndSku, warehouseNo: 6_6_601 */

如何接入本文工具?

如果小夥伴也有類似使用訴求,大家可以先在測試、UAT環境接入試用,然後再逐步推廣線上生產環境。

1、新引入本文的工具依賴

<dependency>
    <groupId>com.jd.sword</groupId>
    <artifactId>sword-utils-common</artifactId>
    <version>1.0.3-SNAPSHOT</version>
</dependency>

2、使用工具類靜態方法

com.jd.sword.utils.common.runtime.StackTraceUtils#simpleTrace()

<!---->

com.jd.sword.utils.common.runtime.StackTraceUtils#simpleTrace(java.lang.String...)

<!---->

com.jd.sword.utils.common.runtime.StackTraceUtils#trace()

<!---->

com.jd.sword.utils.common.runtime.StackTraceUtils#trace(java.lang.String...)

<!---->

com.jd.sword.utils.common.runtime.StackTraceUtils#trace(boolean)

<!---->

com.jd.sword.utils.common.runtime.StackTraceUtils#trace(boolean, boolean, java.lang.String...)
user avatar u_16018702 头像 aijianshendexuegao 头像 yian 头像 yanyue404 头像 leoyi 头像 enaium 头像 beishangdeniuroumian 头像 uname67 头像 huangxincheng 头像 java_3y 头像 sigui_5f58bd7ced379 头像 yugu 头像
点赞 12 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.