博客地址: dim.red
项目开源: DynamicLog

背景

当线上问题出现的时候. 往往需要之前的埋点进行问题定位. 但是埋点个数是有限的. 现有的埋点往往不够排查出具体的问题. 所以我们需要一个更灵活的埋点方案. 要求是可以动态的输出任何方法的入参和出参.

原理

拦截原有的业务方法的入口和出口.

原本:

1
2
3
4
5
6
7
public class O {
private static final String TAG = "O";
public int method1(int i) {
Log.d(TAG, "method1() called with: i = [" + i + "]");
return i * i;
}
}

处理后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class O {
private static final String TAG = "O";
public static volatile MethodMonitor s_Monitor_1;
public int method1(int i) {
//方法开始
int methodId = 11;
Point point = null;
if (s_Monitor_1 != null && s_Monitor_1.hotMethodEnter(methodId)) {
point = new Point();
point.setThisObject(this);
Object[] var2 = new Object[0];
point.setArg(var2);
s_Monitor_1.methodEnter(point);
}
//原有方法执行
Log.d(TAG, "method1() called with: i = [" + i + "]");
int result = i * i;
//方法结束
if (s_Monitor_1 != null && s_Monitor_1.hotMethodReturn(0)) {
if (point == null) {
point = new Point();
point.setThisObject(this);
Object[] var3 = new Object[0];
point.setArg(var3);
}
point.setReturnObject((Integer)result);
s_Monitor_1.methodReturn(point, methodId);
}
return result;
}
}

做法:
为所有的 class 分配一个静态的 s_Monitor_1 对象. 同时为 class 下所有的方法分配一个独有的 methodId . 当 s_Monitor_1 对象不为空, methodId 匹配即命中规则.

编译过程过程:

编译过程

插件

Monitor Plugin
在编译期间进行字节码注入. 同时生成 monitorMapping.txt 用来描述方法和 methodId 的映射关系.

注: 在字节码过程中我们发现有些方法是不需要注入的. 比如 abstract , 桥接方法 和 access$方法.

服务端可以通过 mapping.txt 找到混淆以后的类名,再通过 monitorMapping.txt 获取到 methodId 两则组合成命令往客户端发送.

计划

  • 支持方法流程过滤.类似 AOP 中 cflow的概念

尾巴

用热修复的思想来做动态日志.

相关

美团点评 - Android动态日志系统Holmes