mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix: reduce slack socket reconnect log noise
This commit is contained in:
@@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/doctor: trust a ready gateway memory probe when CLI-side active memory backend resolution is unavailable, preventing false "No active memory plugin is registered" warnings for healthy runtime setups. Fixes #76792. Thanks @som-686.
|
||||
- Memory/status: keep plain `openclaw memory status` and `openclaw memory status --json` on the cheap read-only path by reserving vector and embedding provider probes for `--deep` or `--index`. Fixes #76769. Thanks @daruire.
|
||||
- Telegram: suppress stale same-session replies when a newer accepted message arrives before an older in-flight Telegram dispatch finalizes. Fixes #76642. Thanks @chinar-amrutkar.
|
||||
- Slack: collapse routine Socket Mode pong-timeout reconnects into one OpenClaw reconnect line and suppress the duplicate Slack SDK pong warning.
|
||||
- Gateway/diagnostics: abort-drain embedded runs after an extended no-progress stall so a single dead session no longer leaves queued Discord/channel turns blocked behind repeated `recovery=none` liveness warnings.
|
||||
- Plugins/ClawHub: accept the live artifact resolver `kind`/`sha256` field names alongside the typed `artifactKind`/`artifactSha256` form so `clawhub:` installs of npm-pack and legacy ZIP packages no longer miss downloadable artifacts. Thanks @romneyda.
|
||||
- Control UI/Sessions: avoid full `sessions.list` reloads for chat-turn `sessions.changed` payloads, so large session stores no longer add multi-second delays while chat responses are being delivered. (#76676) Thanks @VACInc.
|
||||
|
||||
@@ -10,8 +10,13 @@ type SlackSocketModeConfig = Pick<
|
||||
SlackSocketModeReceiverOptions,
|
||||
"clientPingTimeout" | "serverPingTimeout" | "pingPongLoggingEnabled"
|
||||
>;
|
||||
type SlackSdkLogger = NonNullable<SlackSocketModeReceiverOptions["logger"]>;
|
||||
type SlackSdkLogLevel = ReturnType<SlackSdkLogger["getLevel"]>;
|
||||
|
||||
const OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS = 15_000;
|
||||
const SLACK_SOCKET_PONG_TIMEOUT_WARNING_PREFIX = "A pong wasn't received from the server";
|
||||
const SLACK_SOCKET_LOG_LEVEL_IGNORED_WARNING_RE =
|
||||
/^The logLevel given to .+ was ignored as you also gave logger$/;
|
||||
|
||||
export type SlackBoltResolvedExports = {
|
||||
App: SlackAppConstructor;
|
||||
@@ -133,6 +138,42 @@ export function publishSlackDisconnectedStatus(
|
||||
});
|
||||
}
|
||||
|
||||
function isSlackSocketPongTimeoutWarning(args: readonly unknown[]) {
|
||||
return (
|
||||
typeof args[0] === "string" && args[0].startsWith(SLACK_SOCKET_PONG_TIMEOUT_WARNING_PREFIX)
|
||||
);
|
||||
}
|
||||
|
||||
function isSlackSocketSelfInflictedLoggerWarning(args: readonly unknown[]) {
|
||||
return typeof args[0] === "string" && SLACK_SOCKET_LOG_LEVEL_IGNORED_WARNING_RE.test(args[0]);
|
||||
}
|
||||
|
||||
export function createSlackSocketModeLogger(
|
||||
sink: Pick<typeof console, "debug" | "info" | "warn" | "error"> = console,
|
||||
): SlackSdkLogger {
|
||||
let level = "info" as SlackSdkLogLevel;
|
||||
let name = "socket-mode";
|
||||
const prefix = () => `socket-mode:${name}`;
|
||||
return {
|
||||
debug: () => {},
|
||||
info: () => {},
|
||||
warn: (...args: unknown[]) => {
|
||||
if (isSlackSocketPongTimeoutWarning(args) || isSlackSocketSelfInflictedLoggerWarning(args)) {
|
||||
return;
|
||||
}
|
||||
sink.warn(prefix(), ...args);
|
||||
},
|
||||
error: (...args: unknown[]) => sink.error(prefix(), ...args),
|
||||
setLevel: (nextLevel) => {
|
||||
level = nextLevel;
|
||||
},
|
||||
getLevel: () => level,
|
||||
setName: (nextName) => {
|
||||
name = nextName;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
@@ -181,6 +222,7 @@ export function createSlackBoltApp(params: {
|
||||
autoReconnectEnabled: false,
|
||||
clientPingTimeout:
|
||||
params.socketMode?.clientPingTimeout ?? OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS,
|
||||
logger: createSlackSocketModeLogger(),
|
||||
installerOptions: {
|
||||
clientOptions: params.clientOptions,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createSlackBoltApp,
|
||||
createSlackSocketModeLogger,
|
||||
resolveSlackBoltInterop,
|
||||
shouldSkipOpenClawSlackSelfEvent,
|
||||
} from "./provider-support.js";
|
||||
@@ -159,6 +160,10 @@ describe("createSlackBoltApp", () => {
|
||||
appToken: "xapp-test",
|
||||
autoReconnectEnabled: false,
|
||||
clientPingTimeout: 15_000,
|
||||
logger: expect.objectContaining({
|
||||
error: expect.any(Function),
|
||||
warn: expect.any(Function),
|
||||
}),
|
||||
installerOptions: {
|
||||
clientOptions,
|
||||
},
|
||||
@@ -200,6 +205,10 @@ describe("createSlackBoltApp", () => {
|
||||
clientPingTimeout: 20_000,
|
||||
serverPingTimeout: 45_000,
|
||||
pingPongLoggingEnabled: true,
|
||||
logger: expect.objectContaining({
|
||||
error: expect.any(Function),
|
||||
warn: expect.any(Function),
|
||||
}),
|
||||
installerOptions: {
|
||||
clientOptions,
|
||||
},
|
||||
@@ -264,6 +273,23 @@ describe("createSlackBoltApp", () => {
|
||||
expect(eagerAuthTestCalls).toBe(0);
|
||||
});
|
||||
|
||||
it("suppresses Slack's redundant pong timeout warning while forwarding other SDK warnings", () => {
|
||||
const warnCalls: unknown[][] = [];
|
||||
const logger = createSlackSocketModeLogger({
|
||||
debug: () => {},
|
||||
info: () => {},
|
||||
warn: (...args: unknown[]) => warnCalls.push(args),
|
||||
error: () => {},
|
||||
});
|
||||
|
||||
logger.setName("SlackWebSocket:1");
|
||||
logger.warn("A pong wasn't received from the server before the timeout of 15000ms!");
|
||||
logger.warn("The logLevel given to Socket Mode was ignored as you also gave logger");
|
||||
logger.warn("another socket warning");
|
||||
|
||||
expect(warnCalls).toEqual([["socket-mode:SlackWebSocket:1", "another socket warning"]]);
|
||||
});
|
||||
|
||||
it("keeps Bolt self filtering except assistant message_changed events", () => {
|
||||
expect(
|
||||
shouldSkipOpenClawSlackSelfEvent({
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
publishSlackDisconnectedStatus,
|
||||
startSlackSocketAndWaitForDisconnect,
|
||||
} from "./provider-support.js";
|
||||
import { formatSlackSocketReconnectMessage } from "./provider.js";
|
||||
import { waitForSlackSocketDisconnect } from "./reconnect-policy.js";
|
||||
|
||||
class FakeEmitter {
|
||||
@@ -85,6 +86,17 @@ describe("slack socket reconnect helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("formats recoverable disconnects as a single reconnect status line", () => {
|
||||
expect(
|
||||
formatSlackSocketReconnectMessage({
|
||||
event: "disconnect",
|
||||
attempt: 1,
|
||||
maxAttempts: 12,
|
||||
delayMs: 2_340,
|
||||
}),
|
||||
).toBe("slack socket disconnected (disconnect); reconnecting in 2s (attempt 1/12)");
|
||||
});
|
||||
|
||||
it("resolves disconnect waiter on socket disconnect event", async () => {
|
||||
const client = new FakeEmitter();
|
||||
const app = { receiver: { client } };
|
||||
|
||||
@@ -85,6 +85,18 @@ async function getSlackBoltInterop(): Promise<SlackBoltResolvedExports> {
|
||||
const SLACK_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
|
||||
const SLACK_WEBHOOK_BODY_TIMEOUT_MS = 30_000;
|
||||
|
||||
export function formatSlackSocketReconnectMessage(params: {
|
||||
event: string;
|
||||
attempt: number;
|
||||
maxAttempts: number;
|
||||
delayMs: number;
|
||||
error?: unknown;
|
||||
}) {
|
||||
const maxAttempts = params.maxAttempts > 0 ? String(params.maxAttempts) : "∞";
|
||||
const suffix = params.error ? ` (${formatUnknownError(params.error)})` : "";
|
||||
return `slack socket disconnected (${params.event}); reconnecting in ${Math.round(params.delayMs / 1000)}s (attempt ${params.attempt}/${maxAttempts})${suffix}`;
|
||||
}
|
||||
|
||||
function parseApiAppIdFromAppToken(raw?: string) {
|
||||
const token = raw?.trim();
|
||||
if (!token) {
|
||||
@@ -443,6 +455,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
try {
|
||||
if (slackMode === "socket") {
|
||||
let reconnectAttempts = 0;
|
||||
let hasLoggedSocketConnected = false;
|
||||
while (!opts.abortSignal?.aborted) {
|
||||
try {
|
||||
const disconnect = await startSlackSocketAndWaitForDisconnect({
|
||||
@@ -451,7 +464,10 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
onStarted: () => {
|
||||
reconnectAttempts = 0;
|
||||
publishSlackConnectedStatus(opts.setStatus);
|
||||
runtime.log?.("slack socket mode connected");
|
||||
if (!hasLoggedSocketConnected) {
|
||||
hasLoggedSocketConnected = true;
|
||||
runtime.log?.("slack socket mode connected");
|
||||
}
|
||||
},
|
||||
});
|
||||
if (!disconnect) {
|
||||
@@ -483,10 +499,16 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
}
|
||||
|
||||
const delayMs = computeBackoff(SLACK_SOCKET_RECONNECT_POLICY, reconnectAttempts);
|
||||
runtime.error?.(
|
||||
`slack socket disconnected (${disconnect.event}). retry ${reconnectAttempts}/${SLACK_SOCKET_RECONNECT_POLICY.maxAttempts || "∞"} in ${Math.round(delayMs / 1000)}s${
|
||||
disconnect.error ? ` (${formatUnknownError(disconnect.error)})` : ""
|
||||
}`,
|
||||
runtime.log?.(
|
||||
warn(
|
||||
formatSlackSocketReconnectMessage({
|
||||
event: disconnect.event,
|
||||
attempt: reconnectAttempts,
|
||||
maxAttempts: SLACK_SOCKET_RECONNECT_POLICY.maxAttempts,
|
||||
delayMs,
|
||||
error: disconnect.error,
|
||||
}),
|
||||
),
|
||||
);
|
||||
await gracefulStop();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user