Gateway: optimize ws logs in normal mode
This commit is contained in:
55
docs/logging.md
Normal file
55
docs/logging.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Logging
|
||||||
|
|
||||||
|
Clawdis has two log “surfaces”:
|
||||||
|
|
||||||
|
- **Console output** (what you see in the terminal / Debug UI).
|
||||||
|
- **File logs** (JSON lines) written by the internal logger.
|
||||||
|
|
||||||
|
## File-based logger
|
||||||
|
|
||||||
|
Clawdis uses a file logger backed by `tslog` (`src/logging.ts`).
|
||||||
|
|
||||||
|
- Default rolling log file is under `/tmp/clawdis/` (one file per day): `clawdis-YYYY-MM-DD.log`
|
||||||
|
- The log file path and level can be configured via `~/.clawdis/clawdis.json`:
|
||||||
|
- `logging.file`
|
||||||
|
- `logging.level`
|
||||||
|
|
||||||
|
The file format is one JSON object per line.
|
||||||
|
|
||||||
|
## Console capture
|
||||||
|
|
||||||
|
The CLI entrypoint enables console capture (`src/index.ts` calls `enableConsoleCapture()`).
|
||||||
|
That means every `console.log/info/warn/error/debug/trace` is also written into the file logs,
|
||||||
|
while still behaving normally on stdout/stderr.
|
||||||
|
|
||||||
|
## Gateway WebSocket logs
|
||||||
|
|
||||||
|
The gateway prints WebSocket protocol logs in two modes:
|
||||||
|
|
||||||
|
- **Normal mode (no `--verbose`)**: only “interesting” RPC results are printed:
|
||||||
|
- errors (`ok=false`)
|
||||||
|
- slow calls (default threshold: `>= 50ms`)
|
||||||
|
- parse errors
|
||||||
|
- **Verbose mode (`--verbose`)**: prints all WS request/response traffic.
|
||||||
|
|
||||||
|
### WS log style
|
||||||
|
|
||||||
|
`clawdis gateway` supports a per-gateway style switch:
|
||||||
|
|
||||||
|
- `--ws-log auto` (default): normal mode is optimized; verbose mode uses compact output
|
||||||
|
- `--ws-log compact`: compact output (paired request/response) when verbose
|
||||||
|
- `--ws-log full`: full per-frame output when verbose
|
||||||
|
- `--compact`: alias for `--ws-log compact`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# optimized (only errors/slow)
|
||||||
|
clawdis gateway
|
||||||
|
|
||||||
|
# show all WS traffic (paired)
|
||||||
|
clawdis gateway --verbose --ws-log compact
|
||||||
|
|
||||||
|
# show all WS traffic (full meta)
|
||||||
|
clawdis gateway --verbose --ws-log full
|
||||||
|
```
|
||||||
@@ -58,8 +58,8 @@ export function registerGatewayCli(program: Command) {
|
|||||||
.option("--verbose", "Verbose logging to stdout/stderr", false)
|
.option("--verbose", "Verbose logging to stdout/stderr", false)
|
||||||
.option(
|
.option(
|
||||||
"--ws-log <style>",
|
"--ws-log <style>",
|
||||||
'Verbose WebSocket log style ("full"|"compact")',
|
'WebSocket log style ("auto"|"full"|"compact")',
|
||||||
"full",
|
"auto",
|
||||||
)
|
)
|
||||||
.option("--compact", 'Alias for "--ws-log compact"', false)
|
.option("--compact", 'Alias for "--ws-log compact"', false)
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
@@ -68,13 +68,18 @@ export function registerGatewayCli(program: Command) {
|
|||||||
| string
|
| string
|
||||||
| undefined;
|
| undefined;
|
||||||
const wsLogStyle: GatewayWsLogStyle =
|
const wsLogStyle: GatewayWsLogStyle =
|
||||||
wsLogRaw === "compact" ? "compact" : "full";
|
wsLogRaw === "compact"
|
||||||
|
? "compact"
|
||||||
|
: wsLogRaw === "full"
|
||||||
|
? "full"
|
||||||
|
: "auto";
|
||||||
if (
|
if (
|
||||||
wsLogRaw !== undefined &&
|
wsLogRaw !== undefined &&
|
||||||
|
wsLogRaw !== "auto" &&
|
||||||
wsLogRaw !== "compact" &&
|
wsLogRaw !== "compact" &&
|
||||||
wsLogRaw !== "full"
|
wsLogRaw !== "full"
|
||||||
) {
|
) {
|
||||||
defaultRuntime.error('Invalid --ws-log (use "full" or "compact")');
|
defaultRuntime.error('Invalid --ws-log (use "auto", "full", "compact")');
|
||||||
defaultRuntime.exit(1);
|
defaultRuntime.exit(1);
|
||||||
}
|
}
|
||||||
setGatewayWsLogStyle(wsLogStyle);
|
setGatewayWsLogStyle(wsLogStyle);
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ import { setHeartbeatsEnabled } from "../web/auto-reply.js";
|
|||||||
import { sendMessageWhatsApp } from "../web/outbound.js";
|
import { sendMessageWhatsApp } from "../web/outbound.js";
|
||||||
import { requestReplyHeartbeatNow } from "../web/reply-heartbeat-wake.js";
|
import { requestReplyHeartbeatNow } from "../web/reply-heartbeat-wake.js";
|
||||||
import { buildMessageWithAttachments } from "./chat-attachments.js";
|
import { buildMessageWithAttachments } from "./chat-attachments.js";
|
||||||
import { getGatewayWsLogStyle } from "./ws-logging.js";
|
import {
|
||||||
|
DEFAULT_WS_SLOW_MS,
|
||||||
|
getGatewayWsLogStyle,
|
||||||
|
} from "./ws-logging.js";
|
||||||
import {
|
import {
|
||||||
type ConnectParams,
|
type ConnectParams,
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
@@ -475,8 +478,13 @@ function logWs(
|
|||||||
kind: string,
|
kind: string,
|
||||||
meta?: Record<string, unknown>,
|
meta?: Record<string, unknown>,
|
||||||
) {
|
) {
|
||||||
if (!isVerbose()) return;
|
const style = getGatewayWsLogStyle();
|
||||||
if (getGatewayWsLogStyle() === "compact") {
|
if (!isVerbose()) {
|
||||||
|
logWsOptimized(direction, kind, meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style === "compact" || style === "auto") {
|
||||||
logWsCompact(direction, kind, meta);
|
logWsCompact(direction, kind, meta);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -560,6 +568,85 @@ type WsInflightEntry = {
|
|||||||
|
|
||||||
const wsInflightCompact = new Map<string, WsInflightEntry>();
|
const wsInflightCompact = new Map<string, WsInflightEntry>();
|
||||||
let wsLastCompactConnId: string | undefined;
|
let wsLastCompactConnId: string | undefined;
|
||||||
|
const wsInflightOptimized = new Map<string, number>();
|
||||||
|
|
||||||
|
function logWsOptimized(
|
||||||
|
direction: "in" | "out",
|
||||||
|
kind: string,
|
||||||
|
meta?: Record<string, unknown>,
|
||||||
|
) {
|
||||||
|
// Keep "normal" mode quiet: only surface errors, slow calls, and parser issues.
|
||||||
|
const connId = typeof meta?.connId === "string" ? meta.connId : undefined;
|
||||||
|
const id = typeof meta?.id === "string" ? meta.id : undefined;
|
||||||
|
const ok = typeof meta?.ok === "boolean" ? meta.ok : undefined;
|
||||||
|
const method = typeof meta?.method === "string" ? meta.method : undefined;
|
||||||
|
|
||||||
|
const inflightKey = connId && id ? `${connId}:${id}` : undefined;
|
||||||
|
|
||||||
|
if (direction === "in" && kind === "req" && inflightKey) {
|
||||||
|
wsInflightOptimized.set(inflightKey, Date.now());
|
||||||
|
if (wsInflightOptimized.size > 2000) wsInflightOptimized.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "parse-error") {
|
||||||
|
const errorMsg =
|
||||||
|
typeof meta?.error === "string" ? formatForLog(meta.error) : undefined;
|
||||||
|
console.log(
|
||||||
|
[
|
||||||
|
`${chalk.gray("[gws]")} ${chalk.redBright("✗")} ${chalk.bold("parse-error")}`,
|
||||||
|
errorMsg ? `${chalk.dim("error")}=${errorMsg}` : undefined,
|
||||||
|
`${chalk.dim("conn")}=${chalk.gray(shortId(connId ?? "?"))}`,
|
||||||
|
]
|
||||||
|
.filter((t): t is string => Boolean(t))
|
||||||
|
.join(" "),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction !== "out" || kind !== "res") return;
|
||||||
|
|
||||||
|
const startedAt = inflightKey ? wsInflightOptimized.get(inflightKey) : undefined;
|
||||||
|
if (inflightKey) wsInflightOptimized.delete(inflightKey);
|
||||||
|
const durationMs =
|
||||||
|
typeof startedAt === "number" ? Date.now() - startedAt : undefined;
|
||||||
|
|
||||||
|
const shouldLog =
|
||||||
|
ok === false ||
|
||||||
|
(typeof durationMs === "number" && durationMs >= DEFAULT_WS_SLOW_MS);
|
||||||
|
if (!shouldLog) return;
|
||||||
|
|
||||||
|
const statusToken =
|
||||||
|
ok === undefined
|
||||||
|
? undefined
|
||||||
|
: ok
|
||||||
|
? chalk.greenBright("✓")
|
||||||
|
: chalk.redBright("✗");
|
||||||
|
const durationToken =
|
||||||
|
typeof durationMs === "number" ? chalk.dim(`${durationMs}ms`) : undefined;
|
||||||
|
|
||||||
|
const restMeta: string[] = [];
|
||||||
|
if (meta) {
|
||||||
|
for (const [key, value] of Object.entries(meta)) {
|
||||||
|
if (value === undefined) continue;
|
||||||
|
if (key === "connId" || key === "id") continue;
|
||||||
|
if (key === "method" || key === "ok") continue;
|
||||||
|
restMeta.push(`${chalk.dim(key)}=${formatForLog(value)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = [
|
||||||
|
`${chalk.gray("[gws]")} ${chalk.yellowBright("⇄")} ${chalk.bold("res")}`,
|
||||||
|
statusToken,
|
||||||
|
method ? chalk.bold(method) : undefined,
|
||||||
|
durationToken,
|
||||||
|
...restMeta,
|
||||||
|
connId ? `${chalk.dim("conn")}=${chalk.gray(shortId(connId))}` : undefined,
|
||||||
|
id ? `${chalk.dim("id")}=${chalk.gray(shortId(id))}` : undefined,
|
||||||
|
].filter((t): t is string => Boolean(t));
|
||||||
|
|
||||||
|
console.log(tokens.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
function logWsCompact(
|
function logWsCompact(
|
||||||
direction: "in" | "out",
|
direction: "in" | "out",
|
||||||
@@ -2197,6 +2284,8 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
|
|||||||
id: req.id,
|
id: req.id,
|
||||||
ok,
|
ok,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
|
errorCode: error?.code,
|
||||||
|
errorMessage: error?.message,
|
||||||
...meta,
|
...meta,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type GatewayWsLogStyle = "full" | "compact";
|
export type GatewayWsLogStyle = "auto" | "full" | "compact";
|
||||||
|
|
||||||
let gatewayWsLogStyle: GatewayWsLogStyle = "full";
|
let gatewayWsLogStyle: GatewayWsLogStyle = "auto";
|
||||||
|
|
||||||
export function setGatewayWsLogStyle(style: GatewayWsLogStyle): void {
|
export function setGatewayWsLogStyle(style: GatewayWsLogStyle): void {
|
||||||
gatewayWsLogStyle = style;
|
gatewayWsLogStyle = style;
|
||||||
@@ -9,3 +9,5 @@ export function setGatewayWsLogStyle(style: GatewayWsLogStyle): void {
|
|||||||
export function getGatewayWsLogStyle(): GatewayWsLogStyle {
|
export function getGatewayWsLogStyle(): GatewayWsLogStyle {
|
||||||
return gatewayWsLogStyle;
|
return gatewayWsLogStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_WS_SLOW_MS = 50;
|
||||||
|
|||||||
Reference in New Issue
Block a user