Arthas 实践--使用 redefine 排查应用奇怪的日志来源

2018-10-24 00:44:31 +08:00
 hengyunabc

背景

随着应用越来越复杂,依赖越来越多,日志系统越来越混乱,有时会出现一些奇怪的日志,比如:

[] [] [] 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的日志。

总结

1454 次点击
所在节点    Java
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/500447

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX