Skip to content

Commit ce920e4

Browse files
committed
🤖 refactor: unify logging with configurable log levels
- Add log levels: error, warn, info, debug (hierarchical) - Add log.warn() method and log.setLevel()/getLevel() for programmatic control - CLI defaults to 'error' level (quiet), Desktop defaults to 'info' - Add --verbose/-v flag to mux run to enable info-level logs - Add --log-level flag for explicit level control - Migrate all console.error/warn calls in src/node/ to log.* - Migrate ORPC error handler to use log.error - Fix circular dependency: log.ts no longer imports defaultConfig Environment variable MUX_LOG_LEVEL=error|warn|info|debug overrides defaults. MUX_DEBUG=1 enables debug level as before. _Generated with mux_
1 parent ae243a3 commit ce920e4

18 files changed

+200
-77
lines changed

src/cli/orpcServer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { router } from "@/node/orpc/router";
1717
import type { ORPCContext } from "@/node/orpc/context";
1818
import { extractWsHeaders } from "@/node/orpc/authMiddleware";
1919
import { VERSION } from "@/version";
20+
import { log } from "@/node/services/log";
2021

2122
// --- Types ---
2223

@@ -71,7 +72,7 @@ export async function createOrpcServer({
7172
context,
7273
serveStatic = false,
7374
staticDir = path.join(__dirname, ".."),
74-
onOrpcError = (error) => console.error("ORPC Error:", error),
75+
onOrpcError = (error) => log.error("ORPC Error:", error),
7576
}: OrpcServerOptions): Promise<OrpcServer> {
7677
// Express app setup
7778
const app = express();

src/cli/run.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import type { RuntimeConfig } from "@/common/types/runtime";
3838
import { parseRuntimeModeAndHost, RUNTIME_MODE } from "@/common/types/runtime";
3939
import assert from "@/common/utils/assert";
4040
import parseDuration from "parse-duration";
41+
import { log, type LogLevel } from "@/node/services/log";
4142

4243
type CLIMode = "plan" | "exec";
4344

@@ -163,6 +164,8 @@ program
163164
.option("--mode <mode>", "agent mode: plan or exec", "exec")
164165
.option("-t, --thinking <level>", "thinking level: off, low, medium, high", "medium")
165166
.option("--timeout <duration>", "timeout (e.g., 5m, 300s, 300000)")
167+
.option("-v, --verbose", "show info-level logs (default: errors only)")
168+
.option("--log-level <level>", "set log level: error, warn, info, debug")
166169
.option("--json", "output NDJSON for programmatic consumption")
167170
.option("-q, --quiet", "only output final result")
168171
.option("--workspace-id <id>", "explicit workspace ID (auto-generated if not provided)")
@@ -189,6 +192,8 @@ interface CLIOptions {
189192
mode: string;
190193
thinking: string;
191194
timeout?: string;
195+
verbose?: boolean;
196+
logLevel?: string;
192197
json?: boolean;
193198
quiet?: boolean;
194199
workspaceId?: string;
@@ -199,6 +204,20 @@ const opts = program.opts<CLIOptions>();
199204
const messageArg = program.args[0];
200205

201206
async function main(): Promise<void> {
207+
// Configure log level early (before any logging happens)
208+
if (opts.logLevel) {
209+
const level = opts.logLevel.toLowerCase();
210+
if (level === "error" || level === "warn" || level === "info" || level === "debug") {
211+
log.setLevel(level as LogLevel);
212+
} else {
213+
console.error(`Invalid log level "${opts.logLevel}". Expected: error, warn, info, debug`);
214+
process.exit(1);
215+
}
216+
} else if (opts.verbose) {
217+
log.setLevel("info");
218+
}
219+
// Default is already "warn" for CLI mode (set in log.ts)
220+
202221
// Resolve directory
203222
const projectDir = path.resolve(opts.dir);
204223
await ensureDirectory(projectDir);
@@ -236,14 +255,13 @@ async function main(): Promise<void> {
236255
if (emitJson) process.stdout.write(`${JSON.stringify(payload)}\n`);
237256
};
238257

239-
if (!suppressHumanOutput) {
240-
console.error(`[mux run] Directory: ${projectDir}`);
241-
console.error(`[mux run] Model: ${model}`);
242-
console.error(
243-
`[mux run] Runtime: ${runtimeConfig.type}${runtimeConfig.type === "ssh" ? ` (${runtimeConfig.host})` : ""}`
244-
);
245-
console.error(`[mux run] Mode: ${initialMode}`);
246-
}
258+
// Log startup info (shown at info+ level, i.e., with --verbose)
259+
log.info(`Directory: ${projectDir}`);
260+
log.info(`Model: ${model}`);
261+
log.info(
262+
`Runtime: ${runtimeConfig.type}${runtimeConfig.type === "ssh" ? ` (${runtimeConfig.host})` : ""}`
263+
);
264+
log.info(`Mode: ${initialMode}`);
247265

248266
// Initialize services
249267
const historyService = new HistoryService(config);

src/node/config.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as path from "path";
33
import * as crypto from "crypto";
44
import * as jsonc from "jsonc-parser";
55
import writeFileAtomic from "write-file-atomic";
6+
import { log } from "@/node/services/log";
67
import type { WorkspaceMetadata, FrontendWorkspaceMetadata } from "@/common/types/workspace";
78
import type { Secret, SecretsConfig } from "@/common/types/secrets";
89
import type { Workspace, ProjectConfig, ProjectsConfig } from "@/common/types/project";
@@ -64,7 +65,7 @@ export class Config {
6465
}
6566
}
6667
} catch (error) {
67-
console.error("Error loading config:", error);
68+
log.error("Error loading config:", error);
6869
}
6970

7071
// Return default config
@@ -85,7 +86,7 @@ export class Config {
8586

8687
await writeFileAtomic(this.configFile, JSON.stringify(data, null, 2), "utf-8");
8788
} catch (error) {
88-
console.error("Error saving config:", error);
89+
log.error("Error saving config:", error);
8990
}
9091
}
9192

