隨着應用愈來愈複雜,依賴愈來愈多,日誌系統愈來愈混亂,有時會出現一些奇怪的日誌,好比:html
[] [] [] No credential found
那麼怎樣排查這些奇怪的日誌從哪裏打印出來的呢?由於搞不清楚是什麼logger打印出來的,因此想定位就比較頭疼。java
下面介紹用arthas的redefine命令快速定位奇怪日誌來源。git
首先在java代碼裏,字符串拼接基本都是經過StringBuilder
來實現的。好比下面的代碼:github
public static String hello(String world) { return "hello " + world; }
實際上生成的字節碼也是用StringBuilder
來拼接的:app
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
的代碼來檢測到日誌來源。ide
StringBuilder.toString()
的原生實現是:oop
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
修改成:ui
@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
時打印出當前棧,這樣子就能夠定位日誌輸出來源了。spa
其實很簡單,在IDE裏把StringBuilder
的代碼複製一份,而後貼到任意一個工程裏,而後編繹便可。debug
也能夠直接用javac來編繹:
javac StringBuilder.java
啓動應用後,在奇怪日誌輸出以前,先使用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
的日誌。