2. 埋点数据采集需求:请求URL、方法、耗时、状态码、异常信息、时间戳
埋点数据的质量直接决定后续分析的准确性。本章系统拆解六个核心埋点字段的采集需求,明确每个字段的数据来源、采集时机和边界情况处理,并给出基于 OkHttp Interceptor 的标准实现框架。
2.1 埋点字段定义与采集规则
| 字段 | 数据类型 | 来源对象 | 采集时机 | 边界说明 |
|---|---|---|---|---|
| 请求 URL | String |
Request.url() |
请求发出前 | 保留完整路径和 Query,不包含 Fragment |
| 请求方法 | String |
Request.method() |
请求发出前 | 标准 HTTP 方法:GET/POST/PUT/DELETE/PATCH |
| 耗时 | long (ms) |
时间差计算 | 响应返回后 | 包含 DNS 解析、连接、TLS 握手、请求发送、响应接收全部时间 |
| 状态码 | int |
Response.code() |
正常响应后 | 含 200–5xx;注意 304 等缓存码也需记录 |
| 异常信息 | String/null |
Throwable |
异常抛出时 | 仅当网络/协议异常时有值;业务错误码归入状态码 |
| 时间戳 | long (epoch ms) |
System.currentTimeMillis() |
请求开始时 | 使用客户端本地时间,避免依赖服务器返回时间 |
设计原则:所有埋点字段必须在 Interceptor 的
intercept() 方法单次调用内完成采集,保证数据原子性。避免跨线程或异步回传时间戳导致的偏差。
2.2 采集流程与拦截器实现骨架
流程关键点:
- 耗时计算必须使用
System.nanoTime()而非currentTimeMillis(),避免系统时间跳变引起的负值或偏差。 - 异常处理应在
try块外声明Request request和long startNs,确保catch块也能访问到请求信息。
2.3 代码示例:基础拦截器埋点实现
public class TracingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startNs = System.nanoTime();
long timestamp = System.currentTimeMillis();
try {
Response response = chain.proceed(request);
long elapsedMs = (System.nanoTime() - startNs) / 1_000_000;
// 组装埋点数据
TraceData data = new TraceData.Builder()
.url(request.url().toString())
.method(request.method())
.costMs(elapsedMs)
.statusCode(response.code())
.exception(null)
.timestamp(timestamp)
.build();
// 输出或发送 data(可在此处存储或上报)
return response;
} catch (IOException e) {
long elapsedMs = (System.nanoTime() - startNs) / 1_000_000;
TraceData data = new TraceData.Builder()
.url(request.url().toString())
.method(request.method())
.costMs(elapsedMs)
.statusCode(0) // 网络异常无状态码
.exception(e.getClass().getName() + ": " + e.getMessage())
.timestamp(timestamp)
.build();
throw e; // 必须重新抛出,保持 OkHttp 的异常传递链
}
}
}
注意:异常分支中
statusCode 不能设为 null,约定使用 0 表示无状态码;exception 字段在正常返回时为 null,异常时为非空字符串。这样下游分析逻辑可以统一按 statusCode == 0 判断网络异常。
2.4 数据模型定义(Java POJO)
public class TraceData {
private final String url;
private final String method;
private final long costMs;
private final int statusCode;
private final String exception;
private final long timestamp;
private TraceData(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.costMs = builder.costMs;
this.statusCode = builder.statusCode;
this.exception = builder.exception;
this.timestamp = builder.timestamp;
}
// Builder 模式可避免构造方法参数过多,详见附录
public static class Builder { /* 省略具体实现 */ }
}
最佳实践:将
TraceData 设计为不可变对象,确保后续多线程上报场景下的线程安全。Builder 模式同时支持可选字段扩展(如后续新增 payloadSize 字段)。