fix logviewer
This commit is contained in:
@@ -115,7 +115,7 @@ async function run(options: RunOptions = {}) {
|
|||||||
path: HOME_DIR,
|
path: HOME_DIR,
|
||||||
maxFiles: 3,
|
maxFiles: 3,
|
||||||
interval: "1d",
|
interval: "1d",
|
||||||
compress: 'gzip',
|
compress: false,
|
||||||
maxSize: "50M"
|
maxSize: "50M"
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,16 +65,16 @@ export const createServer = (config: any): Server => {
|
|||||||
server.app.get("/ui", async (_, reply) => {
|
server.app.get("/ui", async (_, reply) => {
|
||||||
return reply.redirect("/ui/");
|
return reply.redirect("/ui/");
|
||||||
});
|
});
|
||||||
|
|
||||||
// 版本检查端点
|
// 版本检查端点
|
||||||
server.app.get("/api/update/check", async (req, reply) => {
|
server.app.get("/api/update/check", async (req, reply) => {
|
||||||
try {
|
try {
|
||||||
// 获取当前版本
|
// 获取当前版本
|
||||||
const currentVersion = require("../package.json").version;
|
const currentVersion = require("../package.json").version;
|
||||||
const { hasUpdate, latestVersion, changelog } = await checkForUpdates(currentVersion);
|
const { hasUpdate, latestVersion, changelog } = await checkForUpdates(currentVersion);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUpdate,
|
hasUpdate,
|
||||||
latestVersion: hasUpdate ? latestVersion : undefined,
|
latestVersion: hasUpdate ? latestVersion : undefined,
|
||||||
changelog: hasUpdate ? changelog : undefined
|
changelog: hasUpdate ? changelog : undefined
|
||||||
};
|
};
|
||||||
@@ -83,7 +83,7 @@ export const createServer = (config: any): Server => {
|
|||||||
reply.status(500).send({ error: "Failed to check for updates" });
|
reply.status(500).send({ error: "Failed to check for updates" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 执行更新端点
|
// 执行更新端点
|
||||||
server.app.post("/api/update/perform", async (req, reply) => {
|
server.app.post("/api/update/perform", async (req, reply) => {
|
||||||
try {
|
try {
|
||||||
@@ -93,10 +93,10 @@ export const createServer = (config: any): Server => {
|
|||||||
reply.status(403).send("Full access required to perform updates");
|
reply.status(403).send("Full access required to perform updates");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行更新逻辑
|
// 执行更新逻辑
|
||||||
const result = await performUpdate();
|
const result = await performUpdate();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to perform update:", error);
|
console.error("Failed to perform update:", error);
|
||||||
@@ -112,12 +112,12 @@ export const createServer = (config: any): Server => {
|
|||||||
|
|
||||||
if (existsSync(logDir)) {
|
if (existsSync(logDir)) {
|
||||||
const files = readdirSync(logDir);
|
const files = readdirSync(logDir);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.endsWith('.log')) {
|
if (file.endsWith('.log')) {
|
||||||
const filePath = join(logDir, file);
|
const filePath = join(logDir, file);
|
||||||
const stats = statSync(filePath);
|
const stats = statSync(filePath);
|
||||||
|
|
||||||
logFiles.push({
|
logFiles.push({
|
||||||
name: file,
|
name: file,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
@@ -126,7 +126,7 @@ export const createServer = (config: any): Server => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按修改时间倒序排列
|
// 按修改时间倒序排列
|
||||||
logFiles.sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime());
|
logFiles.sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime());
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ export const createServer = (config: any): Server => {
|
|||||||
try {
|
try {
|
||||||
const filePath = (req.query as any).file as string;
|
const filePath = (req.query as any).file as string;
|
||||||
let logFilePath: string;
|
let logFilePath: string;
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
// 如果指定了文件路径,使用指定的路径
|
// 如果指定了文件路径,使用指定的路径
|
||||||
logFilePath = filePath;
|
logFilePath = filePath;
|
||||||
@@ -157,7 +157,7 @@ export const createServer = (config: any): Server => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const logContent = readFileSync(logFilePath, 'utf8');
|
const logContent = readFileSync(logFilePath, 'utf8');
|
||||||
const logLines = logContent.split('\n').filter(line => line.trim());
|
const logLines = logContent.split('\n').filter(line => line.trim())
|
||||||
|
|
||||||
return logLines;
|
return logLines;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -171,7 +171,7 @@ export const createServer = (config: any): Server => {
|
|||||||
try {
|
try {
|
||||||
const filePath = (req.query as any).file as string;
|
const filePath = (req.query as any).file as string;
|
||||||
let logFilePath: string;
|
let logFilePath: string;
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
// 如果指定了文件路径,使用指定的路径
|
// 如果指定了文件路径,使用指定的路径
|
||||||
logFilePath = filePath;
|
logFilePath = filePath;
|
||||||
|
|||||||
@@ -11,11 +11,15 @@ import {join} from "path";
|
|||||||
export async function executeCodeCommand(args: string[] = []) {
|
export async function executeCodeCommand(args: string[] = []) {
|
||||||
// Set environment variables
|
// Set environment variables
|
||||||
const config = await readConfigFile();
|
const config = await readConfigFile();
|
||||||
|
const port = config.PORT || 3456;
|
||||||
const env: Record<string, string> = {
|
const env: Record<string, string> = {
|
||||||
...process.env,
|
...process.env,
|
||||||
ANTHROPIC_AUTH_TOKEN: config?.APIKEY || "test",
|
ANTHROPIC_AUTH_TOKEN: config?.APIKEY || "test",
|
||||||
ANTHROPIC_API_KEY: '',
|
ANTHROPIC_API_KEY: '',
|
||||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${config.PORT || 3456}`,
|
ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}`,
|
||||||
|
NO_PROXY: `127.0.0.1`,
|
||||||
|
DISABLE_TELEMETRY: 'true',
|
||||||
|
DISABLE_COST_WARNINGS: 'true',
|
||||||
API_TIMEOUT_MS: String(config.API_TIMEOUT_MS ?? 600000), // Default to 10 minutes if not set
|
API_TIMEOUT_MS: String(config.API_TIMEOUT_MS ?? 600000), // Default to 10 minutes if not set
|
||||||
};
|
};
|
||||||
let settingsFlag: Record<string, any> | undefined;
|
let settingsFlag: Record<string, any> | undefined;
|
||||||
|
|||||||
@@ -85,19 +85,8 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
const groupedLogs = {};
|
const groupedLogs = {};
|
||||||
|
|
||||||
logs.forEach((log, index) => {
|
logs.forEach((log, index) => {
|
||||||
let reqId = log.reqId;
|
log = JSON.parse(log);
|
||||||
|
let reqId = log.reqId || 'no-req-id';
|
||||||
// 如果没有reqId,尝试从message字段中的JSON解析
|
|
||||||
if (!reqId && log.message && log.message.startsWith('{')) {
|
|
||||||
try {
|
|
||||||
const messageObj = JSON.parse(log.message);
|
|
||||||
reqId = messageObj.reqId;
|
|
||||||
} catch (e) {
|
|
||||||
// 解析失败,忽略
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reqId = reqId || 'no-req-id';
|
|
||||||
|
|
||||||
if (!groupedLogs[reqId]) {
|
if (!groupedLogs[reqId]) {
|
||||||
groupedLogs[reqId] = [];
|
groupedLogs[reqId] = [];
|
||||||
@@ -107,7 +96,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
|
|
||||||
// 按时间戳排序每个组的日志
|
// 按时间戳排序每个组的日志
|
||||||
Object.keys(groupedLogs).forEach(reqId => {
|
Object.keys(groupedLogs).forEach(reqId => {
|
||||||
groupedLogs[reqId].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
groupedLogs[reqId].sort((a, b) => a.time - b.time);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 提取model信息
|
// 提取model信息
|
||||||
@@ -116,11 +105,8 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
for (const log of logGroup) {
|
for (const log of logGroup) {
|
||||||
try {
|
try {
|
||||||
// 尝试从message字段解析JSON
|
// 尝试从message字段解析JSON
|
||||||
if (log.message && log.message.startsWith('{')) {
|
if (log.type === 'request body' && log.data && log.data.model) {
|
||||||
const messageObj = JSON.parse(log.message);
|
return log.data.model;
|
||||||
if (messageObj.body && messageObj.body.model) {
|
|
||||||
return messageObj.body.model;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 解析失败,继续尝试下一条日志
|
// 解析失败,继续尝试下一条日志
|
||||||
@@ -136,8 +122,8 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
requests: Object.keys(groupedLogs).map(reqId => ({
|
requests: Object.keys(groupedLogs).map(reqId => ({
|
||||||
reqId,
|
reqId,
|
||||||
logCount: groupedLogs[reqId].length,
|
logCount: groupedLogs[reqId].length,
|
||||||
firstLog: groupedLogs[reqId][0]?.timestamp,
|
firstLog: groupedLogs[reqId][0]?.time,
|
||||||
lastLog: groupedLogs[reqId][groupedLogs[reqId].length - 1]?.timestamp,
|
lastLog: groupedLogs[reqId][groupedLogs[reqId].length - 1]?.time,
|
||||||
model: extractModelInfo(reqId)
|
model: extractModelInfo(reqId)
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
@@ -179,7 +165,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
// 监听Worker消息
|
// 监听Worker消息
|
||||||
workerRef.current.onmessage = (event) => {
|
workerRef.current.onmessage = (event) => {
|
||||||
const { type, data, error } = event.data;
|
const { type, data, error } = event.data;
|
||||||
|
|
||||||
if (type === 'groupLogsResult') {
|
if (type === 'groupLogsResult') {
|
||||||
setGroupedLogs(data);
|
setGroupedLogs(data);
|
||||||
} else if (type === 'error') {
|
} else if (type === 'error') {
|
||||||
@@ -260,7 +246,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const response = await api.getLogFiles();
|
const response = await api.getLogFiles();
|
||||||
|
|
||||||
if (response && Array.isArray(response)) {
|
if (response && Array.isArray(response)) {
|
||||||
setLogFiles(response);
|
setLogFiles(response);
|
||||||
setSelectedFile(null);
|
setSelectedFile(null);
|
||||||
@@ -283,32 +269,32 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
|
|
||||||
const loadLogs = async () => {
|
const loadLogs = async () => {
|
||||||
if (!selectedFile) return;
|
if (!selectedFile) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setGroupedLogs(null);
|
setGroupedLogs(null);
|
||||||
setSelectedReqId(null);
|
setSelectedReqId(null);
|
||||||
|
|
||||||
// 始终加载原始日志数据
|
// 始终加载原始日志数据
|
||||||
const response = await api.getLogs(selectedFile.path);
|
const response = await api.getLogs(selectedFile.path);
|
||||||
|
|
||||||
if (response && Array.isArray(response)) {
|
if (response && Array.isArray(response)) {
|
||||||
// 现在接口返回的是原始日志字符串数组,直接存储
|
// 现在接口返回的是原始日志字符串数组,直接存储
|
||||||
setLogs(response);
|
setLogs(response);
|
||||||
|
|
||||||
// 如果启用了分组,使用Web Worker进行聚合(需要转换为LogEntry格式供Worker使用)
|
// 如果启用了分组,使用Web Worker进行聚合(需要转换为LogEntry格式供Worker使用)
|
||||||
if (groupByReqId && workerRef.current) {
|
if (groupByReqId && workerRef.current) {
|
||||||
const workerLogs: LogEntry[] = response.map((logLine, index) => ({
|
// const workerLogs: LogEntry[] = response.map((logLine, index) => ({
|
||||||
timestamp: new Date().toISOString(),
|
// timestamp: new Date().toISOString(),
|
||||||
level: 'info',
|
// level: 'info',
|
||||||
message: logLine,
|
// message: logLine,
|
||||||
source: undefined,
|
// source: undefined,
|
||||||
reqId: undefined
|
// reqId: undefined
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
workerRef.current.postMessage({
|
workerRef.current.postMessage({
|
||||||
type: 'groupLogsByReqId',
|
type: 'groupLogsByReqId',
|
||||||
data: { logs: workerLogs }
|
data: { logs: response }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setGroupedLogs(null);
|
setGroupedLogs(null);
|
||||||
@@ -332,7 +318,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
|
|
||||||
const clearLogs = async () => {
|
const clearLogs = async () => {
|
||||||
if (!selectedFile) return;
|
if (!selectedFile) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.clearLogs(selectedFile.path);
|
await api.clearLogs(selectedFile.path);
|
||||||
setLogs([]);
|
setLogs([]);
|
||||||
@@ -352,11 +338,11 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
setAutoRefresh(false); // Reset auto refresh when changing files
|
setAutoRefresh(false); // Reset auto refresh when changing files
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const toggleGroupByReqId = () => {
|
const toggleGroupByReqId = () => {
|
||||||
const newValue = !groupByReqId;
|
const newValue = !groupByReqId;
|
||||||
setGroupByReqId(newValue);
|
setGroupByReqId(newValue);
|
||||||
|
|
||||||
if (newValue && selectedFile && logs.length > 0) {
|
if (newValue && selectedFile && logs.length > 0) {
|
||||||
// 启用聚合时,如果已有日志,则使用Worker进行聚合
|
// 启用聚合时,如果已有日志,则使用Worker进行聚合
|
||||||
if (workerRef.current) {
|
if (workerRef.current) {
|
||||||
@@ -376,7 +362,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
setSelectedReqId(reqId);
|
setSelectedReqId(reqId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const getDisplayLogs = () => {
|
const getDisplayLogs = () => {
|
||||||
if (groupByReqId && groupedLogs) {
|
if (groupByReqId && groupedLogs) {
|
||||||
if (selectedReqId && groupedLogs.groups[selectedReqId]) {
|
if (selectedReqId && groupedLogs.groups[selectedReqId]) {
|
||||||
@@ -403,7 +389,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
|
|
||||||
const downloadLogs = () => {
|
const downloadLogs = () => {
|
||||||
if (!selectedFile || logs.length === 0) return;
|
if (!selectedFile || logs.length === 0) return;
|
||||||
|
|
||||||
// 直接下载原始日志字符串,每行一个日志
|
// 直接下载原始日志字符串,每行一个日志
|
||||||
const logText = logs.join('\n');
|
const logText = logs.join('\n');
|
||||||
|
|
||||||
@@ -416,7 +402,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
if (showToast) {
|
if (showToast) {
|
||||||
showToast(t('log_viewer.logs_downloaded'), 'success');
|
showToast(t('log_viewer.logs_downloaded'), 'success');
|
||||||
}
|
}
|
||||||
@@ -512,10 +498,11 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
// 如果在分组模式且选中了具体请求,显示该请求的日志
|
// 如果在分组模式且选中了具体请求,显示该请求的日志
|
||||||
if (groupByReqId && groupedLogs && selectedReqId && groupedLogs.groups[selectedReqId]) {
|
if (groupByReqId && groupedLogs && selectedReqId && groupedLogs.groups[selectedReqId]) {
|
||||||
const requestLogs = groupedLogs.groups[selectedReqId];
|
const requestLogs = groupedLogs.groups[selectedReqId];
|
||||||
|
console.log(requestLogs)
|
||||||
// 提取原始JSON字符串并每行一个
|
// 提取原始JSON字符串并每行一个
|
||||||
return requestLogs.map(log => log.message).join('\n');
|
return requestLogs.map(log => JSON.stringify(log)).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他情况,直接显示原始日志字符串数组,每行一个
|
// 其他情况,直接显示原始日志字符串数组,每行一个
|
||||||
return logs.join('\n');
|
return logs.join('\n');
|
||||||
};
|
};
|
||||||
@@ -527,20 +514,20 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(isVisible || open) && (
|
{(isVisible || open) && (
|
||||||
<div
|
<div
|
||||||
className={`fixed inset-0 z-50 transition-all duration-300 ease-out ${
|
className={`fixed inset-0 z-50 transition-all duration-300 ease-out ${
|
||||||
isAnimating && open ? 'bg-black/50 opacity-100' : 'bg-black/0 opacity-0 pointer-events-none'
|
isAnimating && open ? 'bg-black/50 opacity-100' : 'bg-black/0 opacity-0 pointer-events-none'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onOpenChange(false)}
|
onClick={() => onOpenChange(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={`fixed bottom-0 left-0 right-0 z-50 flex flex-col bg-white shadow-2xl transition-all duration-300 ease-out transform ${
|
className={`fixed bottom-0 left-0 right-0 z-50 flex flex-col bg-white shadow-2xl transition-all duration-300 ease-out transform ${
|
||||||
isAnimating && open ? 'translate-y-0' : 'translate-y-full'
|
isAnimating && open ? 'translate-y-0' : 'translate-y-full'
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
maxHeight: '100vh'
|
maxHeight: '100vh'
|
||||||
}}
|
}}
|
||||||
@@ -557,7 +544,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
{t('log_viewer.back')}
|
{t('log_viewer.back')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 面包屑导航 */}
|
{/* 面包屑导航 */}
|
||||||
<nav className="flex items-center space-x-1 text-sm">
|
<nav className="flex items-center space-x-1 text-sm">
|
||||||
{getBreadcrumbs().map((breadcrumb, index) => (
|
{getBreadcrumbs().map((breadcrumb, index) => (
|
||||||
@@ -632,7 +619,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 bg-gray-50">
|
<div className="flex-1 min-h-0 bg-gray-50">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
@@ -646,7 +633,7 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
<div className="mb-4 flex-shrink-0">
|
<div className="mb-4 flex-shrink-0">
|
||||||
<h3 className="text-lg font-medium mb-2">{t('log_viewer.request_groups')}</h3>
|
<h3 className="text-lg font-medium mb-2">{t('log_viewer.request_groups')}</h3>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
{t('log_viewer.total_requests')}: {groupedLogs.summary.totalRequests} |
|
{t('log_viewer.total_requests')}: {groupedLogs.summary.totalRequests} |
|
||||||
{t('log_viewer.total_logs')}: {groupedLogs.summary.totalLogs}
|
{t('log_viewer.total_logs')}: {groupedLogs.summary.totalLogs}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -736,4 +723,4 @@ export function LogViewer({ open, onOpenChange, showToast }: LogViewerProps) {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user