diff --git a/README.md b/README.md index ef664ed..55d9ed8 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,5 @@ replace logging-system => ../Logging-System ``` 远程:`ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Logging-System.git` + +与错误码系统的追溯闭环见 [docs/integration.md](docs/integration.md)。可运行示例:`go test ./examples/trace`(两个仓库需为同级目录)。 diff --git a/docs/integration.md b/docs/integration.md new file mode 100644 index 0000000..e9cbae6 --- /dev/null +++ b/docs/integration.md @@ -0,0 +1,64 @@ +# 错误码、日志与 trace_id + +两个库互不导入。错误码库负责稳定的码和 HTTP 信封,日志库负责把同一次调用的运行记录留下来。对齐点只有两个: + +- 追踪头都是 `X-Trace-Id` +- 日志通过 `ErrorCode()` / `ErrorFields()` 识别错误,不需要 `import error-code-system` + +## 一次失败怎么串起来 + +```text +HTTP 中间件生成或沿用 X-Trace-Id + │ + ▼ +errcode.Wrap(...) 得到规范码 + │ + ├─ httpx.WriteError → {"error":{"code","message","reason","trace_id"}} + │ + └─ log.WithContext(ctx).Error(msg, err) + │ + ▼ + 日志条目含 trace_id + code + 字段 + │ + ▼ + HTTP sink 批量 POST 到 :8092 + │ + ▼ + GET /v1/logs?trace_id=... 或 ?q=agent.provider_unavailable +``` + +查问题先拿响应里的 `trace_id` 或 `code`。`trace_id` 能拉出这一次调用的全部日志;`code` 能跨请求看同一类失败。副作用是 `possible` 或 `unknown` 的码不要无条件重放,先看日志里这次执行写过什么。 + +## 最小示例 + +可运行副本:`Logging-System/examples/trace`(需要两个仓库是同级目录)。 + +```go +reg := errcode.NewRegistry() +domains.RegisterAll(reg) + +cfg, _ := config.Load("log.json") // 见 Logging-System/docs/example.json +log, _ := config.Build(cfg) +defer log.Close() + +ctx := logsys.WithTrace(r.Context(), httpx.TraceID(r)) +err := errcode.Wrap(cause, errcode.AgentProviderUnavailable, "模型不可达", "model", "glm-4") +log.WithContext(ctx).Error("provider down", err) + +httpx.WriteError(reg, w, r, err) +``` + +日志条目里会有 `trace_id=...` 和 `code=agent.provider_unavailable`。采集器侧: + +```bash +logctl query --addr http://127.0.0.1:8092 --trace <响应里的 trace_id> +``` + +## 接入现有仓库时 + +本轮不改 Agent-Native / Agent-Squad。对应关系已经写在错误码仓库: + +- `docs/agent-native-mapping.md` +- `docs/agent-squad-mapping.md` + +接入时保持现有事件名和 `AssessStepFailure` 分类,只把对外的错误字符串换成规范码,并把 `run_id` 写入日志字段。`run_id` 回答「哪一次编排」,`trace_id` 回答「哪一次请求」,两者都保留。 diff --git a/examples/trace/go.mod b/examples/trace/go.mod new file mode 100644 index 0000000..d932a7c --- /dev/null +++ b/examples/trace/go.mod @@ -0,0 +1,12 @@ +module logging-system/examples/trace + +go 1.27 + +require ( + error-code-system v0.0.0 + logging-system v0.0.0 +) + +replace error-code-system => ../../../Error-Code-System + +replace logging-system => ../.. diff --git a/examples/trace/trace_test.go b/examples/trace/trace_test.go new file mode 100644 index 0000000..d51488c --- /dev/null +++ b/examples/trace/trace_test.go @@ -0,0 +1,53 @@ +package trace + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "error-code-system/domains" + "error-code-system/errcode" + "error-code-system/httpx" + "logging-system/logsys" + "logging-system/sink" +) + +func TestErrorCodeReachesLogAndHTTP(t *testing.T) { + reg := errcode.NewRegistry() + domains.RegisterAll(reg) + mem := &sink.Memory{} + log := logsys.New(logsys.Options{Service: "agent-squad", Level: logsys.LevelInfo, Sinks: []logsys.Sink{mem}}) + + cause := errors.New("dial tcp: connection refused") + err := errcode.Wrap(cause, errcode.AgentProviderUnavailable, "模型不可达", "model", "glm-4") + const traceID = "trace-loop-0001" + log.WithTraceID(traceID).Error("provider down", err) + + got := mem.Snapshot() + if len(got) != 1 { + t.Fatalf("logs %d", len(got)) + } + if got[0].TraceID != traceID || got[0].Code != string(errcode.AgentProviderUnavailable) { + t.Fatalf("entry %+v", got[0]) + } + if got[0].Fields["model"] != "glm-4" { + t.Fatalf("fields %+v", got[0].Fields) + } + + req := httptest.NewRequest(http.MethodGet, "/v1/runs", nil) + req.Header.Set(httpx.TraceHeader, traceID) + rec := httptest.NewRecorder() + httpx.WriteError(reg, rec, req, err) + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("status %d", rec.Code) + } + var env httpx.Envelope + if err := json.Unmarshal(rec.Body.Bytes(), &env); err != nil { + t.Fatal(err) + } + if env.Error.Code != got[0].Code || env.Error.TraceID != got[0].TraceID { + t.Fatalf("envelope %+v log code %s trace %s", env.Error, got[0].Code, got[0].TraceID) + } +}