fix(logging): decouple file logs from console verbose

This commit is contained in:
Peter Steinberger
2026-01-03 12:32:14 +00:00
parent e52bdaa2a2
commit bb54e60179
18 changed files with 105 additions and 67 deletions

View File

@@ -15,6 +15,7 @@
- Agent tools: scope the Discord tool to Discord surface runs. - Agent tools: scope the Discord tool to Discord surface runs.
- Agent tools: format verbose tool summaries without brackets, with unique emojis and `tool: detail` style. - Agent tools: format verbose tool summaries without brackets, with unique emojis and `tool: detail` style.
- Thinking: default to low for reasoning-capable models when no /think or config default is set. - Thinking: default to low for reasoning-capable models when no /think or config default is set.
- Logging: decouple file log levels from console verbosity; verbose-only details are captured when `logging.level` is debug/trace.
### Docs ### Docs
- Skills: add Sheets/Docs examples to gog skill (#128) — thanks @mbelinky. - Skills: add Sheets/Docs examples to gog skill (#128) — thanks @mbelinky.

View File

@@ -23,12 +23,25 @@ Clawdis uses a file logger backed by `tslog` (`src/logging.ts`).
The file format is one JSON object per line. The file format is one JSON object per line.
**Verbose vs. log levels**
- **File logs** are controlled exclusively by `logging.level`.
- `--verbose` only affects **console verbosity** (and WS log style); it does **not**
raise the file log level.
- To capture verbose-only details in file logs, set `logging.level` to `debug` or
`trace`.
## Console capture ## Console capture
The CLI entrypoint enables console capture (`src/index.ts` calls `enableConsoleCapture()`). 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, That means every `console.log/info/warn/error/debug/trace` is also written into the file logs,
while still behaving normally on stdout/stderr. while still behaving normally on stdout/stderr.
You can tune console verbosity independently via:
- `logging.consoleLevel` (default `info`)
- `logging.consoleStyle` (`pretty` | `compact` | `json`)
## Gateway WebSocket logs ## Gateway WebSocket logs
The gateway prints WebSocket protocol logs in two modes: The gateway prints WebSocket protocol logs in two modes:
@@ -80,7 +93,7 @@ Behavior:
- **Sub-loggers by subsystem** (auto prefix + structured field `{ subsystem }`) - **Sub-loggers by subsystem** (auto prefix + structured field `{ subsystem }`)
- **`logRaw()`** for QR/UX output (no prefix, no formatting) - **`logRaw()`** for QR/UX output (no prefix, no formatting)
- **Console styles** (e.g. `pretty | compact | json`) - **Console styles** (e.g. `pretty | compact | json`)
- **Console log level** separate from file log level (file keeps full detail) - **Console log level** separate from file log level (file keeps full detail when `logging.level` is set to `debug`/`trace`)
- **WhatsApp message bodies** are logged at `debug` (use `--verbose` to see them) - **WhatsApp message bodies** are logged at `debug` (use `--verbose` to see them)
This keeps existing file logs stable while making interactive output scannable. This keeps existing file logs stable while making interactive output scannable.

View File

@@ -4,7 +4,7 @@ import os from "node:os";
import path from "node:path"; import path from "node:path";
import type { ClawdisConfig } from "../config/config.js"; import type { ClawdisConfig } from "../config/config.js";
import { isVerbose, logVerbose } from "../globals.js"; import { logVerbose, shouldLogVerbose } from "../globals.js";
import { runExec } from "../process/exec.js"; import { runExec } from "../process/exec.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { applyTemplate, type MsgContext } from "./templating.js"; import { applyTemplate, type MsgContext } from "./templating.js";
@@ -36,7 +36,7 @@ export async function transcribeInboundAudio(
); );
await fs.writeFile(tmpPath, buffer); await fs.writeFile(tmpPath, buffer);
mediaPath = tmpPath; mediaPath = tmpPath;
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose( logVerbose(
`Downloaded audio for transcription (${(buffer.length / (1024 * 1024)).toFixed(2)}MB) -> ${tmpPath}`, `Downloaded audio for transcription (${(buffer.length / (1024 * 1024)).toFixed(2)}MB) -> ${tmpPath}`,
); );
@@ -48,7 +48,7 @@ export async function transcribeInboundAudio(
const argv = transcriber.command.map((part) => const argv = transcriber.command.map((part) =>
applyTemplate(part, templCtx), applyTemplate(part, templCtx),
); );
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose(`Transcribing audio via command: ${argv.join(" ")}`); logVerbose(`Transcribing audio via command: ${argv.join(" ")}`);
} }
const { stdout } = await runExec(argv[0], argv.slice(1), { const { stdout } = await runExec(argv[0], argv.slice(1), {

View File

@@ -20,7 +20,7 @@ import type {
} from "../config/config.js"; } from "../config/config.js";
import { loadConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js";
import { resolveStorePath, updateLastRoute } from "../config/sessions.js"; import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
import { danger, isVerbose, logVerbose, warn } from "../globals.js"; import { danger, logVerbose, shouldLogVerbose, warn } from "../globals.js";
import { getChildLogger } from "../logging.js"; import { getChildLogger } from "../logging.js";
import { detectMime } from "../media/mime.js"; import { detectMime } from "../media/mime.js";
import { saveMediaBuffer } from "../media/store.js"; import { saveMediaBuffer } from "../media/store.js";
@@ -139,7 +139,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
const groupDmEnabled = dmConfig?.groupEnabled ?? false; const groupDmEnabled = dmConfig?.groupEnabled ?? false;
const groupDmChannels = dmConfig?.groupChannels; const groupDmChannels = dmConfig?.groupChannels;
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose( logVerbose(
`discord: config dm=${dmEnabled ? "on" : "off"} allowFrom=${summarizeAllowList(allowFrom)} groupDm=${groupDmEnabled ? "on" : "off"} groupDmChannels=${summarizeAllowList(groupDmChannels)} guilds=${summarizeGuilds(guildEntries)} historyLimit=${historyLimit} mediaMaxMb=${Math.round(mediaMaxBytes / (1024 * 1024))}`, `discord: config dm=${dmEnabled ? "on" : "off"} allowFrom=${summarizeAllowList(allowFrom)} groupDm=${groupDmEnabled ? "on" : "off"} groupDmChannels=${summarizeAllowList(groupDmChannels)} guilds=${summarizeGuilds(guildEntries)} historyLimit=${historyLimit} mediaMaxMb=${Math.round(mediaMaxBytes / (1024 * 1024))}`,
); );
@@ -191,7 +191,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
const wasMentioned = const wasMentioned =
!isDirectMessage && Boolean(botId && message.mentions.has(botId)); !isDirectMessage && Boolean(botId && message.mentions.has(botId));
const baseText = resolveDiscordMessageText(message); const baseText = resolveDiscordMessageText(message);
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose( logVerbose(
`discord: inbound id=${message.id} guild=${message.guild?.id ?? "dm"} channel=${message.channelId} mention=${wasMentioned ? "yes" : "no"} type=${isDirectMessage ? "dm" : isGroupDm ? "group-dm" : "guild"} content=${baseText ? "yes" : "no"}`, `discord: inbound id=${message.id} guild=${message.guild?.id ?? "dm"} channel=${message.channelId} mention=${wasMentioned ? "yes" : "no"} type=${isDirectMessage ? "dm" : isGroupDm ? "group-dm" : "guild"} content=${baseText ? "yes" : "no"}`,
); );
@@ -414,7 +414,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
}); });
} }
if (isVerbose()) { if (shouldLogVerbose()) {
const preview = combinedBody.slice(0, 200).replace(/\n/g, "\\n"); const preview = combinedBody.slice(0, 200).replace(/\n/g, "\\n");
logVerbose( logVerbose(
`discord inbound: channel=${message.channelId} from=${ctxPayload.From} preview="${preview}"`, `discord inbound: channel=${message.channelId} from=${ctxPayload.From} preview="${preview}"`,
@@ -485,7 +485,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
textLimit, textLimit,
}); });
didSendReply = true; didSendReply = true;
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose( logVerbose(
`discord: delivered ${replies.length} reply${replies.length === 1 ? "" : "ies"} to ${replyTarget}`, `discord: delivered ${replies.length} reply${replies.length === 1 ? "" : "ies"} to ${replyTarget}`,
); );
@@ -524,7 +524,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
logVerbose("discord: drop slash (dms disabled)"); logVerbose("discord: drop slash (dms disabled)");
return; return;
} }
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose( logVerbose(
`discord: slash inbound guild=${interaction.guildId ?? "dm"} channel=${interaction.channelId} type=${isDirectMessage ? "dm" : isGroupDm ? "group-dm" : "guild"}`, `discord: slash inbound guild=${interaction.guildId ?? "dm"} channel=${interaction.channelId} type=${isDirectMessage ? "dm" : isGroupDm ? "group-dm" : "guild"}`,
); );

