feat: 实现日志追踪链路 #186

Merged
huanghaosheng merged 31 commits from develop into v2 2026-06-21 23:24:18 +08:00
3 changed files with 20 additions and 32 deletions
Showing only changes of commit ad700743ef - Show all commits

View File

@@ -8,20 +8,12 @@ import (
"github.com/cloudwego/eino/compose"
"github.com/hhs/camtalk/internal/logger"
"github.com/hhs/camtalk/internal/models"
"github.com/hhs/camtalk/internal/orchestrator"
"github.com/hhs/camtalk/internal/session"
"github.com/hhs/camtalk/internal/trace"
)
// ctxKeySessionID sessionID 的 context key。
type ctxKeySessionID struct{}
// WithSessionID 将 sessionID 注入 context。
func WithSessionID(ctx context.Context, sessionID string) context.Context {
return context.WithValue(ctx, ctxKeySessionID{}, sessionID)
}
// EinoOrchestrator 实现 orchestrator.Orchestrator 接口。
// 将 Eino Graph 包装为现有接口WS Handler 几乎不用改。
type EinoOrchestrator struct {
@@ -48,19 +40,19 @@ func (e *EinoOrchestrator) ProcessQuery(
req models.WsQuery,
sender orchestrator.Sender,
) error {
log := logger.Log
log := trace.FromContext(ctx)
startTime := time.Now()
// 1. 设置活跃请求
if err := e.sessionMgr.SetActiveRequest(ctx, sessionID, req.RequestID); err != nil {
log.Errorw("设置活跃请求失败", "error", err)
return err
}
defer e.sessionMgr.ClearActiveRequest(ctx, sessionID)
// 2. 获取会话配置
sess, err := e.sessionMgr.Get(ctx, sessionID)
if err != nil {
log.Errorw("获取会话失败", "error", err)
log.Errorw("get session failed", "error", err)
sender.SendError(models.WsError{
Type: "error",
RequestID: req.RequestID,
@@ -75,7 +67,7 @@ func (e *EinoOrchestrator) ProcessQuery(
if req.Text == "" && req.Audio != "" {
audioData, err = base64.StdEncoding.DecodeString(req.Audio)
if err != nil {
log.Errorw("音频解码失败", "error", err)
log.Errorw("audio decode failed", "error", err)
sender.SendError(models.WsError{
Type: "error",
RequestID: req.RequestID,
@@ -90,7 +82,7 @@ func (e *EinoOrchestrator) ProcessQuery(
if req.Image != "" {
imageData, err = base64.StdEncoding.DecodeString(req.Image)
if err != nil {
log.Errorw("图片解码失败", "error", err)
log.Errorw("image decode failed", "error", err)
sender.SendError(models.WsError{
Type: "error",
RequestID: req.RequestID,
@@ -107,7 +99,7 @@ func (e *EinoOrchestrator) ProcessQuery(
// 5. 注入 context 值(供 Callback 和 Lambda 节点使用)
ctx = WithSender(ctx, sender)
ctx = WithRequestID(ctx, req.RequestID)
ctx = WithSessionID(ctx, sessionID)
ctx = trace.WithSessionID(ctx, sessionID)
ctx = WithStartTime(ctx, startTime)
// 创建 State 并从 input 复制元数据
@@ -125,7 +117,7 @@ func (e *EinoOrchestrator) ProcessQuery(
// 6. 调用 GraphStream 模式 + 运行时 Callback
streamReader, err := e.graph.Runnable.Stream(ctx, input, e.callbacks)
if err != nil {
log.Errorw("Graph Stream 启动失败", "error", err)
log.Errorw("graph stream start failed", "error", err)
sender.SendError(models.WsError{
Type: "error",
RequestID: req.RequestID,
@@ -143,7 +135,7 @@ func (e *EinoOrchestrator) ProcessQuery(
if err == io.EOF {
break
}
log.Errorw("Graph Stream 消费错误", "error", err)
log.Errorw("graph stream consume error", "error", err)
break
}
output = o
@@ -159,7 +151,7 @@ func (e *EinoOrchestrator) ProcessQuery(
Role: "user",
Content: userText,
}); err != nil {
log.Errorw("追加用户消息到历史失败", "session", sessionID, "error", err)
log.Errorw("append user message failed", "error", err)
}
}
@@ -169,15 +161,12 @@ func (e *EinoOrchestrator) ProcessQuery(
Role: "assistant",
Content: output.FullResponse,
}); err != nil {
log.Errorw("追加助手消息到历史失败", "session", sessionID, "error", err)
log.Errorw("append assistant message failed", "error", err)
}
}
latency := time.Since(startTime).Milliseconds()
log.Infow("Eino 编排完成",
"request_id", req.RequestID,
"latency_ms", latency,
"session_id", sessionID)
log.Infow("eino pipeline completed", "latency_ms", latency)
return nil
}

View File

@@ -9,14 +9,13 @@ import (
"github.com/cloudwego/eino/schema"
callbacksHelper "github.com/cloudwego/eino/utils/callbacks"
"github.com/hhs/camtalk/internal/logger"
"github.com/hhs/camtalk/internal/models"
"github.com/hhs/camtalk/internal/orchestrator"
"github.com/hhs/camtalk/internal/trace"
)
// context key 类型,避免与其他包冲突。
type ctxKeySender struct{}
type ctxKeyRequestID struct{}
type ctxKeyState struct{}
// WithSender 将 Sender 注入 context。
@@ -24,9 +23,9 @@ func WithSender(ctx context.Context, sender orchestrator.Sender) context.Context
return context.WithValue(ctx, ctxKeySender{}, sender)
}
// WithRequestID 将 requestID 注入 context。
// WithRequestID 将 requestID 注入 context(使用 trace 包)
func WithRequestID(ctx context.Context, requestID string) context.Context {
return context.WithValue(ctx, ctxKeyRequestID{}, requestID)
return trace.WithRequestID(ctx, requestID)
}
// WithPipelineState 将 PipelineState 注入 context。
@@ -40,10 +39,9 @@ func senderFromCtx(ctx context.Context) orchestrator.Sender {
return s
}
// requestIDFromCtx 从 context 获取 requestID。
// requestIDFromCtx 从 context 获取 requestID(使用 trace 包)
func requestIDFromCtx(ctx context.Context) string {
s, _ := ctx.Value(ctxKeyRequestID{}).(string)
return s
return trace.GetRequestID(ctx)
}
// stateFromCtx 从 context 获取 PipelineState。
@@ -62,7 +60,7 @@ func BuildCallbackHandler() callbacks.Handler {
return callbacksHelper.NewHandlerHelper().
ChatModel(&callbacksHelper.ModelCallbackHandler{
OnEndWithStreamOutput: func(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[*model.CallbackOutput]) context.Context {
log := logger.Log
log := trace.FromContext(ctx)
sender := senderFromCtx(ctx)
requestID := requestIDFromCtx(ctx)
state := stateFromCtx(ctx)

View File

@@ -13,6 +13,7 @@ import (
"github.com/hhs/camtalk/internal/ai/tts"
"github.com/hhs/camtalk/internal/models"
"github.com/hhs/camtalk/internal/orchestrator"
"github.com/hhs/camtalk/internal/trace"
)
// --- Mock STT Service ---
@@ -176,7 +177,7 @@ func TestContextInjection(t *testing.T) {
sender := &mockSender{}
ctx = WithSender(ctx, sender)
ctx = WithRequestID(ctx, "req-123")
ctx = WithSessionID(ctx, "sess-456")
ctx = trace.WithSessionID(ctx, "sess-456")
ctx = WithStartTime(ctx, time.Now())
ctx = WithPipelineState(ctx, genLocalState(ctx))