用户点了一次提交订单日志里却出现了几十条记录怎么证明它们属于同一次操作做线上问题排查的时候最头疼的就是这个用户说点了一次提交然后报错了。你去翻日志翻出来几十条记录有 UI 线程的、有异步任务的、有 Native 层的、有系统服务的。哪几条是这次操作产生的哪几条是别的操作的根本分不清。这时候才意识到光有时间戳和日志内容不够你需要一个东西把同一次操作的所有日志串起来。这个东西就是 TraceId。一、先想清楚为什么日志串不起来先把最基础的问题想明白。普通日志长这样10:00:01.234 UI 点击提交按钮 10:00:01.456 异步任务开始执行 10:00:01.789 Native 层调用系统服务 10:00:02.123 系统服务返回结果看起来是按时间排的。但问题是用户可能点了两次提交或者别的操作也在跑。你怎么知道这四条日志是同一次操作的问题说明多线程并发多个操作同时在跑跨进程UI 进程和系统服务进程跨设备手机和平板都在跑没有一个统一的 ID你根本分不清哪些日志属于同一次操作。二、ChainId、SpanId、ParentSpanId 是什么HiTraceChain 的核心就是三个 IDID作用ChainId整条调用链的 ID一次操作一个SpanId每一步操作的 IDParentSpanId上一步操作的 IDChainId 把整条链串起来。SpanId 标记每一步。ParentSpanId 告诉你这一步是从哪一步来的。这样你就能还原出完整的调用树第一步是什么第二步是什么第三步是从哪一步分支出来的。三、调用链是怎么创建和传播的完整的调用链是这样的UI 线程处理用户点击创建第一个 Span生成 ChainId调用异步任务把 ChainId 和 SpanId 传过去异步任务创建新的 SpanParentSpanId 指向上一步调用 Native 层把 ChainId 传过去Native 层再创建新的 Span调用系统服务把 ChainId 传过去系统服务再创建新的 Span。每一步都带着同一个 ChainId。这样所有日志都能串起来。这段代码解决什么问题创建调用链 Span。文件trace/TraceHelper.ets用途跨线程调用链追踪接入位置关键业务节点import{hiTraceChain}fromkit.PerformanceAnalysisKit;// 开始一个新的调用链lettraceIdhiTraceChain.begin(submitOrder,hiTraceChain.HiTraceFlag.DEFAULT);// 业务逻辑...// 异步任务传递 TraceIdsetTimeout((){// 把 TraceId 设置到当前线程hiTraceChain.setId(traceId);// 异步任务的业务逻辑...// 结束hiTraceChain.end(traceId);},100);这里最关键的是异步线程里要把 TraceId 传过去不然异步任务的日志就跟主线程断开了。四、为什么异步线程容易丢 TraceId很多人写调用链主线程都好好的一到异步就断了。为什么因为 TraceId 是存在当前线程上下文里的。主线程有新开的异步线程没有。线程有没有 TraceIdUI 主线程有begin 创建的异步任务线程没有新线程是空的Native 层没有要手动传你不手动传异步线程就不知道现在在调用链里它打出来的日志就没有 TraceId就串不起来。五、HiTraceChain 和 HiTraceMeter 有什么区别很多人搞混了这两个。不对。组件作用HiTraceChain管调用链上下文生成和传播 TraceIdHiTraceMeter管性能打点记录某一步花了多久HiTraceChain 是把日志串起来。HiTraceMeter 是记录每一步的耗时。两个配合用Chain 负责串Meter 负责量。六、几个容易踩的坑第一个坑每个函数都重新 begin 导致调用链断裂。每一层都开始新链就串不起来了。第二个坑异步线程没有传递 TraceId。异步任务的日志全断了。第三个坑Span 创建后父子关系错误。ParentSpanId 不对调用树就错了。第四个坑任务结束没有 clearId。线程池里的线程复用TraceId 串到下一个任务去了。第五个坑拿时间戳硬拼日志。自己拼时间线不如用现成的 TraceId。第六个坑只追踪正常路径不标记异常分支。出错的时候反而找不到调用链。七、调用链怎么帮你排障调用链最大的价值不是开发的时候用是排障的时候用。用户报了个错你拿到 ChainId把所有带这个 ChainId 的日志都捞出来按 SpanId 和 ParentSpanId 排好完整的调用过程就出来了。哪一步慢了哪一步报错了一目了然。不用自己猜哪里出问题了。这次做调用链追踪最大的体会是TraceId 不是什么高深的东西就是一个 ID把同一次操作的所有日志串起来。但它难在传播主线程到异步线程、UI 到 Native、应用到系统服务每一层都要记得传。真正做的时候最容易忽略的不是怎么打 Trace而是怎么保证 TraceId 不丢。异步线程、跨进程、跨设备每一个边界都要手动传。丢了一个整条链就断了。
