package trace import ( "context" "time" ) // 本文件是 span 生产者侧的桥接层:它让不持有 *Tracer 句柄的叶子包 // (internal/runtime/agent 的护栏检查等)也能在当前 span 之下再开一个 // 子 span,从而把「尽可能深」的结构化追踪补齐(design/04 §9:LLM / // 工具 / 编排 / 状态转移 / 子代理 / 记忆读写 / guardrail 全都要有生产者)。 // // 图谱(graph*.go)与生产者(span 生产点)在这里分工:图构建器只负责把 // span 折成节点,生产者只负责如实记录发生了什么。两者都不需要对方存在 // —— 没有 tracer 时 ChildSpan 返回 nil,生产点照常跑完业务。 // ChildSpan 在当前 ctx 携带的 span 之下开一个具名字 span:新 span 复用其父 // span 的 Tracer(因此 Finish 时同样交给 Writer 落盘),ParentID 指向父 // span,返回的 ctx 把新 span 作为当前 span 继续往下传。 // // ctx 未携带 span(未开追踪、或在根 span 之外调用)时返回 (ctx, nil): // 调用方必须容忍 nil span(不要用它,也不要把 nil span 的 ctx 当真)。 // 空 ctx 按 context.Background() 处理。 func ChildSpan(ctx context.Context, name string, kind SpanKind) (context.Context, *Span) { if ctx == nil { ctx = context.Background() } parent := SpanFromContext(ctx) if parent == nil { return ctx, nil } parent.mu.RLock() tracer := parent.tracer parent.mu.RUnlock() if tracer == nil { return ctx, nil } span := tracer.newSpan(name, kind, parent) return context.WithValue(ctx, spanKey{}, span), span } // FinishChild 关闭一个由 ChildSpan 开出的 span 并记录耗时与结果:span 为 // nil(未开追踪)时是空操作,因此生产点可以无条件调用它,不必在调用处 // 再写一遍 nil 判断。status 为 ok/error,err 为 nil 时代码不记录 error 属性。 func FinishChild(span *Span, status SpanStatus, err error) { if span == nil { return } if err != nil { _ = span.SetProp("error", err.Error()) } span.Finish(status) } // ElapsedMS 报告 span 自开始以来经过的毫秒数;nil span 或零 StartAt 返回 0。 // 它让叶子包在不依赖 loop 内部助手的情况下记录「耗时」属性。 func ElapsedMS(span *Span) int64 { if span == nil || span.StartedAt.IsZero() { return 0 } return time.Since(span.StartedAt).Milliseconds() }