Showing
1 changed file
with
378 additions
and
167 deletions
| @@ -1392,7 +1392,10 @@ | @@ -1392,7 +1392,10 @@ | ||
| 1392 | refreshForumLog(); | 1392 | refreshForumLog(); |
| 1393 | } | 1393 | } |
| 1394 | if (currentApp === 'report') { | 1394 | if (currentApp === 'report') { |
| 1395 | - refreshReportLog(); | 1395 | + // 使用新的日志管理器刷新 |
| 1396 | + if (reportLogManager && reportLogManager.isRunning) { | ||
| 1397 | + reportLogManager.refresh(); | ||
| 1398 | + } | ||
| 1396 | } | 1399 | } |
| 1397 | }, 100); | 1400 | }, 100); |
| 1398 | } else { | 1401 | } else { |
| @@ -3536,7 +3539,7 @@ | @@ -3536,7 +3539,7 @@ | ||
| 3536 | // 仅保留特殊页面的初始化逻辑 | 3539 | // 仅保留特殊页面的初始化逻辑 |
| 3537 | if (app === 'report') { | 3540 | if (app === 'report') { |
| 3538 | // 【修复】切换到Report Engine时启动日志刷新 | 3541 | // 【修复】切换到Report Engine时启动日志刷新 |
| 3539 | - startReportLogRefresh(); | 3542 | + reportLogManager.start(); |
| 3540 | 3543 | ||
| 3541 | // 只在报告界面未初始化时才重新加载 | 3544 | // 只在报告界面未初始化时才重新加载 |
| 3542 | const reportContent = document.getElementById('reportContent'); | 3545 | const reportContent = document.getElementById('reportContent'); |
| @@ -3549,7 +3552,7 @@ | @@ -3549,7 +3552,7 @@ | ||
| 3549 | }, 500); | 3552 | }, 500); |
| 3550 | } else { | 3553 | } else { |
| 3551 | // 【修复】切换离开Report Engine时停止日志刷新,节省资源 | 3554 | // 【修复】切换离开Report Engine时停止日志刷新,节省资源 |
| 3552 | - stopReportLogRefresh(); | 3555 | + reportLogManager.stop(); |
| 3553 | } | 3556 | } |
| 3554 | } | 3557 | } |
| 3555 | 3558 | ||
| @@ -3822,9 +3825,12 @@ | @@ -3822,9 +3825,12 @@ | ||
| 3822 | refreshForumLog(); | 3825 | refreshForumLog(); |
| 3823 | return; | 3826 | return; |
| 3824 | } | 3827 | } |
| 3825 | - | 3828 | + |
| 3826 | if (currentApp === 'report') { | 3829 | if (currentApp === 'report') { |
| 3827 | - refreshReportLog(); | 3830 | + // 使用新的日志管理器刷新 |
| 3831 | + if (reportLogManager && reportLogManager.isRunning) { | ||
| 3832 | + reportLogManager.refresh(); | ||
| 3833 | + } | ||
| 3828 | return; | 3834 | return; |
| 3829 | } | 3835 | } |
| 3830 | 3836 | ||
| @@ -4246,6 +4252,325 @@ | @@ -4246,6 +4252,325 @@ | ||
| 4246 | let reportLockCheckInterval = null; | 4252 | let reportLockCheckInterval = null; |
| 4247 | let lastCompletedReportTask = null; | 4253 | let lastCompletedReportTask = null; |
| 4248 | 4254 | ||
| 4255 | + // ====== Report Engine 日志管理器 ====== | ||
| 4256 | + class ReportLogManager { | ||
| 4257 | + constructor() { | ||
| 4258 | + this.intervalId = null; | ||
| 4259 | + this.lineCount = 0; | ||
| 4260 | + this.isRunning = false; | ||
| 4261 | + this.refreshInterval = 250; // 250ms轮询一次,更加实时 | ||
| 4262 | + this.lastError = null; | ||
| 4263 | + this.retryCount = 0; | ||
| 4264 | + this.maxRetries = 3; | ||
| 4265 | + } | ||
| 4266 | + | ||
| 4267 | + // 启动日志轮询 | ||
| 4268 | + start() { | ||
| 4269 | + if (this.isRunning) { | ||
| 4270 | + console.log('[ReportLogManager] 已在运行,跳过重复启动'); | ||
| 4271 | + return; | ||
| 4272 | + } | ||
| 4273 | + | ||
| 4274 | + console.log('[ReportLogManager] ===== 启动日志轮询系统 ====='); | ||
| 4275 | + this.isRunning = true; | ||
| 4276 | + this.retryCount = 0; | ||
| 4277 | + | ||
| 4278 | + // 立即执行一次 | ||
| 4279 | + console.log('[ReportLogManager] 执行初始刷新...'); | ||
| 4280 | + this.refresh(); | ||
| 4281 | + | ||
| 4282 | + // 启动定时轮询 | ||
| 4283 | + this.intervalId = setInterval(() => { | ||
| 4284 | + if (currentApp === 'report' && this.isRunning) { | ||
| 4285 | + console.log('[ReportLogManager] 定时器触发,执行刷新...'); | ||
| 4286 | + this.refresh(); | ||
| 4287 | + } | ||
| 4288 | + }, this.refreshInterval); | ||
| 4289 | + | ||
| 4290 | + console.log(`[ReportLogManager] 轮询已启动,频率: ${this.refreshInterval}ms, intervalId: ${this.intervalId}`); | ||
| 4291 | + } | ||
| 4292 | + | ||
| 4293 | + // 停止日志轮询 | ||
| 4294 | + stop() { | ||
| 4295 | + if (!this.isRunning) { | ||
| 4296 | + console.log('[ReportLogManager] 未在运行,无需停止'); | ||
| 4297 | + return; | ||
| 4298 | + } | ||
| 4299 | + | ||
| 4300 | + console.log('[ReportLogManager] ===== 停止日志轮询系统 ====='); | ||
| 4301 | + this.isRunning = false; | ||
| 4302 | + | ||
| 4303 | + if (this.intervalId) { | ||
| 4304 | + clearInterval(this.intervalId); | ||
| 4305 | + this.intervalId = null; | ||
| 4306 | + } | ||
| 4307 | + | ||
| 4308 | + console.log('[ReportLogManager] 轮询已停止'); | ||
| 4309 | + } | ||
| 4310 | + | ||
| 4311 | + // 重置计数器(任务开始时调用) | ||
| 4312 | + reset() { | ||
| 4313 | + console.log(`[ReportLogManager] 重置计数器,原值: ${this.lineCount}`); | ||
| 4314 | + this.lineCount = 0; | ||
| 4315 | + this.lastError = null; | ||
| 4316 | + this.retryCount = 0; | ||
| 4317 | + } | ||
| 4318 | + | ||
| 4319 | + // 刷新日志 | ||
| 4320 | + refresh() { | ||
| 4321 | + if (!this.isRunning) { | ||
| 4322 | + console.log('[ReportLogManager.refresh] 管理器未运行,跳过刷新'); | ||
| 4323 | + return; | ||
| 4324 | + } | ||
| 4325 | + | ||
| 4326 | + console.log('[ReportLogManager.refresh] 开始刷新日志...'); | ||
| 4327 | + | ||
| 4328 | + // 【修复】使用传统的Promise方式,避免async/await兼容性问题 | ||
| 4329 | + const controller = new AbortController(); | ||
| 4330 | + const timeoutId = setTimeout(() => controller.abort(), 3000); | ||
| 4331 | + | ||
| 4332 | + fetch('/api/report/log', { | ||
| 4333 | + method: 'GET', | ||
| 4334 | + headers: { 'Cache-Control': 'no-cache' }, | ||
| 4335 | + signal: controller.signal | ||
| 4336 | + }) | ||
| 4337 | + .then(response => { | ||
| 4338 | + clearTimeout(timeoutId); | ||
| 4339 | + console.log(`[ReportLogManager.refresh] 收到响应,状态: ${response.status}`); | ||
| 4340 | + | ||
| 4341 | + if (!response.ok) { | ||
| 4342 | + throw new Error(`HTTP ${response.status}`); | ||
| 4343 | + } | ||
| 4344 | + | ||
| 4345 | + return response.json(); | ||
| 4346 | + }) | ||
| 4347 | + .then(data => { | ||
| 4348 | + console.log('[ReportLogManager.refresh] 解析JSON成功'); | ||
| 4349 | + | ||
| 4350 | + if (!data.success) { | ||
| 4351 | + throw new Error(data.error || '未知错误'); | ||
| 4352 | + } | ||
| 4353 | + | ||
| 4354 | + // 成功后重置重试计数 | ||
| 4355 | + this.retryCount = 0; | ||
| 4356 | + | ||
| 4357 | + // 【调试】输出获取到的日志行数 | ||
| 4358 | + if (data.log_lines) { | ||
| 4359 | + console.log(`[ReportLogManager.refresh] API返回 ${data.log_lines.length} 行日志`); | ||
| 4360 | + } else { | ||
| 4361 | + console.log('[ReportLogManager.refresh] API返回的log_lines为空'); | ||
| 4362 | + } | ||
| 4363 | + | ||
| 4364 | + // 处理日志数据 | ||
| 4365 | + this.processLogs(data.log_lines || []); | ||
| 4366 | + }) | ||
| 4367 | + .catch(error => { | ||
| 4368 | + clearTimeout(timeoutId); | ||
| 4369 | + console.error('[ReportLogManager.refresh] 错误:', error); | ||
| 4370 | + this.handleError(error); | ||
| 4371 | + }); | ||
| 4372 | + } | ||
| 4373 | + | ||
| 4374 | + // 处理日志数据 | ||
| 4375 | + processLogs(logLines) { | ||
| 4376 | + const totalLines = logLines.length; | ||
| 4377 | + | ||
| 4378 | + console.log(`[ReportLogManager.processLogs] 总行数: ${totalLines}, 当前计数: ${this.lineCount}`); | ||
| 4379 | + | ||
| 4380 | + // 如果有新日志 | ||
| 4381 | + if (totalLines > this.lineCount) { | ||
| 4382 | + const newLines = logLines.slice(this.lineCount); | ||
| 4383 | + console.log(`[ReportLogManager] 发现 ${newLines.length} 条新日志 (${this.lineCount} -> ${totalLines})`); | ||
| 4384 | + | ||
| 4385 | + // 输出前3行新日志用于调试 | ||
| 4386 | + if (newLines.length > 0) { | ||
| 4387 | + console.log('[ReportLogManager] 新日志样本:'); | ||
| 4388 | + newLines.slice(0, 3).forEach((line, idx) => { | ||
| 4389 | + console.log(` [${idx}] ${line.substring(0, 100)}...`); | ||
| 4390 | + }); | ||
| 4391 | + } | ||
| 4392 | + | ||
| 4393 | + // 逐行处理并显示 | ||
| 4394 | + newLines.forEach((line, index) => { | ||
| 4395 | + console.log(`[ReportLogManager.processLogs] 处理第 ${index + 1}/${newLines.length} 行`); | ||
| 4396 | + this.displayLogLine(line); | ||
| 4397 | + }); | ||
| 4398 | + | ||
| 4399 | + // 更新计数器 | ||
| 4400 | + this.lineCount = totalLines; | ||
| 4401 | + console.log(`[ReportLogManager] 计数器更新为: ${this.lineCount}`); | ||
| 4402 | + } else { | ||
| 4403 | + console.log(`[ReportLogManager] 没有新日志 (总数: ${totalLines}, 已读: ${this.lineCount})`); | ||
| 4404 | + } | ||
| 4405 | + } | ||
| 4406 | + | ||
| 4407 | + // 显示单行日志(带格式化) | ||
| 4408 | + displayLogLine(line) { | ||
| 4409 | + // 解析loguru格式的日志 | ||
| 4410 | + const logPattern = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s*\|\s*(INFO|DEBUG|WARNING|ERROR|CRITICAL)\s*\|\s*(.+?)\s*-\s*(.*)$/; | ||
| 4411 | + const match = line.match(logPattern); | ||
| 4412 | + | ||
| 4413 | + if (match) { | ||
| 4414 | + const [, timestamp, level, location, message] = match; | ||
| 4415 | + | ||
| 4416 | + // 【调试】输出匹配到的日志级别 | ||
| 4417 | + if (level === 'WARNING' || level === 'ERROR' || level === 'DEBUG') { | ||
| 4418 | + console.log(`[ReportLogManager] 检测到 ${level} 日志: ${message.substring(0, 50)}...`); | ||
| 4419 | + } | ||
| 4420 | + | ||
| 4421 | + // 格式化输出 - 简化时间戳,只显示时间部分 | ||
| 4422 | + const timeOnly = timestamp.split(' ')[1]; | ||
| 4423 | + const formattedLine = `[${timeOnly}] [${level}] ${message}`; | ||
| 4424 | + | ||
| 4425 | + // 添加到控制台(带样式提示) | ||
| 4426 | + if (level === 'ERROR' || level === 'CRITICAL') { | ||
| 4427 | + appendConsoleTextLine('report', formattedLine, 'error'); | ||
| 4428 | + } else if (level === 'WARNING') { | ||
| 4429 | + appendConsoleTextLine('report', formattedLine, 'warning'); | ||
| 4430 | + } else if (level === 'DEBUG') { | ||
| 4431 | + appendConsoleTextLine('report', formattedLine, 'debug'); | ||
| 4432 | + } else { | ||
| 4433 | + appendConsoleTextLine('report', formattedLine); | ||
| 4434 | + } | ||
| 4435 | + | ||
| 4436 | + // 同时在浏览器控制台输出(便于调试)- 降低输出频率 | ||
| 4437 | + if (level === 'ERROR' || level === 'CRITICAL') { | ||
| 4438 | + console.error(`[ReportLog] ${formattedLine}`); | ||
| 4439 | + } else if (level === 'WARNING') { | ||
| 4440 | + console.warn(`[ReportLog] ${formattedLine}`); | ||
| 4441 | + } else if (level === 'DEBUG') { | ||
| 4442 | + console.debug(`[ReportLog] ${formattedLine}`); | ||
| 4443 | + } | ||
| 4444 | + } else { | ||
| 4445 | + // 非标准格式的日志,直接显示 | ||
| 4446 | + // 【调试】输出未匹配的行,帮助调试正则 | ||
| 4447 | + if (line.includes('WARNING') || line.includes('ERROR') || line.includes('DEBUG')) { | ||
| 4448 | + console.log(`[ReportLogManager] 未匹配的日志行: "${line}"`); | ||
| 4449 | + } | ||
| 4450 | + appendConsoleTextLine('report', line); | ||
| 4451 | + } | ||
| 4452 | + } | ||
| 4453 | + | ||
| 4454 | + // 处理错误 | ||
| 4455 | + handleError(error) { | ||
| 4456 | + // 避免重复错误日志 | ||
| 4457 | + const errorMsg = error.message || error.toString(); | ||
| 4458 | + if (errorMsg === this.lastError) { | ||
| 4459 | + return; // 相同错误不重复输出 | ||
| 4460 | + } | ||
| 4461 | + | ||
| 4462 | + this.lastError = errorMsg; | ||
| 4463 | + this.retryCount++; | ||
| 4464 | + | ||
| 4465 | + // 只在前几次重试时输出错误 | ||
| 4466 | + if (this.retryCount <= this.maxRetries) { | ||
| 4467 | + console.warn(`[ReportLogManager] 获取日志失败 (${this.retryCount}/${this.maxRetries}): ${errorMsg}`); | ||
| 4468 | + } | ||
| 4469 | + | ||
| 4470 | + // 超过最大重试次数时暂停一段时间 | ||
| 4471 | + if (this.retryCount > this.maxRetries) { | ||
| 4472 | + this.stop(); | ||
| 4473 | + console.error('[ReportLogManager] 多次失败,暂停轮询'); | ||
| 4474 | + | ||
| 4475 | + // 5秒后自动重试 | ||
| 4476 | + setTimeout(() => { | ||
| 4477 | + if (currentApp === 'report') { | ||
| 4478 | + console.log('[ReportLogManager] 尝试恢复轮询...'); | ||
| 4479 | + this.start(); | ||
| 4480 | + } | ||
| 4481 | + }, 5000); | ||
| 4482 | + } | ||
| 4483 | + } | ||
| 4484 | + | ||
| 4485 | + // 获取状态信息 | ||
| 4486 | + getStatus() { | ||
| 4487 | + return { | ||
| 4488 | + isRunning: this.isRunning, | ||
| 4489 | + lineCount: this.lineCount, | ||
| 4490 | + intervalId: this.intervalId, | ||
| 4491 | + lastError: this.lastError, | ||
| 4492 | + retryCount: this.retryCount | ||
| 4493 | + }; | ||
| 4494 | + } | ||
| 4495 | + } | ||
| 4496 | + | ||
| 4497 | + // 创建全局日志管理器实例 | ||
| 4498 | + const reportLogManager = new ReportLogManager(); | ||
| 4499 | + | ||
| 4500 | + // 【调试】测试日志管理器 | ||
| 4501 | + window.testReportLogManager = function() { | ||
| 4502 | + console.log('[测试] ===== 开始测试Report日志管理器 ====='); | ||
| 4503 | + | ||
| 4504 | + // 检查当前状态 | ||
| 4505 | + const status = reportLogManager.getStatus(); | ||
| 4506 | + console.log('[测试] 当前状态:', status); | ||
| 4507 | + | ||
| 4508 | + // 如果未运行,启动它 | ||
| 4509 | + if (!status.isRunning) { | ||
| 4510 | + console.log('[测试] 启动日志管理器...'); | ||
| 4511 | + reportLogManager.start(); | ||
| 4512 | + } | ||
| 4513 | + | ||
| 4514 | + // 手动刷新一次 | ||
| 4515 | + console.log('[测试] 手动触发刷新...'); | ||
| 4516 | + reportLogManager.refresh(); | ||
| 4517 | + | ||
| 4518 | + // 模拟添加日志 | ||
| 4519 | + console.log('[测试] 模拟添加WARNING日志...'); | ||
| 4520 | + appendConsoleTextLine('report', '[21:02:43.014] [WARNING] 测试警告消息', 'warning'); | ||
| 4521 | + | ||
| 4522 | + console.log('[测试] 模拟添加ERROR日志...'); | ||
| 4523 | + appendConsoleTextLine('report', '[21:02:43.018] [ERROR] 测试错误消息', 'error'); | ||
| 4524 | + | ||
| 4525 | + console.log('[测试] ===== 测试完成 ====='); | ||
| 4526 | + }; | ||
| 4527 | + | ||
| 4528 | + // 【调试】直接测试API | ||
| 4529 | + window.testReportAPI = function() { | ||
| 4530 | + console.log('[测试API] ===== 开始测试Report API ====='); | ||
| 4531 | + | ||
| 4532 | + fetch('/api/report/log', { | ||
| 4533 | + method: 'GET', | ||
| 4534 | + headers: { 'Cache-Control': 'no-cache' } | ||
| 4535 | + }) | ||
| 4536 | + .then(response => { | ||
| 4537 | + console.log('[测试API] 响应状态:', response.status); | ||
| 4538 | + return response.json(); | ||
| 4539 | + }) | ||
| 4540 | + .then(data => { | ||
| 4541 | + console.log('[测试API] 返回数据:', data); | ||
| 4542 | + if (data.success && data.log_lines) { | ||
| 4543 | + console.log('[测试API] 日志行数:', data.log_lines.length); | ||
| 4544 | + console.log('[测试API] 前5行日志:'); | ||
| 4545 | + data.log_lines.slice(0, 5).forEach((line, idx) => { | ||
| 4546 | + console.log(` ${idx}: ${line}`); | ||
| 4547 | + }); | ||
| 4548 | + | ||
| 4549 | + // 查找WARNING和ERROR日志 | ||
| 4550 | + const warnings = data.log_lines.filter(line => line.includes('WARNING')); | ||
| 4551 | + const errors = data.log_lines.filter(line => line.includes('ERROR')); | ||
| 4552 | + | ||
| 4553 | + console.log(`[测试API] 找到 ${warnings.length} 条WARNING日志`); | ||
| 4554 | + console.log(`[测试API] 找到 ${errors.length} 条ERROR日志`); | ||
| 4555 | + | ||
| 4556 | + if (warnings.length > 0) { | ||
| 4557 | + console.log('[测试API] WARNING日志示例:'); | ||
| 4558 | + warnings.slice(0, 3).forEach(line => console.log(' ', line)); | ||
| 4559 | + } | ||
| 4560 | + | ||
| 4561 | + if (errors.length > 0) { | ||
| 4562 | + console.log('[测试API] ERROR日志示例:'); | ||
| 4563 | + errors.slice(0, 3).forEach(line => console.log(' ', line)); | ||
| 4564 | + } | ||
| 4565 | + } | ||
| 4566 | + }) | ||
| 4567 | + .catch(error => { | ||
| 4568 | + console.error('[测试API] 错误:', error); | ||
| 4569 | + }); | ||
| 4570 | + | ||
| 4571 | + console.log('[测试API] ===== 测试完成 ====='); | ||
| 4572 | + }; | ||
| 4573 | + | ||
| 4249 | // 实时刷新论坛消息(适用于所有页面) | 4574 | // 实时刷新论坛消息(适用于所有页面) |
| 4250 | function refreshForumMessages() { | 4575 | function refreshForumMessages() { |
| 4251 | fetch('/api/forum/log') | 4576 | fetch('/api/forum/log') |
| @@ -4466,119 +4791,30 @@ | @@ -4466,119 +4791,30 @@ | ||
| 4466 | }); | 4791 | }); |
| 4467 | } | 4792 | } |
| 4468 | 4793 | ||
| 4794 | + // 【重构】刷新Report日志(使用新的日志管理器) | ||
| 4469 | function refreshReportLog() { | 4795 | function refreshReportLog() { |
| 4470 | - // 【修复】添加超时控制和完整错误处理 | ||
| 4471 | - const controller = new AbortController(); | ||
| 4472 | - const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超时 | ||
| 4473 | - | ||
| 4474 | - fetch('/api/report/log', { signal: controller.signal }) | ||
| 4475 | - .then(response => { | ||
| 4476 | - clearTimeout(timeoutId); | ||
| 4477 | - // 【修复】检查HTTP状态 | ||
| 4478 | - if (!response.ok) { | ||
| 4479 | - throw new Error(`HTTP ${response.status}: ${response.statusText}`); | ||
| 4480 | - } | ||
| 4481 | - return response.json(); | ||
| 4482 | - }) | ||
| 4483 | - .then(data => { | ||
| 4484 | - // 【修复】检查返回的成功状态 | ||
| 4485 | - if (!data.success) { | ||
| 4486 | - console.error('[Report日志] 刷新失败:', data.error || '未知错误'); | ||
| 4487 | - return; | ||
| 4488 | - } | ||
| 4489 | - | ||
| 4490 | - if (data.log_lines && data.log_lines.length > reportLogLineCount) { | ||
| 4491 | - // 只添加新的行 | ||
| 4492 | - const newLines = data.log_lines.slice(reportLogLineCount); | ||
| 4493 | - | ||
| 4494 | - // 【调试日志】记录实时读取的日志数量 | ||
| 4495 | - if (newLines.length > 0) { | ||
| 4496 | - console.log(`[Report日志] 读取 ${newLines.length} 条新日志(总计 ${data.log_lines.length})`); | ||
| 4497 | - } | ||
| 4498 | - | ||
| 4499 | - newLines.forEach(line => { | ||
| 4500 | - // 直接添加,使用LogVirtualList的默认批处理机制 | ||
| 4501 | - appendConsoleTextLine('report', line); | ||
| 4502 | - }); | ||
| 4503 | - | ||
| 4504 | - reportLogLineCount = data.log_lines.length; | ||
| 4505 | - } | ||
| 4506 | - }) | ||
| 4507 | - .catch(error => { | ||
| 4508 | - clearTimeout(timeoutId); | ||
| 4509 | - // 【修复】区分错误类型 | ||
| 4510 | - if (error.name === 'AbortError') { | ||
| 4511 | - console.warn('[Report日志] 刷新超时(5秒)'); | ||
| 4512 | - } else if (error.message.includes('Failed to fetch')) { | ||
| 4513 | - console.error('[Report日志] 刷新失败: 网络连接错误'); | ||
| 4514 | - } else { | ||
| 4515 | - console.error('[Report日志] 刷新失败:', error.message || error); | ||
| 4516 | - } | ||
| 4517 | - }); | 4796 | + // 兼容旧代码:直接调用日志管理器的刷新 |
| 4797 | + if (reportLogManager && reportLogManager.isRunning) { | ||
| 4798 | + reportLogManager.refresh(); | ||
| 4799 | + } else { | ||
| 4800 | + console.log('[RefreshReportLog] 日志管理器未运行,跳过刷新'); | ||
| 4801 | + } | ||
| 4518 | } | 4802 | } |
| 4519 | 4803 | ||
| 4520 | - // 加载Report Engine日志 | 4804 | + // 加载Report Engine日志(初始化时使用) |
| 4521 | function loadReportLog() { | 4805 | function loadReportLog() { |
| 4522 | - fetch('/api/report/log') | ||
| 4523 | - .then(response => response.json()) | ||
| 4524 | - .then(data => { | ||
| 4525 | - // 【FIX Bug #5】检查是否仍然在report页面 | ||
| 4526 | - if (currentApp !== 'report') { | ||
| 4527 | - console.log('忽略report日志响应(已切换到其他app)'); | ||
| 4528 | - return; | ||
| 4529 | - } | ||
| 4530 | - | ||
| 4531 | - if (data.success) { | ||
| 4532 | - if (reportLogLineCount === 0) { | ||
| 4533 | - clearConsoleLayer('report', '[系统] Report Engine 日志监控已启动'); | ||
| 4534 | - logRenderers['report'].render(); // 立即渲染 | ||
| 4535 | - } | ||
| 4536 | - | ||
| 4537 | - if (data.log_lines && data.log_lines.length > 0) { | ||
| 4538 | - const newLines = data.log_lines.slice(reportLogLineCount); | ||
| 4539 | - const linesToProcess = reportLogLineCount === 0 ? data.log_lines : newLines; | ||
| 4540 | - | ||
| 4541 | - linesToProcess.forEach(line => { | ||
| 4542 | - appendConsoleTextLine('report', line); | ||
| 4543 | - }); | ||
| 4544 | - | ||
| 4545 | - // 重置计数器以确保后续消息能正确显示 | ||
| 4546 | - reportLogLineCount = data.log_lines.length; | ||
| 4547 | - | ||
| 4548 | - // 移除"正在加载"提示(如果存在) | ||
| 4549 | - const renderer = logRenderers['report']; | ||
| 4550 | - if (renderer && renderer.lines.length > 0) { | ||
| 4551 | - const firstLine = renderer.lines[0]; | ||
| 4552 | - if (firstLine && firstLine.text.includes('正在加载')) { | ||
| 4553 | - renderer.lines.shift(); | ||
| 4554 | - renderer.lastRenderHash = null; | ||
| 4555 | - renderer.scheduleRender(true); | ||
| 4556 | - } | ||
| 4557 | - } | ||
| 4558 | - } else { | ||
| 4559 | - // 如果没有日志,重置计数器 | ||
| 4560 | - reportLogLineCount = 0; | ||
| 4561 | - } | ||
| 4562 | - } else { | ||
| 4563 | - // 【优化】加载失败显示错误 | ||
| 4564 | - const renderer = logRenderers['report']; | ||
| 4565 | - if (renderer && currentApp === 'report') { | ||
| 4566 | - renderer.clear('[错误] 加载Report日志失败'); | ||
| 4567 | - renderer.render(); | ||
| 4568 | - } | ||
| 4569 | - } | ||
| 4570 | - }) | ||
| 4571 | - .catch(error => { | ||
| 4572 | - console.error('加载Report日志失败:', error); | ||
| 4573 | - // 【优化】显示错误提示 | ||
| 4574 | - if (currentApp === 'report') { | ||
| 4575 | - const renderer = logRenderers['report']; | ||
| 4576 | - if (renderer) { | ||
| 4577 | - renderer.clear('[错误] 加载Report日志失败: ' + error.message); | ||
| 4578 | - renderer.render(); | ||
| 4579 | - } | ||
| 4580 | - } | ||
| 4581 | - }); | 4806 | + // 使用新的日志管理器 |
| 4807 | + if (!reportLogManager.isRunning) { | ||
| 4808 | + // 清空控制台 | ||
| 4809 | + clearConsoleLayer('report', '[系统] Report Engine 日志监控已启动'); | ||
| 4810 | + | ||
| 4811 | + // 重置计数器并启动 | ||
| 4812 | + reportLogManager.reset(); | ||
| 4813 | + reportLogManager.start(); | ||
| 4814 | + } else { | ||
| 4815 | + // 如果已经在运行,只是刷新一次 | ||
| 4816 | + reportLogManager.refresh(); | ||
| 4817 | + } | ||
| 4582 | } | 4818 | } |
| 4583 | 4819 | ||
| 4584 | // 解析论坛消息并添加到对话区 | 4820 | // 解析论坛消息并添加到对话区 |
| @@ -4739,7 +4975,7 @@ | @@ -4739,7 +4975,7 @@ | ||
| 4739 | 4975 | ||
| 4740 | // 【修复】加载Report界面时启动日志刷新 | 4976 | // 【修复】加载Report界面时启动日志刷新 |
| 4741 | if (currentApp === 'report') { | 4977 | if (currentApp === 'report') { |
| 4742 | - startReportLogRefresh(); | 4978 | + reportLogManager.start(); |
| 4743 | } | 4979 | } |
| 4744 | } else { | 4980 | } else { |
| 4745 | reportContent.innerHTML = ` | 4981 | reportContent.innerHTML = ` |
| @@ -5110,9 +5346,10 @@ | @@ -5110,9 +5346,10 @@ | ||
| 5110 | } | 5346 | } |
| 5111 | 5347 | ||
| 5112 | const query = document.getElementById('searchInput').value.trim() || '智能舆情分析报告'; | 5348 | const query = document.getElementById('searchInput').value.trim() || '智能舆情分析报告'; |
| 5113 | - | ||
| 5114 | - // 重置日志计数器,因为后台会清空日志文件 | ||
| 5115 | - reportLogLineCount = 0; | 5349 | + |
| 5350 | + // 【修复】先停止现有的日志轮询,避免与后端清空日志的竞态条件 | ||
| 5351 | + reportLogManager.stop(); | ||
| 5352 | + | ||
| 5116 | reportAutoPreviewLoaded = false; | 5353 | reportAutoPreviewLoaded = false; |
| 5117 | safeCloseReportStream(true); | 5354 | safeCloseReportStream(true); |
| 5118 | 5355 | ||
| @@ -5157,10 +5394,14 @@ | @@ -5157,10 +5394,14 @@ | ||
| 5157 | 5394 | ||
| 5158 | appendReportStreamLine('任务创建成功,正在建立流式连接...', 'info', { force: true }); | 5395 | appendReportStreamLine('任务创建成功,正在建立流式连接...', 'info', { force: true }); |
| 5159 | 5396 | ||
| 5160 | - // 【优化】先启动日志轮询,再建立SSE连接 | 5397 | + // 【修复】在API成功后重置计数器,此时后端已清空日志文件 |
| 5398 | + // 避免在API调用期间旧interval读取旧日志导致的竞态条件 | ||
| 5399 | + reportLogManager.reset(); | ||
| 5400 | + | ||
| 5401 | + // 【优化】启动日志轮询 | ||
| 5161 | // 确保从任务开始就能读取日志 | 5402 | // 确保从任务开始就能读取日志 |
| 5162 | - startReportLogRefresh(); | ||
| 5163 | - console.log('[Report日志] 任务创建成功,启动日志轮询'); | 5403 | + reportLogManager.start(); |
| 5404 | + console.log('[ReportLogManager] 任务创建成功,计数器已重置,启动日志轮询'); | ||
| 5164 | 5405 | ||
| 5165 | if (window.EventSource) { | 5406 | if (window.EventSource) { |
| 5166 | openReportStream(reportTaskId); | 5407 | openReportStream(reportTaskId); |
| @@ -5192,38 +5433,8 @@ | @@ -5192,38 +5433,8 @@ | ||
| 5192 | } | 5433 | } |
| 5193 | 5434 | ||
| 5194 | // 【修复】启动Report Engine日志实时刷新 | 5435 | // 【修复】启动Report Engine日志实时刷新 |
| 5195 | - function startReportLogRefresh() { | ||
| 5196 | - // 防重复:如果已经在运行,直接返回 | ||
| 5197 | - if (reportLogRefreshInterval) { | ||
| 5198 | - console.log('[Report日志] 日志轮询已在运行,跳过重复启动'); | ||
| 5199 | - return; | ||
| 5200 | - } | ||
| 5201 | - | ||
| 5202 | - // 立即刷新一次,获取当前所有日志 | ||
| 5203 | - refreshReportLog(); | ||
| 5204 | - | ||
| 5205 | - // 启动定时器,每秒刷新一次 | ||
| 5206 | - reportLogRefreshInterval = setInterval(() => { | ||
| 5207 | - if (currentApp === 'report') { | ||
| 5208 | - refreshReportLog(); | ||
| 5209 | - } else { | ||
| 5210 | - // 如果切换到其他应用,自动停止轮询 | ||
| 5211 | - console.log('[Report日志] 检测到应用切换,停止日志轮询'); | ||
| 5212 | - stopReportLogRefresh(); | ||
| 5213 | - } | ||
| 5214 | - }, 1000); // 每秒刷新一次 | ||
| 5215 | - | ||
| 5216 | - console.log('[Report日志] 启动实时日志刷新,频率: 1秒'); | ||
| 5217 | - } | ||
| 5218 | - | ||
| 5219 | - // 【修复】停止Report Engine日志刷新 | ||
| 5220 | - function stopReportLogRefresh() { | ||
| 5221 | - if (reportLogRefreshInterval) { | ||
| 5222 | - clearInterval(reportLogRefreshInterval); | ||
| 5223 | - reportLogRefreshInterval = null; | ||
| 5224 | - console.log('[Report日志] 停止日志刷新'); | ||
| 5225 | - } | ||
| 5226 | - } | 5436 | + // 【新函数】使用新的日志管理器 |
| 5437 | + // 旧的startReportLogRefresh和stopReportLogRefresh已废弃,请使用reportLogManager | ||
| 5227 | 5438 | ||
| 5228 | // 开始进度轮询 | 5439 | // 开始进度轮询 |
| 5229 | function startProgressPolling(taskId) { | 5440 | function startProgressPolling(taskId) { |
| @@ -5243,10 +5454,10 @@ | @@ -5243,10 +5454,10 @@ | ||
| 5243 | .then(data => { | 5454 | .then(data => { |
| 5244 | if (data.success) { | 5455 | if (data.success) { |
| 5245 | updateProgressDisplay(data.task); | 5456 | updateProgressDisplay(data.task); |
| 5246 | - | ||
| 5247 | - // 在检查进度时也刷新日志 | ||
| 5248 | - refreshReportLog(); | ||
| 5249 | - | 5457 | + |
| 5458 | + // 在检查进度时也刷新日志(使用新的日志管理器) | ||
| 5459 | + // reportLogManager会自动处理轮询 | ||
| 5460 | + | ||
| 5250 | if (data.task.status === 'completed') { | 5461 | if (data.task.status === 'completed') { |
| 5251 | clearInterval(reportPollingInterval); | 5462 | clearInterval(reportPollingInterval); |
| 5252 | showMessage('报告生成完成!', 'success'); | 5463 | showMessage('报告生成完成!', 'success'); |
| @@ -5460,8 +5671,8 @@ | @@ -5460,8 +5671,8 @@ | ||
| 5460 | 5671 | ||
| 5461 | // 【修复】启动日志轮询,读取logger.info/debug/warning/error | 5672 | // 【修复】启动日志轮询,读取logger.info/debug/warning/error |
| 5462 | // SSE只推送显式事件(stage/chapter_status等),logger日志需要轮询读取 | 5673 | // SSE只推送显式事件(stage/chapter_status等),logger日志需要轮询读取 |
| 5463 | - startReportLogRefresh(); | ||
| 5464 | - console.log('[Report日志] SSE连接建立,同步启动日志轮询'); | 5674 | + reportLogManager.start(); |
| 5675 | + console.log('[ReportLogManager] SSE连接建立,同步启动日志轮询'); | ||
| 5465 | }; | 5676 | }; |
| 5466 | reportEventSource.onerror = () => { | 5677 | reportEventSource.onerror = () => { |
| 5467 | appendReportStreamLine('检测到网络抖动,SSE正在等待自动重连...', 'warn', { badge: 'SSE' }); | 5678 | appendReportStreamLine('检测到网络抖动,SSE正在等待自动重连...', 'warn', { badge: 'SSE' }); |
| @@ -5488,12 +5699,10 @@ | @@ -5488,12 +5699,10 @@ | ||
| 5488 | clearTimeout(reportStreamReconnectTimer); | 5699 | clearTimeout(reportStreamReconnectTimer); |
| 5489 | reportStreamReconnectTimer = null; | 5700 | reportStreamReconnectTimer = null; |
| 5490 | } | 5701 | } |
| 5491 | - // 清除日志刷新定时器 | ||
| 5492 | - if (reportLogRefreshInterval) { | ||
| 5493 | - clearInterval(reportLogRefreshInterval); | ||
| 5494 | - reportLogRefreshInterval = null; | ||
| 5495 | - console.log('[Report日志] SSE连接关闭,停止日志轮询'); | ||
| 5496 | - } | 5702 | + // 清除日志刷新(使用新的日志管理器) |
| 5703 | + reportLogManager.stop(); | ||
| 5704 | + console.log('[ReportLogManager] SSE连接关闭,停止日志轮询'); | ||
| 5705 | + | ||
| 5497 | clearStreamHeartbeat(); | 5706 | clearStreamHeartbeat(); |
| 5498 | if (!keepIndicator) { | 5707 | if (!keepIndicator) { |
| 5499 | updateReportStreamStatus('idle'); | 5708 | updateReportStreamStatus('idle'); |
| @@ -5600,8 +5809,10 @@ | @@ -5600,8 +5809,10 @@ | ||
| 5600 | case 'completed': | 5809 | case 'completed': |
| 5601 | appendReportStreamLine(payload.message || '任务完成', 'success'); | 5810 | appendReportStreamLine(payload.message || '任务完成', 'success'); |
| 5602 | 5811 | ||
| 5603 | - // 【修复】任务完成前最后一次刷新日志,确保所有日志都被读取 | ||
| 5604 | - refreshReportLog(); | 5812 | + // 【修复】任务完成前强制刷新最后一次日志,确保所有日志都被读取 |
| 5813 | + if (reportLogManager && reportLogManager.isRunning) { | ||
| 5814 | + reportLogManager.refresh(); | ||
| 5815 | + } | ||
| 5605 | 5816 | ||
| 5606 | // 延迟500ms后关闭SSE,确保最后一次日志刷新完成 | 5817 | // 延迟500ms后关闭SSE,确保最后一次日志刷新完成 |
| 5607 | setTimeout(() => { | 5818 | setTimeout(() => { |
-
Please register or login to post a comment