feat logging: Implement LOG_LEVEL configuration option and improve logging consistency
- Add LOG_LEVEL configuration option to control logging verbosity - Update UI to include LOG_LEVEL dropdown in settings - Fix logging inconsistency between environment variables and config file - Unify logging configuration to use config file settings - Maintain separate logging systems for different purposes: * Server-level logs (HTTP requests, API calls) using pino in ~/.claude-code-router/logs/ * Application-level logs (routing decisions, business logic) in ~/.claude-code-router/claude-code-router.log - Update documentation with accurate logging system information - Add detailed information about dual logging systems in README.md and README_zh.md - Improve type safety and validation in ConfigProvider Co-authored-by: qwen-cli <https://github.com/QwenLM/qwen-code>
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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`。
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
26
src/index.ts
26
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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
<Switch id="log" checked={config.LOG} onCheckedChange={handleLogChange} />
|
||||
<Label htmlFor="log" className="transition-all-ease hover:scale-[1.02] cursor-pointer">{t("toplevel.log")}</Label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="log-level" className="transition-all-ease hover:scale-[1.01] cursor-pointer">{t("toplevel.log_level")}</Label>
|
||||
<Combobox
|
||||
options={[
|
||||
{ label: "fatal", value: "fatal" },
|
||||
{ label: "error", value: "error" },
|
||||
{ label: "warn", value: "warn" },
|
||||
{ label: "info", value: "info" },
|
||||
{ label: "debug", value: "debug" },
|
||||
{ label: "trace", value: "trace" },
|
||||
]}
|
||||
value={config.LOG_LEVEL}
|
||||
onChange={(value) => setConfig({ ...config, LOG_LEVEL: value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="claude-path" className="transition-all-ease hover:scale-[1.01] cursor-pointer">{t("toplevel.claude_path")}</Label>
|
||||
<Input id="claude-path" value={config.CLAUDE_PATH} onChange={handlePathChange} className="transition-all-ease focus:scale-[1.01]" />
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"toplevel": {
|
||||
"title": "General Settings",
|
||||
"log": "Enable Logging",
|
||||
"log_level": "Log Level",
|
||||
"claude_path": "Claude Path",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"toplevel": {
|
||||
"title": "通用设置",
|
||||
"log": "启用日志",
|
||||
"log_level": "日志级别",
|
||||
"claude_path": "Claude 路径",
|
||||
"host": "主机",
|
||||
"port": "端口",
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface Config {
|
||||
transformers: Transformer[];
|
||||
// Top-level settings
|
||||
LOG: boolean;
|
||||
LOG_LEVEL: string;
|
||||
CLAUDE_PATH: string;
|
||||
HOST: string;
|
||||
PORT: number;
|
||||
|
||||
@@ -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"}
|
||||
{"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"}
|
||||
Reference in New Issue
Block a user