View File

@@ -83,7 +83,7 @@ import {
sendMessageDiscord, sendMessageDiscord,
} from "../discord/index.js"; } from "../discord/index.js";
import { type DiscordProbe, probeDiscord } from "../discord/probe.js"; import { type DiscordProbe, probeDiscord } from "../discord/probe.js";
import { isVerbose } from "../globals.js"; import { isVerbose, shouldLogVerbose } from "../globals.js";
import { startGmailWatcher, stopGmailWatcher } from "../hooks/gmail-watcher.js"; import { startGmailWatcher, stopGmailWatcher } from "../hooks/gmail-watcher.js";
import { import {
monitorIMessageProvider, monitorIMessageProvider,
@@ -2084,7 +2084,7 @@ export async function startGatewayServer(
lastError: null, lastError: null,
}; };
const task = monitorWebProvider( const task = monitorWebProvider(
isVerbose(), shouldLogVerbose(),
undefined, undefined,
true, true,
undefined, undefined,
@@ -2137,7 +2137,7 @@ export async function startGatewayServer(
running: false, running: false,
lastError: "disabled", lastError: "disabled",
}; };
if (isVerbose()) { if (shouldLogVerbose()) {
logTelegram.debug( logTelegram.debug(
"telegram provider disabled (telegram.enabled=false)", "telegram provider disabled (telegram.enabled=false)",
); );
@@ -2154,7 +2154,7 @@ export async function startGatewayServer(
lastError: "not configured", lastError: "not configured",
}; };
// keep quiet by default; this is a normal state // keep quiet by default; this is a normal state
if (isVerbose()) { if (shouldLogVerbose()) {
logTelegram.debug( logTelegram.debug(
"telegram provider not configured (no TELEGRAM_BOT_TOKEN)", "telegram provider not configured (no TELEGRAM_BOT_TOKEN)",
); );
@@ -2171,7 +2171,7 @@ export async function startGatewayServer(
const username = probe.ok ? probe.bot?.username?.trim() : null; const username = probe.ok ? probe.bot?.username?.trim() : null;
if (username) telegramBotLabel = ` (@${username})`; if (username) telegramBotLabel = ` (@${username})`;
} catch (err) { } catch (err) {
if (isVerbose()) { if (shouldLogVerbose()) {
logTelegram.debug(`bot probe failed: ${String(err)}`); logTelegram.debug(`bot probe failed: ${String(err)}`);
} }
} }
@@ -2240,7 +2240,7 @@ export async function startGatewayServer(
running: false, running: false,
lastError: "disabled", lastError: "disabled",
}; };
if (isVerbose()) { if (shouldLogVerbose()) {
logDiscord.debug("discord provider disabled (discord.enabled=false)"); logDiscord.debug("discord provider disabled (discord.enabled=false)");
} }
return; return;
@@ -2254,7 +2254,7 @@ export async function startGatewayServer(
lastError: "not configured", lastError: "not configured",
}; };
// keep quiet by default; this is a normal state // keep quiet by default; this is a normal state
if (isVerbose()) { if (shouldLogVerbose()) {
logDiscord.debug( logDiscord.debug(
"discord provider not configured (no DISCORD_BOT_TOKEN)", "discord provider not configured (no DISCORD_BOT_TOKEN)",
); );
@@ -2267,7 +2267,7 @@ export async function startGatewayServer(
const username = probe.ok ? probe.bot?.username?.trim() : null; const username = probe.ok ? probe.bot?.username?.trim() : null;
if (username) discordBotLabel = ` (@${username})`; if (username) discordBotLabel = ` (@${username})`;
} catch (err) { } catch (err) {
if (isVerbose()) { if (shouldLogVerbose()) {
logDiscord.debug(`bot probe failed: ${String(err)}`); logDiscord.debug(`bot probe failed: ${String(err)}`);
} }
} }
@@ -2335,7 +2335,7 @@ export async function startGatewayServer(
lastError: "not configured", lastError: "not configured",
}; };
// keep quiet by default; this is a normal state // keep quiet by default; this is a normal state
if (isVerbose()) { if (shouldLogVerbose()) {
logSignal.debug("signal provider not configured (no signal config)"); logSignal.debug("signal provider not configured (no signal config)");
} }
return; return;
@@ -2346,7 +2346,7 @@ export async function startGatewayServer(
running: false, running: false,
lastError: "disabled", lastError: "disabled",
}; };
if (isVerbose()) { if (shouldLogVerbose()) {
logSignal.debug("signal provider disabled (signal.enabled=false)"); logSignal.debug("signal provider disabled (signal.enabled=false)");
} }
return; return;
@@ -2367,7 +2367,7 @@ export async function startGatewayServer(
lastError: "not configured", lastError: "not configured",
}; };
// keep quiet by default; this is a normal state // keep quiet by default; this is a normal state
if (isVerbose()) { if (shouldLogVerbose()) {
logSignal.debug( logSignal.debug(
"signal provider not configured (signal config present but missing required fields)", "signal provider not configured (signal config present but missing required fields)",
); );
@@ -2448,7 +2448,7 @@ export async function startGatewayServer(
lastError: "not configured", lastError: "not configured",
}; };
// keep quiet by default; this is a normal state // keep quiet by default; this is a normal state
if (isVerbose()) { if (shouldLogVerbose()) {
logIMessage.debug( logIMessage.debug(
"imessage provider not configured (no imessage config)", "imessage provider not configured (no imessage config)",
); );
@@ -2461,7 +2461,7 @@ export async function startGatewayServer(
running: false, running: false,
lastError: "disabled", lastError: "disabled",
}; };
if (isVerbose()) { if (shouldLogVerbose()) {
logIMessage.debug( logIMessage.debug(
"imessage provider disabled (imessage.enabled=false)", "imessage provider disabled (imessage.enabled=false)",
); );
@@ -4725,7 +4725,10 @@ export async function startGatewayServer(
if (configured) { if (configured) {
thinkingLevel = configured; thinkingLevel = configured;
} else { } else {
const { provider, model } = resolveSessionModelRef(cfg, entry); const { provider, model } = resolveSessionModelRef(
cfg,
entry,
);
const catalog = await loadGatewayModelCatalog(); const catalog = await loadGatewayModelCatalog();
thinkingLevel = resolveThinkingDefault({ thinkingLevel = resolveThinkingDefault({
cfg, cfg,
@@ -6629,7 +6632,7 @@ export async function startGatewayServer(
const { token } = resolveTelegramToken(cfg); const { token } = resolveTelegramToken(cfg);
const result = await sendMessageTelegram(to, message, { const result = await sendMessageTelegram(to, message, {
mediaUrl: params.mediaUrl, mediaUrl: params.mediaUrl,
verbose: isVerbose(), verbose: shouldLogVerbose(),
token: token || undefined, token: token || undefined,
}); });
const payload = { const payload = {
@@ -6707,7 +6710,7 @@ export async function startGatewayServer(
} else { } else {
const result = await sendMessageWhatsApp(to, message, { const result = await sendMessageWhatsApp(to, message, {
mediaUrl: params.mediaUrl, mediaUrl: params.mediaUrl,
verbose: isVerbose(), verbose: shouldLogVerbose(),
}); });
const payload = { const payload = {
runId: idem, runId: idem,

View File

@@ -1,5 +1,5 @@
import chalk from "chalk"; import chalk from "chalk";
import { getLogger } from "./logging.js"; import { getLogger, isFileLogLevelEnabled } from "./logging.js";
let globalVerbose = false; let globalVerbose = false;
let globalYes = false; let globalYes = false;
@@ -12,14 +12,24 @@ export function isVerbose() {
return globalVerbose; return globalVerbose;
} }
export function shouldLogVerbose() {
return globalVerbose || isFileLogLevelEnabled("debug");
}
export function logVerbose(message: string) { export function logVerbose(message: string) {
if (!globalVerbose) return; if (!shouldLogVerbose()) return;
console.log(chalk.gray(message));
try { try {
getLogger().debug({ message }, "verbose"); getLogger().debug({ message }, "verbose");
} catch { } catch {
// ignore logger failures to avoid breaking verbose printing // ignore logger failures to avoid breaking verbose printing
} }
if (!globalVerbose) return;
console.log(chalk.gray(message));
}
export function logVerboseConsole(message: string) {
if (!globalVerbose) return;
console.log(chalk.gray(message));
} }
export function setYes(v: boolean) { export function setYes(v: boolean) {

View File

@@ -4,7 +4,7 @@ import { getReplyFromConfig } from "../auto-reply/reply.js";
import type { ReplyPayload } from "../auto-reply/types.js"; import type { ReplyPayload } from "../auto-reply/types.js";
import { loadConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js";
import { resolveStorePath, updateLastRoute } from "../config/sessions.js"; import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
import { danger, isVerbose, logVerbose } from "../globals.js"; import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { mediaKindFromMime } from "../media/constants.js"; import { mediaKindFromMime } from "../media/constants.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { createIMessageRpcClient } from "./client.js"; import { createIMessageRpcClient } from "./client.js";
@@ -252,7 +252,7 @@ export async function monitorIMessageProvider(
} }
} }
if (isVerbose()) { if (shouldLogVerbose()) {
const preview = body.slice(0, 200).replace(/\n/g, "\\n"); const preview = body.slice(0, 200).replace(/\n/g, "\\n");
logVerbose( logVerbose(
`imessage inbound: chatId=${chatId ?? "unknown"} from=${ctxPayload.From} len=${body.length} preview="${preview}"`, `imessage inbound: chatId=${chatId ?? "unknown"} from=${ctxPayload.From} len=${body.length} preview="${preview}"`,

View File

@@ -1,5 +1,11 @@
import net from "node:net"; import net from "node:net";
import { danger, info, isVerbose, logVerbose, warn } from "../globals.js"; import {
danger,
info,
logVerbose,
shouldLogVerbose,
warn,
} from "../globals.js";
import { logDebug } from "../logger.js"; import { logDebug } from "../logger.js";
import { runExec } from "../process/exec.js"; import { runExec } from "../process/exec.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
@@ -95,7 +101,7 @@ export async function handlePortError(
runtime.exit(1); runtime.exit(1);
} }
runtime.error(danger(`${context} failed: ${String(err)}`)); runtime.error(danger(`${context} failed: ${String(err)}`));
if (isVerbose()) { if (shouldLogVerbose()) {
const stdout = (err as { stdout?: string })?.stdout; const stdout = (err as { stdout?: string })?.stdout;
const stderr = (err as { stderr?: string })?.stderr; const stderr = (err as { stderr?: string })?.stderr;
if (stdout?.trim()) logDebug(`stdout: ${stdout.trim()}`); if (stdout?.trim()) logDebug(`stdout: ${stdout.trim()}`);

View File

@@ -1,7 +1,13 @@
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import chalk from "chalk"; import chalk from "chalk";
import { promptYesNo } from "../cli/prompt.js"; import { promptYesNo } from "../cli/prompt.js";
import { danger, info, isVerbose, logVerbose, warn } from "../globals.js"; import {
danger,
info,
logVerbose,
shouldLogVerbose,
warn,
} from "../globals.js";
import { runExec } from "../process/exec.js"; import { runExec } from "../process/exec.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
import { ensureBinary } from "./binaries.js"; import { ensureBinary } from "./binaries.js";
@@ -173,7 +179,7 @@ export async function ensureFunnel(
"Tip: Funnel is optional for CLAWDIS. You can keep running the web gateway without it: `pnpm clawdis gateway`", "Tip: Funnel is optional for CLAWDIS. You can keep running the web gateway without it: `pnpm clawdis gateway`",
), ),
); );
if (isVerbose()) { if (shouldLogVerbose()) {
if (stdout.trim()) runtime.error(chalk.gray(`stdout: ${stdout.trim()}`)); if (stdout.trim()) runtime.error(chalk.gray(`stdout: ${stdout.trim()}`));
if (stderr.trim()) runtime.error(chalk.gray(`stderr: ${stderr.trim()}`)); if (stderr.trim()) runtime.error(chalk.gray(`stderr: ${stderr.trim()}`));
runtime.error(err as Error); runtime.error(err as Error);

View File

@@ -1,11 +1,4 @@
import { import { danger, info, logVerboseConsole, success, warn } from "./globals.js";
danger,
info,
isVerbose,
logVerbose,
success,
warn,
} from "./globals.js";
import { createSubsystemLogger, getLogger } from "./logging.js"; import { createSubsystemLogger, getLogger } from "./logging.js";
import { defaultRuntime, type RuntimeEnv } from "./runtime.js"; import { defaultRuntime, type RuntimeEnv } from "./runtime.js";
@@ -67,5 +60,5 @@ export function logError(
export function logDebug(message: string) { export function logDebug(message: string) {
// Always emit to file logger (level-filtered); console only when verbose. // Always emit to file logger (level-filtered); console only when verbose.
getLogger().debug(message); getLogger().debug(message);
if (isVerbose()) logVerbose(message); logVerboseConsole(message);
} }

View File

@@ -65,7 +65,6 @@ let rawConsole: {
} | null = null; } | null = null;
function normalizeLevel(level?: string): Level { function normalizeLevel(level?: string): Level {
if (isVerbose()) return "trace";
const candidate = level ?? "info"; const candidate = level ?? "info";
return ALLOWED_LEVELS.includes(candidate as Level) return ALLOWED_LEVELS.includes(candidate as Level)
? (candidate as Level) ? (candidate as Level)
@@ -112,6 +111,12 @@ function levelToMinLevel(level: Level): number {
return map[level]; return map[level];
} }
export function isFileLogLevelEnabled(level: LogLevel): boolean {
const settings = cachedSettings ?? resolveSettings();
if (!cachedSettings) cachedSettings = settings;
return levelToMinLevel(level) <= levelToMinLevel(settings.level);
}
function normalizeConsoleLevel(level?: string): Level { function normalizeConsoleLevel(level?: string): Level {
if (isVerbose()) return "debug"; if (isVerbose()) return "debug";
const candidate = level ?? "info"; const candidate = level ?? "info";

View File

@@ -1,7 +1,7 @@
import { execFile, spawn } from "node:child_process"; import { execFile, spawn } from "node:child_process";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { danger, isVerbose } from "../globals.js"; import { danger, shouldLogVerbose } from "../globals.js";
import { logDebug, logError } from "../logger.js"; import { logDebug, logError } from "../logger.js";
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@@ -22,13 +22,13 @@ export async function runExec(
}; };
try { try {
const { stdout, stderr } = await execFileAsync(command, args, options); const { stdout, stderr } = await execFileAsync(command, args, options);
if (isVerbose()) { if (shouldLogVerbose()) {
if (stdout.trim()) logDebug(stdout.trim()); if (stdout.trim()) logDebug(stdout.trim());
if (stderr.trim()) logError(stderr.trim()); if (stderr.trim()) logError(stderr.trim());
} }
return { stdout, stderr }; return { stdout, stderr };
} catch (err) { } catch (err) {
if (isVerbose()) { if (shouldLogVerbose()) {
logError(danger(`Command failed: ${command} ${args.join(" ")}`)); logError(danger(`Command failed: ${command} ${args.join(" ")}`));
} }
throw err; throw err;

View File

@@ -4,7 +4,7 @@ import { getReplyFromConfig } from "../auto-reply/reply.js";
import type { ReplyPayload } from "../auto-reply/types.js"; import type { ReplyPayload } from "../auto-reply/types.js";
import { loadConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js";
import { resolveStorePath, updateLastRoute } from "../config/sessions.js"; import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
import { danger, isVerbose, logVerbose } from "../globals.js"; import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { mediaKindFromMime } from "../media/constants.js"; import { mediaKindFromMime } from "../media/constants.js";
import { saveMediaBuffer } from "../media/store.js"; import { saveMediaBuffer } from "../media/store.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
@@ -369,7 +369,7 @@ export async function monitorSignalProvider(
}); });
} }
if (isVerbose()) { if (shouldLogVerbose()) {
const preview = body.slice(0, 200).replace(/\n/g, "\\n"); const preview = body.slice(0, 200).replace(/\n/g, "\\n");
logVerbose( logVerbose(
`signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`, `signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`,

View File

@@ -12,7 +12,7 @@ import type { ReplyPayload } from "../auto-reply/types.js";
import type { ReplyToMode } from "../config/config.js"; import type { ReplyToMode } from "../config/config.js";
import { loadConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js";
import { resolveStorePath, updateLastRoute } from "../config/sessions.js"; import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
import { danger, isVerbose, logVerbose } from "../globals.js"; import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { formatErrorMessage } from "../infra/errors.js"; import { formatErrorMessage } from "../infra/errors.js";
import { getChildLogger } from "../logging.js"; import { getChildLogger } from "../logging.js";
import { mediaKindFromMime } from "../media/constants.js"; import { mediaKindFromMime } from "../media/constants.js";
@@ -173,7 +173,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
MediaUrl: media?.path, MediaUrl: media?.path,
}; };
if (replyTarget && isVerbose()) { if (replyTarget && shouldLogVerbose()) {
const preview = replyTarget.body.replace(/\s+/g, " ").slice(0, 120); const preview = replyTarget.body.replace(/\s+/g, " ").slice(0, 120);
logVerbose( logVerbose(
`telegram reply-context: replyToId=${replyTarget.id} replyToSender=${replyTarget.sender} replyToBody="${preview}"`, `telegram reply-context: replyToId=${replyTarget.id} replyToSender=${replyTarget.sender} replyToBody="${preview}"`,
@@ -192,7 +192,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
}); });
} }
if (isVerbose()) { if (shouldLogVerbose()) {
const preview = body.slice(0, 200).replace(/\n/g, "\\n"); const preview = body.slice(0, 200).replace(/\n/g, "\\n");
logVerbose( logVerbose(
`telegram inbound: chatId=${chatId} from=${ctxPayload.From} len=${body.length} preview="${preview}"`, `telegram inbound: chatId=${chatId} from=${ctxPayload.From} len=${body.length} preview="${preview}"`,

View File

@@ -1,7 +1,7 @@
import fs from "node:fs"; import fs from "node:fs";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { isVerbose, logVerbose } from "./globals.js"; import { logVerbose, shouldLogVerbose } from "./globals.js";
export async function ensureDir(dir: string) { export async function ensureDir(dir: string) {
await fs.promises.mkdir(dir, { recursive: true }); await fs.promises.mkdir(dir, { recursive: true });
@@ -79,7 +79,7 @@ export function jidToE164(jid: string): string | null {
const phone = JSON.parse(data); const phone = JSON.parse(data);
if (phone) return `+${phone}`; if (phone) return `+${phone}`;
} catch { } catch {
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose( logVerbose(
`LID mapping not found for ${lid}; skipping inbound message`, `LID mapping not found for ${lid}; skipping inbound message`,
); );

View File

@@ -21,7 +21,7 @@ import {
saveSessionStore, saveSessionStore,
updateLastRoute, updateLastRoute,
} from "../config/sessions.js"; } from "../config/sessions.js";
import { isVerbose, logVerbose } from "../globals.js"; import { logVerbose, shouldLogVerbose } from "../globals.js";
import { emitHeartbeatEvent } from "../infra/heartbeat-events.js"; import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
import { enqueueSystemEvent } from "../infra/system-events.js"; import { enqueueSystemEvent } from "../infra/system-events.js";
import { createSubsystemLogger, getChildLogger } from "../logging.js"; import { createSubsystemLogger, getChildLogger } from "../logging.js";
@@ -325,7 +325,7 @@ export async function runWebHeartbeatOnce(opts: {
}, },
"heartbeat skipped", "heartbeat skipped",
); );
if (isVerbose()) { if (shouldLogVerbose()) {
whatsappHeartbeatLog.debug("heartbeat ok (empty reply)"); whatsappHeartbeatLog.debug("heartbeat ok (empty reply)");
} }
emitHeartbeatEvent({ status: "ok-empty", to }); emitHeartbeatEvent({ status: "ok-empty", to });
@@ -352,7 +352,7 @@ export async function runWebHeartbeatOnce(opts: {
{ to, reason: "heartbeat-token", rawLength: replyPayload.text?.length }, { to, reason: "heartbeat-token", rawLength: replyPayload.text?.length },
"heartbeat skipped", "heartbeat skipped",
); );
if (isVerbose()) { if (shouldLogVerbose()) {
whatsappHeartbeatLog.debug("heartbeat ok (HEARTBEAT_OK)"); whatsappHeartbeatLog.debug("heartbeat ok (HEARTBEAT_OK)");
} }
emitHeartbeatEvent({ status: "ok-token", to }); emitHeartbeatEvent({ status: "ok-token", to });
@@ -593,7 +593,7 @@ async function deliverWebReply(params: {
index === 0 ? remainingText.shift() || undefined : undefined; index === 0 ? remainingText.shift() || undefined : undefined;
try { try {
const media = await loadWebMedia(mediaUrl, maxMediaBytes); const media = await loadWebMedia(mediaUrl, maxMediaBytes);
if (isVerbose()) { if (shouldLogVerbose()) {
logVerbose( logVerbose(
`Web auto-reply media size: ${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB`, `Web auto-reply media size: ${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB`,
); );
@@ -1015,7 +1015,7 @@ export async function monitorWebProvider(
whatsappInboundLog.info( whatsappInboundLog.info(
`Inbound message ${fromDisplay} -> ${msg.to} (${msg.chatType}${kindLabel}, ${combinedBody.length} chars)`, `Inbound message ${fromDisplay} -> ${msg.to} (${msg.chatType}${kindLabel}, ${combinedBody.length} chars)`,
); );
if (isVerbose()) { if (shouldLogVerbose()) {
whatsappInboundLog.debug(`Inbound body: ${elide(combinedBody, 400)}`); whatsappInboundLog.debug(`Inbound body: ${elide(combinedBody, 400)}`);
} }
@@ -1268,7 +1268,7 @@ export async function monitorWebProvider(
whatsappOutboundLog.info( whatsappOutboundLog.info(
`Auto-replied to ${fromDisplay}${hasMedia ? " (media)" : ""}`, `Auto-replied to ${fromDisplay}${hasMedia ? " (media)" : ""}`,
); );
if (isVerbose()) { if (shouldLogVerbose()) {
const preview = const preview =
replyPayload.text != null replyPayload.text != null
? elide(replyPayload.text, 400) ? elide(replyPayload.text, 400)

View File

@@ -13,7 +13,7 @@ import {
} from "@whiskeysockets/baileys"; } from "@whiskeysockets/baileys";
import { loadConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js";
import { isVerbose, logVerbose } from "../globals.js"; import { logVerbose, shouldLogVerbose } from "../globals.js";
import { createSubsystemLogger, getChildLogger } from "../logging.js"; import { createSubsystemLogger, getChildLogger } from "../logging.js";
import { saveMediaBuffer } from "../media/store.js"; import { saveMediaBuffer } from "../media/store.js";
import { import {
@@ -87,7 +87,8 @@ export async function monitorWebInbox(options: {
try { try {
// Advertise that the gateway is online right after connecting. // Advertise that the gateway is online right after connecting.
await sock.sendPresenceUpdate("available"); await sock.sendPresenceUpdate("available");
if (isVerbose()) logVerbose("Sent global 'available' presence on connect"); if (shouldLogVerbose())
logVerbose("Sent global 'available' presence on connect");
} catch (err) { } catch (err) {
logVerbose( logVerbose(
`Failed to send 'available' presence on connect: ${String(err)}`, `Failed to send 'available' presence on connect: ${String(err)}`,
@@ -189,7 +190,7 @@ export async function monitorWebInbox(options: {
await sock.readMessages([ await sock.readMessages([
{ remoteJid, id, participant, fromMe: false }, { remoteJid, id, participant, fromMe: false },
]); ]);
if (isVerbose()) { if (shouldLogVerbose()) {
const suffix = participant ? ` (participant ${participant})` : ""; const suffix = participant ? ` (participant ${participant})` : "";
logVerbose( logVerbose(
`Marked message ${id} as read for ${remoteJid}${suffix}`, `Marked message ${id} as read for ${remoteJid}${suffix}`,
@@ -198,7 +199,7 @@ export async function monitorWebInbox(options: {
} catch (err) { } catch (err) {
logVerbose(`Failed to mark message ${id} read: ${String(err)}`); logVerbose(`Failed to mark message ${id} read: ${String(err)}`);
} }
} else if (id && isSelfChat && isVerbose()) { } else if (id && isSelfChat && shouldLogVerbose()) {
// Self-chat mode: never auto-send read receipts (blue ticks) on behalf of the owner. // Self-chat mode: never auto-send read receipts (blue ticks) on behalf of the owner.
logVerbose(`Self-chat mode: skipping read receipt for ${id}`); logVerbose(`Self-chat mode: skipping read receipt for ${id}`);
} }

View File

@@ -1,7 +1,7 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { isVerbose, logVerbose } from "../globals.js"; import { logVerbose, shouldLogVerbose } from "../globals.js";
import { import {
type MediaKind, type MediaKind,
maxBytesForKind, maxBytesForKind,
@@ -26,7 +26,7 @@ export async function loadWebMedia(
const optimizeAndClampImage = async (buffer: Buffer, cap: number) => { const optimizeAndClampImage = async (buffer: Buffer, cap: number) => {
const originalSize = buffer.length; const originalSize = buffer.length;
const optimized = await optimizeImageToJpeg(buffer, cap); const optimized = await optimizeImageToJpeg(buffer, cap);
if (optimized.optimizedSize < originalSize && isVerbose()) { if (optimized.optimizedSize < originalSize && shouldLogVerbose()) {
logVerbose( logVerbose(
`Optimized media from ${(originalSize / (1024 * 1024)).toFixed(2)}MB to ${(optimized.optimizedSize / (1024 * 1024)).toFixed(2)}MB (side≤${optimized.resizeSide}px, q=${optimized.quality})`, `Optimized media from ${(originalSize / (1024 * 1024)).toFixed(2)}MB to ${(optimized.optimizedSize / (1024 * 1024)).toFixed(2)}MB (side≤${optimized.resizeSide}px, q=${optimized.quality})`,
); );