























@@ -0,0 +1,101 @@
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, describe, expect, it } from "vitest";
5+import {
6+emitDiagnosticEvent,
7+onDiagnosticEvent,
8+resetDiagnosticEventsForTest,
9+} from "../infra/diagnostic-events.js";
10+import {
11+getActiveDiagnosticTraceContext,
12+resetDiagnosticTraceContextForTest,
13+type DiagnosticTraceContext,
14+} from "../infra/diagnostic-trace-context.js";
15+import { getLogger, resetLogger, setLoggerOverride } from "../logging.js";
16+import type { ResolvedGatewayAuth } from "./auth.js";
17+import { createGatewayHttpServer } from "./server-http.js";
18+import { withTempConfig } from "./test-temp-config.js";
19+20+const resolvedAuth: ResolvedGatewayAuth = { mode: "none", allowTailscale: false };
21+22+async function listen(server: ReturnType<typeof createGatewayHttpServer>): Promise<number> {
23+return await new Promise<number>((resolve) => {
24+server.listen(0, "127.0.0.1", () => {
25+const address = server.address();
26+resolve(typeof address === "object" && address ? address.port : 0);
27+});
28+});
29+}
30+31+async function closeServer(server: ReturnType<typeof createGatewayHttpServer>): Promise<void> {
32+await new Promise<void>((resolve, reject) =>
33+server.close((err) => (err ? reject(err) : resolve())),
34+);
35+}
36+37+afterEach(() => {
38+resetDiagnosticEventsForTest();
39+resetDiagnosticTraceContextForTest();
40+setLoggerOverride(null);
41+resetLogger();
42+});
43+44+describe("gateway HTTP request trace scope", () => {
45+it("threads active request trace through logs and diagnostics", async () => {
46+const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-gateway-request-trace-"));
47+const logPath = path.join(dir, "gateway.log");
48+const events: Array<{ trace?: DiagnosticTraceContext; type: string }> = [];
49+const stop = onDiagnosticEvent((event) => {
50+events.push({ trace: event.trace, type: event.type });
51+});
52+let activeTraceInHandler: DiagnosticTraceContext | undefined;
53+54+await withTempConfig({
55+cfg: { gateway: { auth: { mode: "none" } } },
56+run: async () => {
57+setLoggerOverride({ level: "info", file: logPath });
58+const httpServer = createGatewayHttpServer({
59+canvasHost: null,
60+clients: new Set(),
61+controlUiEnabled: false,
62+controlUiBasePath: "/__control__",
63+openAiChatCompletionsEnabled: false,
64+openResponsesEnabled: false,
65+handleHooksRequest: async (_req, res) => {
66+activeTraceInHandler = getActiveDiagnosticTraceContext();
67+getLogger().info({ route: "/hook" }, "handled request trace");
68+emitDiagnosticEvent({ type: "message.queued", source: "gateway-test" });
69+res.statusCode = 204;
70+res.end();
71+return true;
72+},
73+ resolvedAuth,
74+});
75+const port = await listen(httpServer);
76+try {
77+const response = await fetch(`http://127.0.0.1:${port}/hook`);
78+expect(response.status).toBe(204);
79+} finally {
80+await closeServer(httpServer);
81+}
82+},
83+});
84+85+stop();
86+try {
87+expect(activeTraceInHandler?.traceId).toMatch(/^[0-9a-f]{32}$/);
88+expect(activeTraceInHandler?.spanId).toMatch(/^[0-9a-f]{16}$/);
89+expect(events).toEqual([{ trace: activeTraceInHandler, type: "message.queued" }]);
90+91+const [line] = fs.readFileSync(logPath, "utf8").trim().split("\n");
92+const record = JSON.parse(line ?? "{}") as Record<string, unknown>;
93+expect(record).toMatchObject({
94+traceId: activeTraceInHandler?.traceId,
95+spanId: activeTraceInHandler?.spanId,
96+});
97+} finally {
98+fs.rmSync(dir, { recursive: true, force: true });
99+}
100+});
101+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。