diff --git a/README.md b/README.md index 5b4f804..9866e10 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,11 @@ Create and configure your `~/.claude-code-router/config.json` file. For more det The `config.json` file has several key sections: - **`PROXY_URL`** (optional): You can set a proxy for API requests, for example: `"PROXY_URL": "http://127.0.0.1:7890"`. -- **`LOG`** (optional): You can enable logging by setting it to `true`. The log file will be located at `$HOME/.claude-code-router.log`. +- **`LOG`** (optional): You can enable logging by setting it to `true`. When set to `false`, no log files will be created. Default is `true`. +- **`LOG_LEVEL`** (optional): Set the logging level. Available options are: `"fatal"`, `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. Default is `"info"`. +- **Logging Systems**: The Claude Code Router uses two separate logging systems: + - **Server-level logs**: HTTP requests, API calls, and server events are logged using pino in the `~/.claude-code-router/logs/` directory with filenames like `ccr-*.log` + - **Application-level logs**: Routing decisions and business logic events are logged in `~/.claude-code-router/claude-code-router.log` - **`APIKEY`** (optional): You can set a secret key to authenticate requests. When set, clients must provide this key in the `Authorization` header (e.g., `Bearer your-secret-key`) or the `x-api-key` header. Example: `"APIKEY": "your-secret-key"`. - **`HOST`** (optional): You can set the host address for the server. If `APIKEY` is not set, the host will be forced to `127.0.0.1` for security reasons to prevent unauthorized access. Example: `"HOST": "0.0.0.0"`. - **`NON_INTERACTIVE_MODE`** (optional): When set to `true`, enables compatibility with non-interactive environments like GitHub Actions, Docker containers, or other CI/CD systems. This sets appropriate environment variables (`CI=true`, `FORCE_COLOR=0`, etc.) and configures stdin handling to prevent the process from hanging in automated environments. Example: `"NON_INTERACTIVE_MODE": true`. diff --git a/README_zh.md b/README_zh.md index 708589f..15d1710 100644 --- a/README_zh.md +++ b/README_zh.md @@ -37,7 +37,11 @@ npm install -g @musistudio/claude-code-router `config.json` 文件有几个关键部分: - **`PROXY_URL`** (可选): 您可以为 API 请求设置代理,例如:`"PROXY_URL": "http://127.0.0.1:7890"`。 -- **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。日志文件将位于 `$HOME/.claude-code-router.log`。 +- **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。当设置为 `false` 时,将不会创建日志文件。默认值为 `true`。 +- **`LOG_LEVEL`** (可选): 设置日志级别。可用选项包括:`"fatal"`、`"error"`、`"warn"`、`"info"`、`"debug"`、`"trace"`。默认值为 `"info"`。 +- **日志系统**: Claude Code Router 使用两个独立的日志系统: + - **服务器级别日志**: HTTP 请求、API 调用和服务器事件使用 pino 记录在 `~/.claude-code-router/logs/` 目录中,文件名类似于 `ccr-*.log` + - **应用程序级别日志**: 路由决策和业务逻辑事件记录在 `~/.claude-code-router/claude-code-router.log` 文件中 - **`APIKEY`** (可选): 您可以设置一个密钥来进行身份验证。设置后,客户端请求必须在 `Authorization` 请求头 (例如, `Bearer your-secret-key`) 或 `x-api-key` 请求头中提供此密钥。例如:`"APIKEY": "your-secret-key"`。 - **`HOST`** (可选): 您可以设置服务的主机地址。如果未设置 `APIKEY`,出于安全考虑,主机地址将强制设置为 `127.0.0.1`,以防止未经授权的访问。例如:`"HOST": "0.0.0.0"`。 - **`NON_INTERACTIVE_MODE`** (可选): 当设置为 `true` 时,启用与非交互式环境(如 GitHub Actions、Docker 容器或其他 CI/CD 系统)的兼容性。这会设置适当的环境变量(`CI=true`、`FORCE_COLOR=0` 等)并配置 stdin 处理,以防止进程在自动化环境中挂起。例如:`"NON_INTERACTIVE_MODE": true`。 diff --git a/config.example.json b/config.example.json index bc4d660..a6d592a 100644 --- a/config.example.json +++ b/config.example.json @@ -115,5 +115,7 @@ "APIKEY": "your-secret-key", "HOST": "0.0.0.0", "API_TIMEOUT_MS": 600000, - "NON_INTERACTIVE_MODE": false + "NON_INTERACTIVE_MODE": false, + "LOG": true, + "LOG_LEVEL": "info" } diff --git a/src/index.ts b/src/index.ts index bf94cc8..ecb7e11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import { import { CONFIG_FILE } from "./constants"; import createWriteStream from "pino-rotating-file-stream"; import { HOME_DIR } from "./constants"; +import { configureLogging } from "./utils/log"; async function initializeClaudeConfig() { const homeDir = homedir(); @@ -51,6 +52,10 @@ async function run(options: RunOptions = {}) { // Clean up old log files, keeping only the 10 most recent ones await cleanupLogFiles(); const config = await initConfig(); + + // Configure logging based on config + configureLogging(config); + let HOST = config.HOST; if (config.HOST && !config.APIKEY) { @@ -82,6 +87,17 @@ async function run(options: RunOptions = {}) { ? parseInt(process.env.SERVICE_PORT) : port; + // Configure logger based on config settings + const loggerConfig = config.LOG !== false ? { + level: config.LOG_LEVEL || "info", + stream: createWriteStream({ + path: HOME_DIR, + filename: config.LOGNAME || `./logs/ccr-${+new Date()}.log`, + maxFiles: 3, + interval: "1d", + }), + } : false; + const server = createServer({ jsonPath: CONFIG_FILE, initialConfig: { @@ -95,15 +111,7 @@ async function run(options: RunOptions = {}) { "claude-code-router.log" ), }, - logger: { - level: "debug", - stream: createWriteStream({ - path: HOME_DIR, - filename: config.LOGNAME || `./logs/ccr-${+new Date()}.log`, - maxFiles: 3, - interval: "1d", - }), - }, + logger: loggerConfig, }); // Add async preHandler hook for authentication server.addHook("preHandler", async (req, reply) => { diff --git a/src/utils/log.ts b/src/utils/log.ts index 6999726..111cc8d 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -9,9 +9,21 @@ if (!fs.existsSync(HOME_DIR)) { fs.mkdirSync(HOME_DIR, { recursive: true }); } +// Global variable to store the logging configuration +let isLogEnabled: boolean | null = null; +let logLevel: string = "info"; + +// Function to configure logging +export function configureLogging(config: { LOG?: boolean; LOG_LEVEL?: string }) { + isLogEnabled = config.LOG !== false; // Default to true if not explicitly set to false + logLevel = config.LOG_LEVEL || "info"; +} + export function log(...args: any[]) { - // Check if logging is enabled via environment variable - const isLogEnabled = process.env.LOG === "true"; + // If logging configuration hasn't been set, default to enabled + if (isLogEnabled === null) { + isLogEnabled = true; + } if (!isLogEnabled) { return; diff --git a/ui/src/components/ConfigProvider.tsx b/ui/src/components/ConfigProvider.tsx index aae4d34..f679cee 100644 --- a/ui/src/components/ConfigProvider.tsx +++ b/ui/src/components/ConfigProvider.tsx @@ -69,6 +69,7 @@ export function ConfigProvider({ children }: ConfigProviderProps) { // Validate the received data to ensure it has the expected structure const validConfig = { LOG: typeof data.LOG === 'boolean' ? data.LOG : false, + LOG_LEVEL: typeof data.LOG_LEVEL === 'string' ? data.LOG_LEVEL : 'info', CLAUDE_PATH: typeof data.CLAUDE_PATH === 'string' ? data.CLAUDE_PATH : '', HOST: typeof data.HOST === 'string' ? data.HOST : '127.0.0.1', PORT: typeof data.PORT === 'number' ? data.PORT : 3456, @@ -103,6 +104,7 @@ export function ConfigProvider({ children }: ConfigProviderProps) { // Set default empty config when fetch fails setConfig({ LOG: false, + LOG_LEVEL: 'info', CLAUDE_PATH: '', HOST: '127.0.0.1', PORT: 3456, diff --git a/ui/src/components/SettingsDialog.tsx b/ui/src/components/SettingsDialog.tsx index c966435..9da6ffc 100644 --- a/ui/src/components/SettingsDialog.tsx +++ b/ui/src/components/SettingsDialog.tsx @@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; +import { Combobox } from "@/components/ui/combobox"; import { useConfig } from "./ConfigProvider"; interface SettingsDialogProps { @@ -45,6 +46,21 @@ export function SettingsDialog({ isOpen, onOpenChange }: SettingsDialogProps) { +
+ + setConfig({ ...config, LOG_LEVEL: value })} + /> +
diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json index 78484e9..2994a45 100644 --- a/ui/src/locales/en.json +++ b/ui/src/locales/en.json @@ -36,6 +36,7 @@ "toplevel": { "title": "General Settings", "log": "Enable Logging", + "log_level": "Log Level", "claude_path": "Claude Path", "host": "Host", "port": "Port", diff --git a/ui/src/locales/zh.json b/ui/src/locales/zh.json index 6186df3..be625ed 100644 --- a/ui/src/locales/zh.json +++ b/ui/src/locales/zh.json @@ -36,6 +36,7 @@ "toplevel": { "title": "通用设置", "log": "启用日志", + "log_level": "日志级别", "claude_path": "Claude 路径", "host": "主机", "port": "端口", diff --git a/ui/src/types.ts b/ui/src/types.ts index 5bd9cec..b3cf608 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -33,6 +33,7 @@ export interface Config { transformers: Transformer[]; // Top-level settings LOG: boolean; + LOG_LEVEL: string; CLAUDE_PATH: string; HOST: string; PORT: number; diff --git a/ui/tsconfig.tsbuildinfo b/ui/tsconfig.tsbuildinfo index 62ad4f2..8a65f06 100644 --- a/ui/tsconfig.tsbuildinfo +++ b/ui/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/jsoneditor.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/utils.ts"],"version":"5.8.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/ConfigProvider.tsx","./src/components/JsonEditor.tsx","./src/components/Login.tsx","./src/components/ProtectedRoute.tsx","./src/components/ProviderList.tsx","./src/components/Providers.tsx","./src/components/PublicRoute.tsx","./src/components/Router.tsx","./src/components/SettingsDialog.tsx","./src/components/TransformerList.tsx","./src/components/Transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/utils.ts"],"version":"5.8.3"} \ No newline at end of file