Skip to content

Commit 8fe0a45

Browse files
committed
Add logging functionality and improve configuration handling
1 parent db94f48 commit 8fe0a45

File tree

4 files changed

+79
-6
lines changed

4 files changed

+79
-6
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"ollama-ai-provider-v2": "^1.1.1",
4141
"react": "^19.1.1",
4242
"simple-git": "^3.28.0",
43+
"winston": "^3.17.0",
4344
"zod": "^4.0.17",
4445
"zustand": "^5.0.7"
4546
},

src/cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
import React from "react";
33
import { render } from "ink";
44
import App from "./ui/App.js";
5+
import logger from "./logger.js";
6+
7+
logger.info("Tobi application started.");
58

69
render(React.createElement(App));

src/config.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import fs from "fs/promises";
44
import path from "path";
55
import os from "os";
66
import json5 from "json5";
7-
7+
import logger from "./logger.js";
88
const modelSchema = z.object({
99
name: z.string().describe("A user-friendly nickname for the model."),
1010
provider: z
@@ -81,37 +81,50 @@ const defaultConfig: Config = {
8181
mcpServers: {},
8282
};
8383

84-
export const CONFIG_DIR = path.join(os.homedir(), ".config", "tobi");
85-
export const HISTORY_PATH = path.join(CONFIG_DIR, "history");
86-
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json5");
84+
export function getConfigDir(): string {
85+
return path.join(os.homedir(), ".config", "tobi");
86+
}
87+
export const HISTORY_PATH = path.join(getConfigDir(), "history");
88+
const CONFIG_PATH = path.join(getConfigDir(), "config.json5");
8789

8890
export async function loadConfig(): Promise<Config> {
91+
logger.debug("Attempting to load configuration.");
8992
try {
9093
const configContent = await fs.readFile(CONFIG_PATH, "utf-8");
9194
const parsedConfig = json5.parse(configContent);
9295

9396
// Merge user's config over defaults. This makes adding new keys in updates non-breaking.
9497
const mergedConfig = { ...defaultConfig, ...parsedConfig };
95-
return configSchema.parse(mergedConfig);
98+
const finalConfig = configSchema.parse(mergedConfig);
99+
logger.info("Configuration loaded successfully.");
100+
return finalConfig;
96101
} catch (error: unknown) {
97102
if (error instanceof Error && (error as NodeJS.ErrnoException).code === "ENOENT") {
103+
logger.warn(`Configuration file not found. Creating a default one at: ${CONFIG_PATH}`);
98104
console.warn(`Configuration file not found. Creating a default one at: ${CONFIG_PATH}`);
99105
console.warn(
100106
`Please open this file and update your environment variables (.env) with your API keys.`,
101107
);
102-
await fs.mkdir(CONFIG_DIR, { recursive: true });
108+
await fs.mkdir(getConfigDir(), { recursive: true });
109+
// Also create the logs directory
110+
const LOGS_DIR = path.join(getConfigDir(), "logs");
111+
await fs.mkdir(LOGS_DIR, { recursive: true });
103112
await fs.writeFile(CONFIG_PATH, json5.stringify(defaultConfig, null, 2));
113+
logger.info("Default configuration file created.");
104114
return defaultConfig;
105115
} else if (error instanceof z.ZodError) {
116+
logger.error("Configuration file is invalid:", { error: error.issues });
106117
console.error("Configuration file is invalid:", error.issues);
107118
} else {
119+
logger.error("Failed to load configuration:", { error });
108120
console.error("Failed to load configuration:", error);
109121
}
110122
process.exit(1);
111123
}
112124
}
113125

114126
export async function saveConfig(config: Config): Promise<void> {
127+
logger.debug("Attempting to save configuration.");
115128
try {
116129
// We only want to save the fields that are user-configurable, not the derived ones.
117130
const configToSave: Partial<Config> = {
@@ -123,7 +136,9 @@ export async function saveConfig(config: Config): Promise<void> {
123136
mcpServers: config.mcpServers,
124137
};
125138
await fs.writeFile(CONFIG_PATH, json5.stringify(configToSave, null, 2));
139+
logger.info("Configuration saved successfully.");
126140
} catch (error) {
141+
logger.error("Failed to save configuration:", { error });
127142
console.error("Failed to save configuration:", error);
128143
// We don't want to exit the app if saving fails, but we should let the user know.
129144
}

src/logger.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import winston from "winston";
2+
import path from "path";
3+
4+
import { getConfigDir } from "./config.js";
5+
6+
let logger: winston.Logger | null = null;
7+
8+
function getLogger(): winston.Logger {
9+
if (logger) {
10+
return logger;
11+
}
12+
13+
const LOGS_DIR = path.join(getConfigDir(), "logs");
14+
15+
const isLoggingEnabled = process.env.DEBUG_TOBI !== undefined;
16+
17+
if (isLoggingEnabled) {
18+
logger = winston.createLogger({
19+
level: "debug",
20+
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
21+
transports: [
22+
new winston.transports.File({
23+
filename: path.join(LOGS_DIR, `tobi-${new Date().toISOString()}.log`),
24+
maxsize: 1024 * 1024 * 5, // 5MB
25+
maxFiles: 5,
26+
tailable: true,
27+
zippedArchive: true,
28+
}),
29+
],
30+
});
31+
} else {
32+
logger = winston.createLogger({
33+
transports: [new winston.transports.Console()],
34+
silent: true,
35+
});
36+
}
37+
38+
return logger;
39+
}
40+
41+
// Use a proxy to lazily initialize the logger on first access.
42+
const loggerProxy = new Proxy(
43+
{},
44+
{
45+
get(_, prop) {
46+
const loggerInstance = getLogger();
47+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
48+
// @ts-ignore
49+
return loggerInstance[prop];
50+
},
51+
},
52+
) as winston.Logger;
53+
54+
export default loggerProxy;

0 commit comments

Comments
 (0)