Showing
5 changed files
with
170 additions
and
14 deletions
| @@ -8,6 +8,20 @@ import sys | @@ -8,6 +8,20 @@ import sys | ||
| 8 | import streamlit as st | 8 | import streamlit as st |
| 9 | from datetime import datetime | 9 | from datetime import datetime |
| 10 | import json | 10 | import json |
| 11 | +import locale | ||
| 12 | + | ||
| 13 | +# 设置UTF-8编码环境 | ||
| 14 | +os.environ['PYTHONIOENCODING'] = 'utf-8' | ||
| 15 | +os.environ['PYTHONUTF8'] = '1' | ||
| 16 | + | ||
| 17 | +# 设置系统编码 | ||
| 18 | +try: | ||
| 19 | + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') | ||
| 20 | +except locale.Error: | ||
| 21 | + try: | ||
| 22 | + locale.setlocale(locale.LC_ALL, 'C.UTF-8') | ||
| 23 | + except locale.Error: | ||
| 24 | + pass | ||
| 11 | 25 | ||
| 12 | # 添加src目录到Python路径 | 26 | # 添加src目录到Python路径 |
| 13 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) | 27 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) |
| @@ -8,6 +8,20 @@ import sys | @@ -8,6 +8,20 @@ import sys | ||
| 8 | import streamlit as st | 8 | import streamlit as st |
| 9 | from datetime import datetime | 9 | from datetime import datetime |
| 10 | import json | 10 | import json |
| 11 | +import locale | ||
| 12 | + | ||
| 13 | +# 设置UTF-8编码环境 | ||
| 14 | +os.environ['PYTHONIOENCODING'] = 'utf-8' | ||
| 15 | +os.environ['PYTHONUTF8'] = '1' | ||
| 16 | + | ||
| 17 | +# 设置系统编码 | ||
| 18 | +try: | ||
| 19 | + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') | ||
| 20 | +except locale.Error: | ||
| 21 | + try: | ||
| 22 | + locale.setlocale(locale.LC_ALL, 'C.UTF-8') | ||
| 23 | + except locale.Error: | ||
| 24 | + pass | ||
| 11 | 25 | ||
| 12 | # 添加src目录到Python路径 | 26 | # 添加src目录到Python路径 |
| 13 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) | 27 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) |
| @@ -8,6 +8,20 @@ import sys | @@ -8,6 +8,20 @@ import sys | ||
| 8 | import streamlit as st | 8 | import streamlit as st |
| 9 | from datetime import datetime | 9 | from datetime import datetime |
| 10 | import json | 10 | import json |
| 11 | +import locale | ||
| 12 | + | ||
| 13 | +# 设置UTF-8编码环境 | ||
| 14 | +os.environ['PYTHONIOENCODING'] = 'utf-8' | ||
| 15 | +os.environ['PYTHONUTF8'] = '1' | ||
| 16 | + | ||
| 17 | +# 设置系统编码 | ||
| 18 | +try: | ||
| 19 | + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') | ||
| 20 | +except locale.Error: | ||
| 21 | + try: | ||
| 22 | + locale.setlocale(locale.LC_ALL, 'C.UTF-8') | ||
| 23 | + except locale.Error: | ||
| 24 | + pass | ||
| 11 | 25 | ||
| 12 | # 添加src目录到Python路径 | 26 | # 添加src目录到Python路径 |
| 13 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) | 27 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) |
| @@ -15,16 +15,26 @@ from flask_socketio import SocketIO, emit | @@ -15,16 +15,26 @@ from flask_socketio import SocketIO, emit | ||
| 15 | import signal | 15 | import signal |
| 16 | import atexit | 16 | import atexit |
| 17 | import requests | 17 | import requests |
| 18 | +import logging | ||
| 19 | +from pathlib import Path | ||
| 18 | 20 | ||
| 19 | app = Flask(__name__) | 21 | app = Flask(__name__) |
| 20 | app.config['SECRET_KEY'] = 'weibo_analysis_system_2024' | 22 | app.config['SECRET_KEY'] = 'weibo_analysis_system_2024' |
| 21 | socketio = SocketIO(app, cors_allowed_origins="*") | 23 | socketio = SocketIO(app, cors_allowed_origins="*") |
| 22 | 24 | ||
| 25 | +# 设置UTF-8编码环境 | ||
| 26 | +os.environ['PYTHONIOENCODING'] = 'utf-8' | ||
| 27 | +os.environ['PYTHONUTF8'] = '1' | ||
| 28 | + | ||
| 29 | +# 创建日志目录 | ||
| 30 | +LOG_DIR = Path('logs') | ||
| 31 | +LOG_DIR.mkdir(exist_ok=True) | ||
| 32 | + | ||
| 23 | # 全局变量存储进程信息 | 33 | # 全局变量存储进程信息 |
| 24 | processes = { | 34 | processes = { |
| 25 | - 'insight': {'process': None, 'port': 8501, 'status': 'stopped', 'output': []}, | ||
| 26 | - 'media': {'process': None, 'port': 8502, 'status': 'stopped', 'output': []}, | ||
| 27 | - 'query': {'process': None, 'port': 8503, 'status': 'stopped', 'output': []} | 35 | + 'insight': {'process': None, 'port': 8501, 'status': 'stopped', 'output': [], 'log_file': None}, |
| 36 | + 'media': {'process': None, 'port': 8502, 'status': 'stopped', 'output': [], 'log_file': None}, | ||
| 37 | + 'query': {'process': None, 'port': 8503, 'status': 'stopped', 'output': [], 'log_file': None} | ||
| 28 | } | 38 | } |
| 29 | 39 | ||
| 30 | # 输出队列 | 40 | # 输出队列 |
| @@ -34,8 +44,36 @@ output_queues = { | @@ -34,8 +44,36 @@ output_queues = { | ||
| 34 | 'query': Queue() | 44 | 'query': Queue() |
| 35 | } | 45 | } |
| 36 | 46 | ||
| 47 | +def write_log_to_file(app_name, line): | ||
| 48 | + """将日志写入文件""" | ||
| 49 | + try: | ||
| 50 | + log_file_path = LOG_DIR / f"{app_name}.log" | ||
| 51 | + with open(log_file_path, 'a', encoding='utf-8') as f: | ||
| 52 | + f.write(line + '\n') | ||
| 53 | + f.flush() | ||
| 54 | + except Exception as e: | ||
| 55 | + print(f"Error writing log for {app_name}: {e}") | ||
| 56 | + | ||
| 57 | +def read_log_from_file(app_name, tail_lines=None): | ||
| 58 | + """从文件读取日志""" | ||
| 59 | + try: | ||
| 60 | + log_file_path = LOG_DIR / f"{app_name}.log" | ||
| 61 | + if not log_file_path.exists(): | ||
| 62 | + return [] | ||
| 63 | + | ||
| 64 | + with open(log_file_path, 'r', encoding='utf-8') as f: | ||
| 65 | + lines = f.readlines() | ||
| 66 | + lines = [line.rstrip('\n\r') for line in lines if line.strip()] | ||
| 67 | + | ||
| 68 | + if tail_lines: | ||
| 69 | + return lines[-tail_lines:] | ||
| 70 | + return lines | ||
| 71 | + except Exception as e: | ||
| 72 | + print(f"Error reading log for {app_name}: {e}") | ||
| 73 | + return [] | ||
| 74 | + | ||
| 37 | def read_process_output(process, app_name): | 75 | def read_process_output(process, app_name): |
| 38 | - """读取进程输出并放入队列""" | 76 | + """读取进程输出并写入文件""" |
| 39 | while True: | 77 | while True: |
| 40 | try: | 78 | try: |
| 41 | if process.poll() is not None: | 79 | if process.poll() is not None: |
| @@ -43,15 +81,14 @@ def read_process_output(process, app_name): | @@ -43,15 +81,14 @@ def read_process_output(process, app_name): | ||
| 43 | 81 | ||
| 44 | output = process.stdout.readline() | 82 | output = process.stdout.readline() |
| 45 | if output: | 83 | if output: |
| 46 | - line = output.decode('utf-8', errors='ignore').strip() | 84 | + # 使用UTF-8解码,忽略错误字符 |
| 85 | + line = output.decode('utf-8', errors='replace').strip() | ||
| 47 | if line: | 86 | if line: |
| 48 | timestamp = datetime.now().strftime('%H:%M:%S') | 87 | timestamp = datetime.now().strftime('%H:%M:%S') |
| 49 | formatted_line = f"[{timestamp}] {line}" | 88 | formatted_line = f"[{timestamp}] {line}" |
| 50 | 89 | ||
| 51 | - # 添加到输出列表(保持最近100行) | ||
| 52 | - processes[app_name]['output'].append(formatted_line) | ||
| 53 | - if len(processes[app_name]['output']) > 100: | ||
| 54 | - processes[app_name]['output'].pop(0) | 90 | + # 写入日志文件 |
| 91 | + write_log_to_file(app_name, formatted_line) | ||
| 55 | 92 | ||
| 56 | # 发送到前端 | 93 | # 发送到前端 |
| 57 | socketio.emit('console_output', { | 94 | socketio.emit('console_output', { |
| @@ -59,7 +96,9 @@ def read_process_output(process, app_name): | @@ -59,7 +96,9 @@ def read_process_output(process, app_name): | ||
| 59 | 'line': formatted_line | 96 | 'line': formatted_line |
| 60 | }) | 97 | }) |
| 61 | except Exception as e: | 98 | except Exception as e: |
| 62 | - print(f"Error reading output for {app_name}: {e}") | 99 | + error_msg = f"Error reading output for {app_name}: {e}" |
| 100 | + print(error_msg) | ||
| 101 | + write_log_to_file(app_name, f"[{datetime.now().strftime('%H:%M:%S')}] {error_msg}") | ||
| 63 | break | 102 | break |
| 64 | 103 | ||
| 65 | def start_streamlit_app(app_name, script_path, port): | 104 | def start_streamlit_app(app_name, script_path, port): |
| @@ -72,6 +111,15 @@ def start_streamlit_app(app_name, script_path, port): | @@ -72,6 +111,15 @@ def start_streamlit_app(app_name, script_path, port): | ||
| 72 | if not os.path.exists(script_path): | 111 | if not os.path.exists(script_path): |
| 73 | return False, f"文件不存在: {script_path}" | 112 | return False, f"文件不存在: {script_path}" |
| 74 | 113 | ||
| 114 | + # 清空之前的日志文件 | ||
| 115 | + log_file_path = LOG_DIR / f"{app_name}.log" | ||
| 116 | + if log_file_path.exists(): | ||
| 117 | + log_file_path.unlink() | ||
| 118 | + | ||
| 119 | + # 创建启动日志 | ||
| 120 | + start_msg = f"[{datetime.now().strftime('%H:%M:%S')}] 启动 {app_name} 应用..." | ||
| 121 | + write_log_to_file(app_name, start_msg) | ||
| 122 | + | ||
| 75 | cmd = [ | 123 | cmd = [ |
| 76 | sys.executable, '-m', 'streamlit', 'run', | 124 | sys.executable, '-m', 'streamlit', 'run', |
| 77 | script_path, | 125 | script_path, |
| @@ -81,6 +129,15 @@ def start_streamlit_app(app_name, script_path, port): | @@ -81,6 +129,15 @@ def start_streamlit_app(app_name, script_path, port): | ||
| 81 | '--logger.level', 'info' | 129 | '--logger.level', 'info' |
| 82 | ] | 130 | ] |
| 83 | 131 | ||
| 132 | + # 设置环境变量确保UTF-8编码 | ||
| 133 | + env = os.environ.copy() | ||
| 134 | + env.update({ | ||
| 135 | + 'PYTHONIOENCODING': 'utf-8', | ||
| 136 | + 'PYTHONUTF8': '1', | ||
| 137 | + 'LANG': 'en_US.UTF-8', | ||
| 138 | + 'LC_ALL': 'en_US.UTF-8' | ||
| 139 | + }) | ||
| 140 | + | ||
| 84 | # 使用当前工作目录而不是脚本目录 | 141 | # 使用当前工作目录而不是脚本目录 |
| 85 | process = subprocess.Popen( | 142 | process = subprocess.Popen( |
| 86 | cmd, | 143 | cmd, |
| @@ -88,7 +145,9 @@ def start_streamlit_app(app_name, script_path, port): | @@ -88,7 +145,9 @@ def start_streamlit_app(app_name, script_path, port): | ||
| 88 | stderr=subprocess.STDOUT, | 145 | stderr=subprocess.STDOUT, |
| 89 | bufsize=1, | 146 | bufsize=1, |
| 90 | universal_newlines=False, | 147 | universal_newlines=False, |
| 91 | - cwd=os.getcwd() | 148 | + cwd=os.getcwd(), |
| 149 | + env=env, | ||
| 150 | + encoding=None # 让我们手动处理编码 | ||
| 92 | ) | 151 | ) |
| 93 | 152 | ||
| 94 | processes[app_name]['process'] = process | 153 | processes[app_name]['process'] = process |
| @@ -106,7 +165,9 @@ def start_streamlit_app(app_name, script_path, port): | @@ -106,7 +165,9 @@ def start_streamlit_app(app_name, script_path, port): | ||
| 106 | return True, f"{app_name} 应用启动中..." | 165 | return True, f"{app_name} 应用启动中..." |
| 107 | 166 | ||
| 108 | except Exception as e: | 167 | except Exception as e: |
| 109 | - return False, f"启动失败: {str(e)}" | 168 | + error_msg = f"启动失败: {str(e)}" |
| 169 | + write_log_to_file(app_name, f"[{datetime.now().strftime('%H:%M:%S')}] {error_msg}") | ||
| 170 | + return False, error_msg | ||
| 110 | 171 | ||
| 111 | def stop_streamlit_app(app_name): | 172 | def stop_streamlit_app(app_name): |
| 112 | """停止Streamlit应用""" | 173 | """停止Streamlit应用""" |
| @@ -245,9 +306,12 @@ def get_output(app_name): | @@ -245,9 +306,12 @@ def get_output(app_name): | ||
| 245 | if app_name not in processes: | 306 | if app_name not in processes: |
| 246 | return jsonify({'success': False, 'message': '未知应用'}) | 307 | return jsonify({'success': False, 'message': '未知应用'}) |
| 247 | 308 | ||
| 309 | + # 从文件读取完整日志 | ||
| 310 | + output_lines = read_log_from_file(app_name) | ||
| 311 | + | ||
| 248 | return jsonify({ | 312 | return jsonify({ |
| 249 | 'success': True, | 313 | 'success': True, |
| 250 | - 'output': processes[app_name]['output'] | 314 | + 'output': output_lines |
| 251 | }) | 315 | }) |
| 252 | 316 | ||
| 253 | @app.route('/api/search', methods=['POST']) | 317 | @app.route('/api/search', methods=['POST']) |
| @@ -338,6 +338,11 @@ | @@ -338,6 +338,11 @@ | ||
| 338 | checkStatus(); | 338 | checkStatus(); |
| 339 | setInterval(checkStatus, 5000); | 339 | setInterval(checkStatus, 5000); |
| 340 | 340 | ||
| 341 | + // 定期刷新控制台输出 | ||
| 342 | + setInterval(() => { | ||
| 343 | + refreshConsoleOutput(); | ||
| 344 | + }, 2000); | ||
| 345 | + | ||
| 341 | // 延迟预加载iframe以确保应用启动完成 | 346 | // 延迟预加载iframe以确保应用启动完成 |
| 342 | setTimeout(() => { | 347 | setTimeout(() => { |
| 343 | preloadIframes(); | 348 | preloadIframes(); |
| @@ -446,12 +451,18 @@ | @@ -446,12 +451,18 @@ | ||
| 446 | 451 | ||
| 447 | // 清空并加载新的控制台输出 | 452 | // 清空并加载新的控制台输出 |
| 448 | document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + app + ' 应用</div>'; | 453 | document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + app + ' 应用</div>'; |
| 454 | + | ||
| 455 | + // 重置行计数 | ||
| 456 | + lastLineCount[app] = 0; | ||
| 449 | loadConsoleOutput(app); | 457 | loadConsoleOutput(app); |
| 450 | 458 | ||
| 451 | // 更新嵌入页面 | 459 | // 更新嵌入页面 |
| 452 | updateEmbeddedPage(app); | 460 | updateEmbeddedPage(app); |
| 453 | } | 461 | } |
| 454 | 462 | ||
| 463 | + // 存储最后显示的行数,避免重复加载 | ||
| 464 | + let lastLineCount = {}; | ||
| 465 | + | ||
| 455 | // 加载控制台输出 | 466 | // 加载控制台输出 |
| 456 | function loadConsoleOutput(app) { | 467 | function loadConsoleOutput(app) { |
| 457 | fetch(`/api/output/${app}`) | 468 | fetch(`/api/output/${app}`) |
| @@ -459,12 +470,19 @@ | @@ -459,12 +470,19 @@ | ||
| 459 | .then(data => { | 470 | .then(data => { |
| 460 | if (data.success && data.output.length > 0) { | 471 | if (data.success && data.output.length > 0) { |
| 461 | const consoleOutput = document.getElementById('consoleOutput'); | 472 | const consoleOutput = document.getElementById('consoleOutput'); |
| 462 | - data.output.forEach(line => { | 473 | + |
| 474 | + // 只添加新的行 | ||
| 475 | + const lastCount = lastLineCount[app] || 0; | ||
| 476 | + const newLines = data.output.slice(lastCount); | ||
| 477 | + | ||
| 478 | + newLines.forEach(line => { | ||
| 463 | const div = document.createElement('div'); | 479 | const div = document.createElement('div'); |
| 464 | div.className = 'console-line'; | 480 | div.className = 'console-line'; |
| 465 | div.textContent = line; | 481 | div.textContent = line; |
| 466 | consoleOutput.appendChild(div); | 482 | consoleOutput.appendChild(div); |
| 467 | }); | 483 | }); |
| 484 | + | ||
| 485 | + lastLineCount[app] = data.output.length; | ||
| 468 | consoleOutput.scrollTop = consoleOutput.scrollHeight; | 486 | consoleOutput.scrollTop = consoleOutput.scrollHeight; |
| 469 | } | 487 | } |
| 470 | }) | 488 | }) |
| @@ -473,6 +491,38 @@ | @@ -473,6 +491,38 @@ | ||
| 473 | }); | 491 | }); |
| 474 | } | 492 | } |
| 475 | 493 | ||
| 494 | + // 刷新当前应用的控制台输出 | ||
| 495 | + function refreshConsoleOutput() { | ||
| 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 | + }); | ||
| 514 | + | ||
| 515 | + lastLineCount[currentApp] = data.output.length; | ||
| 516 | + consoleOutput.scrollTop = consoleOutput.scrollHeight; | ||
| 517 | + } | ||
| 518 | + } | ||
| 519 | + }) | ||
| 520 | + .catch(error => { | ||
| 521 | + console.error('刷新输出失败:', error); | ||
| 522 | + }); | ||
| 523 | + } | ||
| 524 | + } | ||
| 525 | + | ||
| 476 | // 添加控制台输出 | 526 | // 添加控制台输出 |
| 477 | function addConsoleOutput(line) { | 527 | function addConsoleOutput(line) { |
| 478 | const consoleOutput = document.getElementById('consoleOutput'); | 528 | const consoleOutput = document.getElementById('consoleOutput'); |
-
Please register or login to post a comment