@@ -344,7 +345,7 @@ export class Config {
344345
workspaceMetadata.push(this.addPathsToMetadata(metadata, workspace.path, projectPath));
345346
}
346347
} catch (error) {
347-
console.error(`Failed to load/migrate workspace metadata:`, error);
348+
log.error(`Failed to load/migrate workspace metadata:`, error);
348349
// Fallback to basic metadata if migration fails
349350
const legacyId = this.generateLegacyId(projectPath, workspace.path);
350351
const metadata: WorkspaceMetadata = {
@@ -431,7 +432,7 @@ export class Config {
431432
}
432433

433434
if (!workspaceFound) {
434-
console.warn(`Workspace ${workspaceId} not found in config during removal`);
435+
log.warn(`Workspace ${workspaceId} not found in config during removal`);
435436
}
436437

437438
return config;
@@ -469,7 +470,7 @@ export class Config {
469470
return jsonc.parse(data) as ProvidersConfig;
470471
}
471472
} catch (error) {
472-
console.error("Error loading providers config:", error);
473+
log.error("Error loading providers config:", error);
473474
}
474475

475476
return null;
@@ -510,7 +511,7 @@ ${jsonString}`;
510511

511512
fs.writeFileSync(this.providersFile, contentWithComments);
512513
} catch (error) {
513-
console.error("Error saving providers config:", error);
514+
log.error("Error saving providers config:", error);
514515
throw error; // Re-throw to let caller handle
515516
}
516517
}
@@ -526,7 +527,7 @@ ${jsonString}`;
526527
return JSON.parse(data) as SecretsConfig;
527528
}
528529
} catch (error) {
529-
console.error("Error loading secrets config:", error);
530+
log.error("Error loading secrets config:", error);
530531
}
531532

532533
return {};
@@ -544,7 +545,7 @@ ${jsonString}`;
544545

545546
await writeFileAtomic(this.secretsFile, JSON.stringify(config, null, 2), "utf-8");
546547
} catch (error) {
547-
console.error("Error saving secrets config:", error);
548+
log.error("Error saving secrets config:", error);
548549
throw error;
549550
}
550551
}

src/node/services/ExtensionMetadataService.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getExtensionMetadataPath,
99
} from "@/node/utils/extensionMetadata";
1010
import type { WorkspaceActivitySnapshot } from "@/common/types/workspace";
11+
import { log } from "@/node/services/log";
1112

1213
/**
1314
* Stateless service for managing workspace metadata used by VS Code extension integration.
@@ -74,13 +75,13 @@ export class ExtensionMetadataService {
7475

7576
// Validate structure
7677
if (typeof parsed !== "object" || parsed.version !== 1) {
77-
console.error("[ExtensionMetadataService] Invalid metadata file, resetting");
78+
log.error("Invalid metadata file, resetting");
7879
return { version: 1, workspaces: {} };
7980
}
8081

8182
return parsed;
8283
} catch (error) {
83-
console.error("[ExtensionMetadataService] Failed to load metadata:", error);
84+
log.error("Failed to load metadata:", error);
8485
return { version: 1, workspaces: {} };
8586
}
8687
}
@@ -90,7 +91,7 @@ export class ExtensionMetadataService {
9091
const content = JSON.stringify(data, null, 2);
9192
await writeFileAtomic(this.filePath, content, "utf-8");
9293
} catch (error) {
93-
console.error("[ExtensionMetadataService] Failed to save metadata:", error);
94+
log.error("Failed to save metadata:", error);
9495
}
9596
}
9697

src/node/services/compactionHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { LanguageModelV2Usage } from "@ai-sdk/provider";
88
import { collectUsageHistory } from "@/common/utils/tokens/displayUsage";
99
import { sumUsageHistory } from "@/common/utils/tokens/usageAggregator";
1010
import { createMuxMessage, type MuxMessage } from "@/common/types/message";
11+
import { log } from "@/node/services/log";
1112

1213
interface CompactionHandlerOptions {
1314
workspaceId: string;
@@ -71,7 +72,7 @@ export class CompactionHandler {
7172

7273
const result = await this.performCompaction(summary, messages, event.metadata);
7374
if (!result.success) {
74-
console.error("[CompactionHandler] Compaction failed:", result.error);
75+
log.error("Compaction failed:", result.error);
7576
return false;
7677
}
7778

src/node/services/historyService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ export class HistoryService {
5454
messages.push(normalizeLegacyMuxMetadata(message));
5555
} catch (parseError) {
5656
// Skip malformed lines but log error for debugging
57-
console.error(
58-
`[HistoryService] Skipping malformed JSON at line ${i + 1} in ${workspaceId}/chat.jsonl:`,
57+
log.warn(
58+
`Skipping malformed JSON at line ${i + 1} in ${workspaceId}/chat.jsonl:`,
5959
parseError instanceof Error ? parseError.message : String(parseError),
6060
"\nLine content:",
6161
lines[i].substring(0, 100) + (lines[i].length > 100 ? "..." : "")

0 commit comments

Comments
 (0)