fix logviewer

This commit is contained in:
musistudio
2025-09-09 21:06:19 +08:00
parent cec8421dd9
commit 1b3a8f8803
4 changed files with 58 additions and 67 deletions

View File

@@ -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"
}), }),
} }

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>
</> </>
); );
} }