Shell脚本调用Python脚本超2小时后日志无法写入文件问题
核心现象
直接在Linux终端执行Python脚本时,日志可正常完整写入目标文件;通过Shell脚本调用执行时,程序持续运行2~3小时及更长时间后,日志无法继续写入文件。
常见根因与对应修复方案
1. 流缓冲模式差异导致日志滞留在内存
直接在终端执行Python时,进程检测到stdout/stderr绑定到TTY设备,默认采用行缓冲模式,每输出一行日志就会立即刷入磁盘,因此日志写入正常。
当通过Shell脚本调用时,Python进程的stdout/stderr不再关联TTY,会自动切换为*全缓冲(块缓冲)*模式,缓冲区默认大小为4KB~8KB,仅当缓冲区写满、进程正常退出、手动执行flush操作时,才会把缓冲区内容写入磁盘。长时运行场景下如果日志输出频率不稳定,很容易出现日志长时间滞留在内存、看起来没有写入文件的现象,极端情况下进程异常退出时缓冲区内容会直接丢失。
修复方式:
- 调用Python时添加
-u参数,强制关闭stdout/stderr的全缓冲:
# Shell脚本中修改Python调用命令为如下形式 python3 -u your_target_script.py >> /var/log/your_app.log 2>&1
- 如果使用Python内置
logging模块写日志,显式配置写入后立即刷盘:
import logging file_handler = logging.FileHandler("/var/log/your_app.log") # 配置日志格式 file_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) # 每次输出日志后强制刷入磁盘 original_emit = file_handler.emit def emit_with_flush(record): original_emit(record) file_handler.stream.flush() file_handler.emit = emit_with_flush logger = logging.getLogger(__name__) logger.addHandler(file_handler) logger.setLevel(logging.INFO)
- 如果直接用
print输出日志,给所有print调用添加flush=True参数:print("your log content", flush=True)
2. 日志切割后文件句柄未更新
如果日志目录配置了logrotate按时间/大小切割,长时运行2~3小时刚好触发切割规则时,logrotate会默认将旧日志重命名为your_app.log.1,再创建新的your_app.log文件。此时如果Python进程、Shell重定向逻辑仍持有旧文件的inode句柄,后续日志会持续写入已经被重命名的旧文件,你查看新的日志文件时就会发现没有新内容写入,误以为日志写入中断。
修复方式:
- 修改logrotate配置,对该日志文件添加
copytruncate参数,切割时复制原文件内容后直接清空原文件,不需要进程重新打开文件句柄 - 优先使用Python logging模块自带的
RotatingFileHandler/TimedRotatingFileHandler实现日志切割,不要依赖系统层logrotate切割进程已打开的日志文件 - 如果必须使用系统logrotate,给Python进程增加日志句柄重载逻辑,切割完成后给进程发送
SIGUSR1信号触发重新打开日志文件
3. 父Shell进程退出导致文件描述符失效
如果Shell脚本中只是简单把Python进程放到后台执行(比如直接写python3 your_script.py &),没有做进程托管,当Shell脚本执行完毕退出后,父进程打开的文件描述符会被系统回收,此时Python进程持有的日志文件句柄失效,后续写入操作会触发Bad file descriptor错误,由于这类错误默认不会输出到可见的日志中,外在表现就是日志停止写入。
修复方式:
- 长时运行的Python进程不要通过Shell嵌套调用启动,优先使用
systemd、supervisor这类专业进程管理器托管,在服务配置中固定日志重定向规则、进程重启策略 - 如果临时需要通过Shell启动,使用
nohup脱离终端和父进程关联,示例:nohup python3 -u your_script.py >> /var/log/your_app.log 2>&1 &
4. 磁盘资源耗尽
长时运行场景下日志持续写入,如果没有配置切割清理策略,2~3小时的日志量可能刚好写满日志所在分区的磁盘空间,或者耗尽分区inode,此时所有写入文件的操作都会失败。直接在终端执行时往往运行时间较短,没有触发资源耗尽阈值,因此不会暴露问题。
排查命令:
# 查看磁盘空间使用率 df -h # 查看inode使用率 df -i
修复方式:清理分区内冗余文件,配置日志自动切割、过期清理策略,避免磁盘资源被写满。
内容的提问来源于stack exchange,提问作者Priyanka Chaudhari

