Arthas實踐--使用redefine排查應用奇怪的日誌來源

背景

隨着應用越來越複雜,依賴越來越多,日誌系統越來越混亂,有時會出現一些奇怪的日誌,比如:

[] [] [] No credential found

那麼怎樣排查這些奇怪的日誌從哪裏打印出來的呢?因爲搞不清楚是什麼logger打印出來的,所以想定位就比較頭疼。

下面介紹用arthas的redefine命令快速定位奇怪日誌來源。

修改StringBuilder

首先在java代碼裏,字符串拼接基本都是通過StringBuilder來實現的。比如下面的代碼:

    public static String hello(String world) {
        return "hello " + world;
    }

實際上生成的字節碼也是用StringBuilder來拼接的:

  public static java.lang.String hello(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #22                 // class java/lang/StringBuilder
         3: dup
         4: ldc           #24                 // String hello
         6: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
         9: aload_0
        10: invokevirtual #29                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        13: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        16: areturn

在java的logger系統裏,輸出日誌時通常也是StringBuilder來實現的,最終會調用StringBuilder.toString(),那麼我們可以修改StringBuilder的代碼來檢測到日誌來源。

StringBuilder.toString() 的原生實現是:

    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

修改爲:

    @Override
    public String toString() {
        // Create a copy, don't share the array
        String result = new String(value, 0, count);
        if(result.contains("No credential found")) {
            System.err.println(result);
            new Throwable().printStackTrace();
        }
        return result;
    }

增加的邏輯是:當String裏包含No credential found時打印出當前棧,這樣子就可以定位日誌輸出來源了。

編繹修改過的StringBuilder

其實很簡單,在IDE裏把StringBuilder的代碼複製一份,然後貼到任意一個工程裏,然後編繹即可。

也可以直接用javac來編繹:

javac StringBuilder.java

啓動應用,使用Arthas redefine修改過的StringBuilder

啓動應用後,在奇怪日誌輸出之前,先使用arthas attach應用,再redefine StringBuilder:

$ redefine -p /tmp/StringBuilder.class
redefine success, size: 1

當執行到輸出[] [] [] No credential found的logger代碼時,會打印當前棧。實際運行結果是:

[] [] [] No credential found
java.lang.Throwable
    at java.lang.StringBuilder.toString(StringBuilder.java:410)
    at com.taobao.middleware.logger.util.MessageUtil.getMessage(MessageUtil.java:26)
    at com.taobao.middleware.logger.util.MessageUtil.getMessage(MessageUtil.java:15)
    at com.taobao.middleware.logger.slf4j.Slf4jLogger.info(Slf4jLogger.java:77)
    at com.taobao.spas.sdk.common.log.SpasLogger.info(SpasLogger.java:18)
    at com.taobao.spas.sdk.client.identity.CredentialWatcher.loadCredential(CredentialWatcher.java:128)
    at com.taobao.spas.sdk.client.identity.CredentialWatcher.access$200(CredentialWatcher.java:18)
    at com.taobao.spas.sdk.client.identity.CredentialWatcher$1.run(CredentialWatcher.java:58)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

可以看到是spas.sdk打印出了[] [] [] No credential found的日誌。

總結

  • logger最終會用StringBuilder來輸出
  • 修改StringBuilder來定位輸出特定日誌的地方
  • 使用Arthas redefine命令來加載修改過的StringBuilder
  • redefine命令實際上實現了任意代碼線上debug的功能,可以隨意本地修改代碼重新編繹,然後線上redefine加載
  • redefine的功能過於強大,所以請小心使用:)

Arthas實踐系列

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章