mirror of https://github.com/openclaw/openclaw.git
refactor(gateway): separate ciao classification from logging
This commit is contained in:
parent
31ee442d3f
commit
ee077804b0
|
|
@ -1,70 +1,51 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const logDebugMock = vi.hoisted(() => vi.fn());
|
||||
const logWarnMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../logger.js", () => ({
|
||||
logDebug: (...args: unknown[]) => logDebugMock(...args),
|
||||
logWarn: (...args: unknown[]) => logWarnMock(...args),
|
||||
}));
|
||||
|
||||
const { ignoreCiaoUnhandledRejection } = await import("./bonjour-ciao.js");
|
||||
const { classifyCiaoUnhandledRejection, ignoreCiaoUnhandledRejection } =
|
||||
await import("./bonjour-ciao.js");
|
||||
|
||||
describe("bonjour-ciao", () => {
|
||||
it("ignores and logs ciao announcement cancellation rejections", () => {
|
||||
it("classifies ciao cancellation rejections separately from side effects", () => {
|
||||
expect(classifyCiaoUnhandledRejection(new Error("CIAO PROBING CANCELLED"))).toEqual({
|
||||
kind: "cancellation",
|
||||
formatted: "CIAO PROBING CANCELLED",
|
||||
});
|
||||
});
|
||||
|
||||
it("classifies ciao interface assertions separately from side effects", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection(
|
||||
new Error("Reached illegal state! IPV4 address change from defined to undefined!"),
|
||||
),
|
||||
).toEqual({
|
||||
kind: "interface-assertion",
|
||||
formatted: "Reached illegal state! IPV4 address change from defined to undefined!",
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses ciao announcement cancellation rejections", () => {
|
||||
expect(ignoreCiaoUnhandledRejection(new Error("Ciao announcement cancelled by shutdown"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(logDebugMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining("ignoring unhandled ciao rejection"),
|
||||
);
|
||||
expect(logWarnMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores and logs ciao probing cancellation rejections", () => {
|
||||
logDebugMock.mockReset();
|
||||
logWarnMock.mockReset();
|
||||
|
||||
it("suppresses ciao probing cancellation rejections", () => {
|
||||
expect(ignoreCiaoUnhandledRejection(new Error("CIAO PROBING CANCELLED"))).toBe(true);
|
||||
expect(logDebugMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining("ignoring unhandled ciao rejection"),
|
||||
);
|
||||
expect(logWarnMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores lower-case string cancellation reasons too", () => {
|
||||
logDebugMock.mockReset();
|
||||
logWarnMock.mockReset();
|
||||
|
||||
it("suppresses lower-case string cancellation reasons too", () => {
|
||||
expect(ignoreCiaoUnhandledRejection("ciao announcement cancelled during cleanup")).toBe(true);
|
||||
expect(logDebugMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining("ignoring unhandled ciao rejection"),
|
||||
);
|
||||
expect(logWarnMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("suppresses ciao interface assertion rejections as non-fatal", () => {
|
||||
logDebugMock.mockReset();
|
||||
logWarnMock.mockReset();
|
||||
|
||||
const error = Object.assign(
|
||||
new Error("Reached illegal state! IPV4 address change from defined to undefined!"),
|
||||
{ name: "AssertionError" },
|
||||
);
|
||||
|
||||
expect(ignoreCiaoUnhandledRejection(error)).toBe(true);
|
||||
expect(logWarnMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining("suppressing ciao interface assertion"),
|
||||
);
|
||||
expect(logDebugMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps unrelated rejections visible", () => {
|
||||
logDebugMock.mockReset();
|
||||
logWarnMock.mockReset();
|
||||
|
||||
expect(ignoreCiaoUnhandledRejection(new Error("boom"))).toBe(false);
|
||||
expect(logDebugMock).not.toHaveBeenCalled();
|
||||
expect(logWarnMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
import { logDebug, logWarn } from "../logger.js";
|
||||
import { formatBonjourError } from "./bonjour-errors.js";
|
||||
|
||||
const CIAO_CANCELLATION_MESSAGE_RE = /^CIAO (?:ANNOUNCEMENT|PROBING) CANCELLED\b/u;
|
||||
const CIAO_INTERFACE_ASSERTION_MESSAGE_RE =
|
||||
/REACHED ILLEGAL STATE!?\s+IPV4 ADDRESS CHANGE FROM DEFINED TO UNDEFINED!?/u;
|
||||
|
||||
export function ignoreCiaoUnhandledRejection(reason: unknown): boolean {
|
||||
export type CiaoUnhandledRejectionClassification =
|
||||
| { kind: "cancellation"; formatted: string }
|
||||
| { kind: "interface-assertion"; formatted: string };
|
||||
|
||||
export function classifyCiaoUnhandledRejection(
|
||||
reason: unknown,
|
||||
): CiaoUnhandledRejectionClassification | null {
|
||||
const formatted = formatBonjourError(reason);
|
||||
const message = formatted.toUpperCase();
|
||||
if (!CIAO_CANCELLATION_MESSAGE_RE.test(message)) {
|
||||
if (!CIAO_INTERFACE_ASSERTION_MESSAGE_RE.test(message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
logWarn(`bonjour: suppressing ciao interface assertion: ${formatted}`);
|
||||
return true;
|
||||
if (CIAO_CANCELLATION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "cancellation", formatted };
|
||||
}
|
||||
logDebug(`bonjour: ignoring unhandled ciao rejection: ${formatted}`);
|
||||
return true;
|
||||
if (CIAO_INTERFACE_ASSERTION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "interface-assertion", formatted };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ignoreCiaoUnhandledRejection(reason: unknown): boolean {
|
||||
return classifyCiaoUnhandledRejection(reason) !== null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,6 +241,39 @@ describe("gateway bonjour advertiser", () => {
|
|||
expect(order).toEqual(["shutdown", "cleanup"]);
|
||||
});
|
||||
|
||||
it("logs ciao handler classifications at the bonjour caller", async () => {
|
||||
enableAdvertiserUnitMode();
|
||||
|
||||
const destroy = vi.fn().mockResolvedValue(undefined);
|
||||
const advertise = vi.fn().mockResolvedValue(undefined);
|
||||
mockCiaoService({ advertise, destroy });
|
||||
|
||||
const started = await startGatewayBonjourAdvertiser({
|
||||
gatewayPort: 18789,
|
||||
sshPort: 2222,
|
||||
});
|
||||
|
||||
const handler = registerUnhandledRejectionHandler.mock.calls[0]?.[0] as
|
||||
| ((reason: unknown) => boolean)
|
||||
| undefined;
|
||||
expect(handler).toBeTypeOf("function");
|
||||
|
||||
expect(handler?.(new Error("CIAO PROBING CANCELLED"))).toBe(true);
|
||||
expect(logDebug).toHaveBeenCalledWith(
|
||||
expect.stringContaining("ignoring unhandled ciao rejection"),
|
||||
);
|
||||
|
||||
logDebug.mockClear();
|
||||
expect(
|
||||
handler?.(new Error("Reached illegal state! IPV4 address change from defined to undefined!")),
|
||||
).toBe(true);
|
||||
expect(logWarn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("suppressing ciao interface assertion"),
|
||||
);
|
||||
|
||||
await started.stop();
|
||||
});
|
||||
|
||||
it("logs advertise failures and retries via watchdog", async () => {
|
||||
enableAdvertiserUnitMode();
|
||||
vi.useFakeTimers();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { logDebug, logWarn } from "../logger.js";
|
||||
import { getLogger } from "../logging.js";
|
||||
import { ignoreCiaoUnhandledRejection } from "./bonjour-ciao.js";
|
||||
import { classifyCiaoUnhandledRejection } from "./bonjour-ciao.js";
|
||||
import { formatBonjourError } from "./bonjour-errors.js";
|
||||
import { isTruthyEnvValue } from "./env.js";
|
||||
import { registerUnhandledRejectionHandler } from "./unhandled-rejections.js";
|
||||
|
|
@ -94,6 +94,21 @@ function isAnnouncedState(state: BonjourServiceState | "unknown") {
|
|||
return String(state) === "announced";
|
||||
}
|
||||
|
||||
function handleCiaoUnhandledRejection(reason: unknown): boolean {
|
||||
const classification = classifyCiaoUnhandledRejection(reason);
|
||||
if (!classification) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (classification.kind === "interface-assertion") {
|
||||
logWarn(`bonjour: suppressing ciao interface assertion: ${classification.formatted}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
logDebug(`bonjour: ignoring unhandled ciao rejection: ${classification.formatted}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function startGatewayBonjourAdvertiser(
|
||||
opts: GatewayBonjourAdvertiseOpts,
|
||||
): Promise<GatewayBonjourAdvertiser> {
|
||||
|
|
@ -175,7 +190,7 @@ export async function startGatewayBonjourAdvertiser(
|
|||
|
||||
const cleanupUnhandledRejection =
|
||||
services.length > 0
|
||||
? registerUnhandledRejectionHandler(ignoreCiaoUnhandledRejection)
|
||||
? registerUnhandledRejectionHandler(handleCiaoUnhandledRejection)
|
||||
: undefined;
|
||||
|
||||
return { responder, services, cleanupUnhandledRejection };
|
||||
|
|
|
|||
Loading…
Reference in New Issue