Introduce the function of initially restoring the real-time output of console information.
Showing
2 changed files
with
138 additions
and
132 deletions
| @@ -74,27 +74,69 @@ def read_log_from_file(app_name, tail_lines=None): | @@ -74,27 +74,69 @@ def read_log_from_file(app_name, tail_lines=None): | ||
| 74 | 74 | ||
| 75 | def read_process_output(process, app_name): | 75 | def read_process_output(process, app_name): |
| 76 | """读取进程输出并写入文件""" | 76 | """读取进程输出并写入文件""" |
| 77 | + import select | ||
| 78 | + import sys | ||
| 79 | + | ||
| 77 | while True: | 80 | while True: |
| 78 | try: | 81 | try: |
| 79 | if process.poll() is not None: | 82 | if process.poll() is not None: |
| 83 | + # 进程结束,读取剩余输出 | ||
| 84 | + remaining_output = process.stdout.read() | ||
| 85 | + if remaining_output: | ||
| 86 | + lines = remaining_output.decode('utf-8', errors='replace').split('\n') | ||
| 87 | + for line in lines: | ||
| 88 | + line = line.strip() | ||
| 89 | + if line: | ||
| 90 | + timestamp = datetime.now().strftime('%H:%M:%S') | ||
| 91 | + formatted_line = f"[{timestamp}] {line}" | ||
| 92 | + write_log_to_file(app_name, formatted_line) | ||
| 93 | + socketio.emit('console_output', { | ||
| 94 | + 'app': app_name, | ||
| 95 | + 'line': formatted_line | ||
| 96 | + }) | ||
| 80 | break | 97 | break |
| 81 | 98 | ||
| 82 | - output = process.stdout.readline() | ||
| 83 | - if output: | ||
| 84 | - # 使用UTF-8解码,忽略错误字符 | ||
| 85 | - line = output.decode('utf-8', errors='replace').strip() | ||
| 86 | - if line: | ||
| 87 | - timestamp = datetime.now().strftime('%H:%M:%S') | ||
| 88 | - formatted_line = f"[{timestamp}] {line}" | ||
| 89 | - | ||
| 90 | - # 写入日志文件 | ||
| 91 | - write_log_to_file(app_name, formatted_line) | ||
| 92 | - | ||
| 93 | - # 发送到前端 | ||
| 94 | - socketio.emit('console_output', { | ||
| 95 | - 'app': app_name, | ||
| 96 | - 'line': formatted_line | ||
| 97 | - }) | 99 | + # 使用非阻塞读取 |
| 100 | + if sys.platform == 'win32': | ||
| 101 | + # Windows下使用不同的方法 | ||
| 102 | + output = process.stdout.readline() | ||
| 103 | + if output: | ||
| 104 | + line = output.decode('utf-8', errors='replace').strip() | ||
| 105 | + if line: | ||
| 106 | + timestamp = datetime.now().strftime('%H:%M:%S') | ||
| 107 | + formatted_line = f"[{timestamp}] {line}" | ||
| 108 | + | ||
| 109 | + # 写入日志文件 | ||
| 110 | + write_log_to_file(app_name, formatted_line) | ||
| 111 | + | ||
| 112 | + # 发送到前端 | ||
| 113 | + socketio.emit('console_output', { | ||
| 114 | + 'app': app_name, | ||
| 115 | + 'line': formatted_line | ||
| 116 | + }) | ||
| 117 | + else: | ||
| 118 | + # 没有输出时短暂休眠 | ||
| 119 | + time.sleep(0.1) | ||
| 120 | + else: | ||
| 121 | + # Unix系统使用select | ||
| 122 | + ready, _, _ = select.select([process.stdout], [], [], 0.1) | ||
| 123 | + if ready: | ||
| 124 | + output = process.stdout.readline() | ||
| 125 | + if output: | ||
| 126 | + line = output.decode('utf-8', errors='replace').strip() | ||
| 127 | + if line: | ||
| 128 | + timestamp = datetime.now().strftime('%H:%M:%S') | ||
| 129 | + formatted_line = f"[{timestamp}] {line}" | ||
| 130 | + | ||
| 131 | + # 写入日志文件 | ||
| 132 | + write_log_to_file(app_name, formatted_line) | ||
| 133 | + | ||
| 134 | + # 发送到前端 | ||
| 135 | + socketio.emit('console_output', { | ||
| 136 | + 'app': app_name, | ||
| 137 | + 'line': formatted_line | ||
| 138 | + }) | ||
| 139 | + | ||
| 98 | except Exception as e: | 140 | except Exception as e: |
| 99 | error_msg = f"Error reading output for {app_name}: {e}" | 141 | error_msg = f"Error reading output for {app_name}: {e}" |
| 100 | print(error_msg) | 142 | print(error_msg) |
| @@ -126,16 +168,19 @@ def start_streamlit_app(app_name, script_path, port): | @@ -126,16 +168,19 @@ def start_streamlit_app(app_name, script_path, port): | ||
| 126 | '--server.port', str(port), | 168 | '--server.port', str(port), |
| 127 | '--server.headless', 'true', | 169 | '--server.headless', 'true', |
| 128 | '--browser.gatherUsageStats', 'false', | 170 | '--browser.gatherUsageStats', 'false', |
| 129 | - '--logger.level', 'info' | 171 | + '--logger.level', 'debug', # 增加日志详细程度 |
| 172 | + '--server.enableCORS', 'false' | ||
| 130 | ] | 173 | ] |
| 131 | 174 | ||
| 132 | - # 设置环境变量确保UTF-8编码 | 175 | + # 设置环境变量确保UTF-8编码和减少缓冲 |
| 133 | env = os.environ.copy() | 176 | env = os.environ.copy() |
| 134 | env.update({ | 177 | env.update({ |
| 135 | 'PYTHONIOENCODING': 'utf-8', | 178 | 'PYTHONIOENCODING': 'utf-8', |
| 136 | 'PYTHONUTF8': '1', | 179 | 'PYTHONUTF8': '1', |
| 137 | 'LANG': 'en_US.UTF-8', | 180 | 'LANG': 'en_US.UTF-8', |
| 138 | - 'LC_ALL': 'en_US.UTF-8' | 181 | + 'LC_ALL': 'en_US.UTF-8', |
| 182 | + 'PYTHONUNBUFFERED': '1', # 禁用Python缓冲 | ||
| 183 | + 'STREAMLIT_BROWSER_GATHER_USAGE_STATS': 'false' | ||
| 139 | }) | 184 | }) |
| 140 | 185 | ||
| 141 | # 使用当前工作目录而不是脚本目录 | 186 | # 使用当前工作目录而不是脚本目录 |
| @@ -143,11 +188,12 @@ def start_streamlit_app(app_name, script_path, port): | @@ -143,11 +188,12 @@ def start_streamlit_app(app_name, script_path, port): | ||
| 143 | cmd, | 188 | cmd, |
| 144 | stdout=subprocess.PIPE, | 189 | stdout=subprocess.PIPE, |
| 145 | stderr=subprocess.STDOUT, | 190 | stderr=subprocess.STDOUT, |
| 146 | - bufsize=1, | 191 | + bufsize=0, # 无缓冲 |
| 147 | universal_newlines=False, | 192 | universal_newlines=False, |
| 148 | cwd=os.getcwd(), | 193 | cwd=os.getcwd(), |
| 149 | env=env, | 194 | env=env, |
| 150 | - encoding=None # 让我们手动处理编码 | 195 | + encoding=None, # 让我们手动处理编码 |
| 196 | + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 | ||
| 151 | ) | 197 | ) |
| 152 | 198 | ||
| 153 | processes[app_name]['process'] = process | 199 | processes[app_name]['process'] = process |
| @@ -314,6 +360,27 @@ def get_output(app_name): | @@ -314,6 +360,27 @@ def get_output(app_name): | ||
| 314 | 'output': output_lines | 360 | 'output': output_lines |
| 315 | }) | 361 | }) |
| 316 | 362 | ||
| 363 | +@app.route('/api/test_log/<app_name>') | ||
| 364 | +def test_log(app_name): | ||
| 365 | + """测试日志写入功能""" | ||
| 366 | + if app_name not in processes: | ||
| 367 | + return jsonify({'success': False, 'message': '未知应用'}) | ||
| 368 | + | ||
| 369 | + # 写入测试消息 | ||
| 370 | + test_msg = f"[{datetime.now().strftime('%H:%M:%S')}] 测试日志消息 - {datetime.now()}" | ||
| 371 | + write_log_to_file(app_name, test_msg) | ||
| 372 | + | ||
| 373 | + # 通过Socket.IO发送 | ||
| 374 | + socketio.emit('console_output', { | ||
| 375 | + 'app': app_name, | ||
| 376 | + 'line': test_msg | ||
| 377 | + }) | ||
| 378 | + | ||
| 379 | + return jsonify({ | ||
| 380 | + 'success': True, | ||
| 381 | + 'message': f'测试消息已写入 {app_name} 日志' | ||
| 382 | + }) | ||
| 383 | + | ||
| 317 | @app.route('/api/search', methods=['POST']) | 384 | @app.route('/api/search', methods=['POST']) |
| 318 | def search(): | 385 | def search(): |
| 319 | """统一搜索接口""" | 386 | """统一搜索接口""" |
| @@ -172,32 +172,16 @@ | @@ -172,32 +172,16 @@ | ||
| 172 | /* 控制台输出 */ | 172 | /* 控制台输出 */ |
| 173 | .console-output { | 173 | .console-output { |
| 174 | flex: 1; | 174 | flex: 1; |
| 175 | + padding: 15px; | ||
| 175 | background-color: #000000; | 176 | background-color: #000000; |
| 176 | color: #00ff00; | 177 | color: #00ff00; |
| 177 | font-family: 'Courier New', monospace; | 178 | font-family: 'Courier New', monospace; |
| 178 | font-size: 12px; | 179 | font-size: 12px; |
| 179 | - overflow: hidden; /* 隐藏滚动条,由内部面板控制 */ | ||
| 180 | - position: relative; | ||
| 181 | - min-height: 0; /* 允许内容缩小 */ | ||
| 182 | - max-height: 100%; /* 限制最大高度 */ | ||
| 183 | - } | ||
| 184 | - | ||
| 185 | - /* 控制台面板 */ | ||
| 186 | - .console-panel { | ||
| 187 | - position: absolute; | ||
| 188 | - top: 0; | ||
| 189 | - left: 0; | ||
| 190 | - width: 100%; | ||
| 191 | - height: 100%; | ||
| 192 | - padding: 15px; | ||
| 193 | overflow-y: auto; | 180 | overflow-y: auto; |
| 194 | white-space: pre-wrap; | 181 | white-space: pre-wrap; |
| 195 | word-break: break-all; | 182 | word-break: break-all; |
| 196 | - display: none; /* 默认隐藏 */ | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - .console-panel.active { | ||
| 200 | - display: block; /* 激活时显示 */ | 183 | + min-height: 0; /* 允许内容缩小 */ |
| 184 | + max-height: 100%; /* 限制最大高度 */ | ||
| 201 | } | 185 | } |
| 202 | 186 | ||
| 203 | .console-line { | 187 | .console-line { |
| @@ -320,20 +304,7 @@ | @@ -320,20 +304,7 @@ | ||
| 320 | 304 | ||
| 321 | <!-- 控制台输出 --> | 305 | <!-- 控制台输出 --> |
| 322 | <div class="console-output" id="consoleOutput"> | 306 | <div class="console-output" id="consoleOutput"> |
| 323 | - <!-- Insight Engine 控制台 --> | ||
| 324 | - <div class="console-panel active" id="console-insight"> | ||
| 325 | - <div class="console-line">[系统] Insight Engine 控制台</div> | ||
| 326 | - </div> | ||
| 327 | - | ||
| 328 | - <!-- Media Engine 控制台 --> | ||
| 329 | - <div class="console-panel" id="console-media"> | ||
| 330 | - <div class="console-line">[系统] Media Engine 控制台</div> | ||
| 331 | - </div> | ||
| 332 | - | ||
| 333 | - <!-- Query Engine 控制台 --> | ||
| 334 | - <div class="console-panel" id="console-query"> | ||
| 335 | - <div class="console-line">[系统] Query Engine 控制台</div> | ||
| 336 | - </div> | 307 | + <div class="console-line">[系统] 等待连接...</div> |
| 337 | </div> | 308 | </div> |
| 338 | </div> | 309 | </div> |
| 339 | </div> | 310 | </div> |
| @@ -367,13 +338,10 @@ | @@ -367,13 +338,10 @@ | ||
| 367 | checkStatus(); | 338 | checkStatus(); |
| 368 | setInterval(checkStatus, 5000); | 339 | setInterval(checkStatus, 5000); |
| 369 | 340 | ||
| 370 | - // 初始化行计数 | ||
| 371 | - lastLineCount = { insight: 1, media: 1, query: 1 }; // 跳过初始消息 | ||
| 372 | - | ||
| 373 | // 定期刷新控制台输出 | 341 | // 定期刷新控制台输出 |
| 374 | setInterval(() => { | 342 | setInterval(() => { |
| 375 | refreshConsoleOutput(); | 343 | refreshConsoleOutput(); |
| 376 | - }, 2000); | 344 | + }, 1000); |
| 377 | 345 | ||
| 378 | // 延迟预加载iframe以确保应用启动完成 | 346 | // 延迟预加载iframe以确保应用启动完成 |
| 379 | setTimeout(() => { | 347 | setTimeout(() => { |
| @@ -395,16 +363,8 @@ | @@ -395,16 +363,8 @@ | ||
| 395 | }); | 363 | }); |
| 396 | 364 | ||
| 397 | socket.on('console_output', function(data) { | 365 | socket.on('console_output', function(data) { |
| 398 | - // 直接添加到对应应用的控制台面板,不依赖currentApp | ||
| 399 | - const consolePanel = document.getElementById(`console-${data.app}`); | ||
| 400 | - if (consolePanel) { | ||
| 401 | - const div = document.createElement('div'); | ||
| 402 | - div.className = 'console-line'; | ||
| 403 | - div.textContent = data.line; | ||
| 404 | - consolePanel.appendChild(div); | ||
| 405 | - | ||
| 406 | - // 自动滚动到底部 | ||
| 407 | - consolePanel.scrollTop = consolePanel.scrollHeight; | 366 | + if (data.app === currentApp) { |
| 367 | + addConsoleOutput(data.line); | ||
| 408 | } | 368 | } |
| 409 | }); | 369 | }); |
| 410 | 370 | ||
| @@ -489,24 +449,12 @@ | @@ -489,24 +449,12 @@ | ||
| 489 | 449 | ||
| 490 | currentApp = app; | 450 | currentApp = app; |
| 491 | 451 | ||
| 492 | - // 切换控制台面板显示 | ||
| 493 | - document.querySelectorAll('.console-panel').forEach(panel => { | ||
| 494 | - panel.classList.remove('active'); | ||
| 495 | - }); | ||
| 496 | - document.getElementById(`console-${app}`).classList.add('active'); | 452 | + // 清空并加载新的控制台输出 |
| 453 | + document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + app + ' 应用</div>'; | ||
| 497 | 454 | ||
| 498 | - // 只在面板为空时加载输出,不重置行计数 | ||
| 499 | - const consolePanel = document.getElementById(`console-${app}`); | ||
| 500 | - if (consolePanel && consolePanel.children.length <= 1) { // 只有初始消息 | ||
| 501 | - loadConsoleOutput(app); | ||
| 502 | - } | ||
| 503 | - | ||
| 504 | - // 切换后滚动到底部 | ||
| 505 | - setTimeout(() => { | ||
| 506 | - if (consolePanel) { | ||
| 507 | - consolePanel.scrollTop = consolePanel.scrollHeight; | ||
| 508 | - } | ||
| 509 | - }, 100); | 455 | + // 重置行计数 |
| 456 | + lastLineCount[app] = 0; | ||
| 457 | + loadConsoleOutput(app); | ||
| 510 | 458 | ||
| 511 | // 更新嵌入页面 | 459 | // 更新嵌入页面 |
| 512 | updateEmbeddedPage(app); | 460 | updateEmbeddedPage(app); |
| @@ -521,7 +469,7 @@ | @@ -521,7 +469,7 @@ | ||
| 521 | .then(response => response.json()) | 469 | .then(response => response.json()) |
| 522 | .then(data => { | 470 | .then(data => { |
| 523 | if (data.success && data.output.length > 0) { | 471 | if (data.success && data.output.length > 0) { |
| 524 | - const consolePanel = document.getElementById(`console-${app}`); | 472 | + const consoleOutput = document.getElementById('consoleOutput'); |
| 525 | 473 | ||
| 526 | // 只添加新的行 | 474 | // 只添加新的行 |
| 527 | const lastCount = lastLineCount[app] || 0; | 475 | const lastCount = lastLineCount[app] || 0; |
| @@ -531,11 +479,11 @@ | @@ -531,11 +479,11 @@ | ||
| 531 | const div = document.createElement('div'); | 479 | const div = document.createElement('div'); |
| 532 | div.className = 'console-line'; | 480 | div.className = 'console-line'; |
| 533 | div.textContent = line; | 481 | div.textContent = line; |
| 534 | - consolePanel.appendChild(div); | 482 | + consoleOutput.appendChild(div); |
| 535 | }); | 483 | }); |
| 536 | 484 | ||
| 537 | lastLineCount[app] = data.output.length; | 485 | lastLineCount[app] = data.output.length; |
| 538 | - consolePanel.scrollTop = consolePanel.scrollHeight; | 486 | + consoleOutput.scrollTop = consoleOutput.scrollHeight; |
| 539 | } | 487 | } |
| 540 | }) | 488 | }) |
| 541 | .catch(error => { | 489 | .catch(error => { |
| @@ -543,57 +491,48 @@ | @@ -543,57 +491,48 @@ | ||
| 543 | }); | 491 | }); |
| 544 | } | 492 | } |
| 545 | 493 | ||
| 546 | - // 刷新所有应用的控制台输出 | 494 | + // 刷新当前应用的控制台输出 |
| 547 | function refreshConsoleOutput() { | 495 | function refreshConsoleOutput() { |
| 548 | - // 为每个应用刷新输出,不只是当前应用 | ||
| 549 | - Object.keys(appStatus).forEach(app => { | ||
| 550 | - if (appStatus[app] === 'running' || appStatus[app] === 'starting') { | ||
| 551 | - fetch(`/api/output/${app}`) | ||
| 552 | - .then(response => response.json()) | ||
| 553 | - .then(data => { | ||
| 554 | - if (data.success && data.output.length > 0) { | ||
| 555 | - const consolePanel = document.getElementById(`console-${app}`); | ||
| 556 | - | ||
| 557 | - // 只添加新的行 | ||
| 558 | - const lastCount = lastLineCount[app] || 0; | ||
| 559 | - const newLines = data.output.slice(lastCount); | 496 | + if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') { |
| 497 | + fetch(`/api/output/${currentApp}`) | ||
| 498 | + .then(response => response.json()) | ||
| 499 | + .then(data => { | ||
| 500 | + if (data.success && data.output.length > 0) { | ||
| 501 | + const consoleOutput = document.getElementById('consoleOutput'); | ||
| 502 | + | ||
| 503 | + // 只添加新的行 | ||
| 504 | + const lastCount = lastLineCount[currentApp] || 0; | ||
| 505 | + const newLines = data.output.slice(lastCount); | ||
| 506 | + | ||
| 507 | + if (newLines.length > 0) { | ||
| 508 | + newLines.forEach(line => { | ||
| 509 | + const div = document.createElement('div'); | ||
| 510 | + div.className = 'console-line'; | ||
| 511 | + div.textContent = line; | ||
| 512 | + consoleOutput.appendChild(div); | ||
| 513 | + }); | ||
| 560 | 514 | ||
| 561 | - if (newLines.length > 0) { | ||
| 562 | - newLines.forEach(line => { | ||
| 563 | - const div = document.createElement('div'); | ||
| 564 | - div.className = 'console-line'; | ||
| 565 | - div.textContent = line; | ||
| 566 | - consolePanel.appendChild(div); | ||
| 567 | - }); | ||
| 568 | - | ||
| 569 | - lastLineCount[app] = data.output.length; | ||
| 570 | - // 只有当前显示的面板才自动滚动 | ||
| 571 | - if (app === currentApp) { | ||
| 572 | - consolePanel.scrollTop = consolePanel.scrollHeight; | ||
| 573 | - } | ||
| 574 | - } | 515 | + lastLineCount[currentApp] = data.output.length; |
| 516 | + consoleOutput.scrollTop = consoleOutput.scrollHeight; | ||
| 575 | } | 517 | } |
| 576 | - }) | ||
| 577 | - .catch(error => { | ||
| 578 | - console.error(`刷新${app}输出失败:`, error); | ||
| 579 | - }); | ||
| 580 | - } | ||
| 581 | - }); | 518 | + } |
| 519 | + }) | ||
| 520 | + .catch(error => { | ||
| 521 | + console.error('刷新输出失败:', error); | ||
| 522 | + }); | ||
| 523 | + } | ||
| 582 | } | 524 | } |
| 583 | 525 | ||
| 584 | // 添加控制台输出 | 526 | // 添加控制台输出 |
| 585 | function addConsoleOutput(line) { | 527 | function addConsoleOutput(line) { |
| 586 | - // 根据当前应用添加到对应的控制台面板 | ||
| 587 | - const consolePanel = document.getElementById(`console-${currentApp}`); | ||
| 588 | - if (consolePanel) { | ||
| 589 | - const div = document.createElement('div'); | ||
| 590 | - div.className = 'console-line'; | ||
| 591 | - div.textContent = line; | ||
| 592 | - consolePanel.appendChild(div); | ||
| 593 | - | ||
| 594 | - // 自动滚动到底部显示最新内容 | ||
| 595 | - consolePanel.scrollTop = consolePanel.scrollHeight; | ||
| 596 | - } | 528 | + const consoleOutput = document.getElementById('consoleOutput'); |
| 529 | + const div = document.createElement('div'); | ||
| 530 | + div.className = 'console-line'; | ||
| 531 | + div.textContent = line; | ||
| 532 | + consoleOutput.appendChild(div); | ||
| 533 | + | ||
| 534 | + // 自动滚动到底部显示最新内容 | ||
| 535 | + consoleOutput.scrollTop = consoleOutput.scrollHeight; | ||
| 597 | } | 536 | } |
| 598 | 537 | ||
| 599 | // 预加载的iframe存储 | 538 | // 预加载的iframe存储 |
-
Please register or login to post a comment