diff --git a/oedp/build/oedp.spec b/oedp/build/oedp.spec index d417aae70a10942ad04271079f97d5522d484d6a..e4bf17e4b59877271da2f896111dbd32c1c24731 100644 --- a/oedp/build/oedp.spec +++ b/oedp/build/oedp.spec @@ -75,6 +75,7 @@ fi %changelog * Sun Jul 27 2025 Ding Jiahui - 1.1.3-0 - Downlowd details when repo updating. +- The operation log will be recorded in the plugin directory. * Tue Jul 5 2025 Ding Jiahui - 1.1.2-0 - Add local deployment mode and runtime printing. diff --git a/oedp/src/commands/run/run_action.py b/oedp/src/commands/run/run_action.py index 9637222db35f498062199b04137a08ce6d6ef12b..5c4edda52a6d1268d0e21169dd0f6a76b1800013 100644 --- a/oedp/src/commands/run/run_action.py +++ b/oedp/src/commands/run/run_action.py @@ -93,7 +93,8 @@ class RunAction: self.log.debug(f'Executing cmd: {cmd}') try: - out, err, ret = CommandExecutor.run_single_cmd(cmd, print_on_console=True, env=env) + log_file = os.path.join(project, 'run.log') + out, err, ret = CommandExecutor.run_single_cmd(cmd, print_on_console=True, env=env, log_path=log_file) self.log.debug('Execution log:\n' + out) if ret != 0: self.log.error(f'Execute cmd failed [code:{ret}]:\nSTDOUT: {out}\nSTDERR: {err}') diff --git a/oedp/src/utils/command/command_executor.py b/oedp/src/utils/command/command_executor.py index e85bc24fd168bb550137bac21b9736241a80a933..f3279962c609c83df736b9283d6f8c9c08e3c8e5 100644 --- a/oedp/src/utils/command/command_executor.py +++ b/oedp/src/utils/command/command_executor.py @@ -11,13 +11,19 @@ # Create: 2024-12-23 # ====================================================================================================================== +import atexit import os import signal import subprocess import sys +from src.utils.log.logger_generator import LoggerGenerator + class CommandExecutor: + _log_file = None + _log_file_path = None + _atexit_registered = False """ 系统命令封装,收编挪到公共组件目录 """ @@ -30,6 +36,7 @@ class CommandExecutor: encoding = kwargs.get('encoding', None) print_on_console = kwargs.get('print_on_console', False) env = kwargs.get('env', None) + log_path = kwargs.get('log_path', None) if env is not None and not isinstance(env, dict): raise ValueError("env parameter must be a dictionary or None") @@ -48,7 +55,7 @@ class CommandExecutor: return "", str(error_except), 1 # 返回默认错误码,避免访问未赋值的pipe变量 try: - stdout, stderr = CommandExecutor._get_stdout_stderr(pipe, timeout, print_on_console) + stdout, stderr = CommandExecutor._get_stdout_stderr(pipe, timeout, print_on_console, log_path) except subprocess.TimeoutExpired as timeout_err: # 使用os.killpg杀死进程组 pipe.kill() @@ -67,8 +74,16 @@ class CommandExecutor: return "", "", pipe.returncode @staticmethod - def run_mult_cmd(cmds, timeout=600, raise_exception=False, show_error=True, encoding=None, print_on_console=False, - file_descriptor=None, env=None): + def run_mult_cmd(cmds, **kwargs): + timeout = kwargs.get('timeout', 600) + raise_exception = kwargs.get('raise_exception', False) + show_error = kwargs.get('show_error', True) + encoding = kwargs.get('encoding', None) + print_on_console = kwargs.get('print_on_console', False) + file_descriptor = kwargs.get('file_descriptor', None) + env = kwargs.get('env', None) + log_path = kwargs.get('log_path', None) + if encoding is None: encoding = sys.getdefaultencoding() if env is not None and not isinstance(env, dict): @@ -96,7 +111,7 @@ class CommandExecutor: pipes.append(pipe) try: - stdout, stderr = CommandExecutor._get_stdout_stderr(pipes[-1], timeout, print_on_console) + stdout, stderr = CommandExecutor._get_stdout_stderr(pipes[-1], timeout, print_on_console, log_path) except subprocess.TimeoutExpired as timeout_err: # 使用os.killpg杀死进程组 pipes[-1].kill() @@ -113,17 +128,48 @@ class CommandExecutor: return stdout, error, pipes[-1].returncode return "", "", pipes[-1].returncode - @staticmethod - def _get_stdout_stderr(pipe, timeout, print_on_console): - if print_on_console: - stdout, stderr = '', '' - for stdout_line in iter(pipe.stdout.readline, ''): - stdout += stdout_line - print(stdout_line, end='') - for stderr_line in iter(pipe.stderr.readline, ''): - stderr += stderr_line - print(stderr_line, end='') - pipe.wait(timeout=timeout) - else: - stdout, stderr = pipe.communicate(timeout=timeout) + @classmethod + def _process_stream(cls, stream, output, print_on_console): + for line in iter(stream.readline, ''): + output += line + log_line = line.rstrip() + "\n" + cls._log_file.write(log_line) + cls._log_file.flush() + if print_on_console: + print(line, end='') + return output + + @classmethod + def _get_stdout_stderr(cls, pipe, timeout, print_on_console, log_path=None): + stdout, stderr = '', '' + + # 初始化直接日志文件写入 + if log_path and (cls._log_file is None or cls._log_file_path != log_path): + if cls._log_file: + cls._log_file.close() + cls._log_file_path = log_path + cls._log_file = open(log_path, 'a', buffering=1) # 行缓冲 + elif cls._log_file is None: + cls._log_file_path = LoggerGenerator().get_logger('command_executor').handlers[1].baseFilename + cls._log_file = open(cls._log_file_path, 'a', buffering=1) # 行缓冲 + + # 注册atexit处理函数 + if not cls._atexit_registered: + atexit.register(cls.cleanup_resources) + cls._atexit_registered = True + + stdout = cls._process_stream(pipe.stdout, stdout, print_on_console) + stderr = cls._process_stream(pipe.stderr, stderr, print_on_console) + + pipe.wait(timeout=timeout) return stdout, stderr + + @classmethod + def cleanup_resources(cls): + """清理类持有的文件描述符""" + if cls._log_file is not None: + try: + cls._log_file.close() + except Exception as e: + LoggerGenerator().get_logger('command_executor').error(f"Failed to close log file: {str(e)}") + cls._log_file = None diff --git a/oedp/src/utils/log/logger_generator.py b/oedp/src/utils/log/logger_generator.py index 399482712f76173444ec43163dabd4efd7d2be5f..8a4cc5d343bd4aee5f88c4489f00bd68faee09f2 100644 --- a/oedp/src/utils/log/logger_generator.py +++ b/oedp/src/utils/log/logger_generator.py @@ -58,7 +58,8 @@ class LoggerGenerator: file_handler = logging.handlers.RotatingFileHandler( filename=LOG_CONFIG_OBJ.log_file_path, maxBytes=LOG_CONFIG_OBJ.file_max_size, - backupCount=LOG_CONFIG_OBJ.backup_count + backupCount=LOG_CONFIG_OBJ.backup_count, + delay=False # 确保立即写入文件 ) file_handler.setLevel(LOG_CONFIG_OBJ.file_log_level) file_formatter = logging.Formatter(LOG_CONFIG_OBJ.file_log_format)