Linux服务器后台运行Python脚本并保存日志
详解在Linux服务器后台持续运行Python脚本并可靠保存日志的完整操作方法,涵盖脚本与目录准备、nohup重定向与缓冲控制、进程核验与停止、systemd服务托管及日志排查。
在Linux服务器上让Python脚本脱离SSH终端持续运行并完整记录日志,最直接的做法是使用nohup python3 -u 脚本路径 >> 日志路径 2>&1 &;若任务需要开机自启和异常重启,则应交由systemd托管,并在Python脚本内部使用RotatingFileHandler控制日志体积。本文将按照准备环境、启动后台任务、实时查看与停止进程、配置长期服务以及排查日志异常的顺序,完整说明每一步的操作方法与背后的运行机制。
一、为什么直接运行脚本会在断开SSH后停止且看不到日志
通过SSH登录Linux服务器并直接执行python3 worker.py时,Python进程是当前Shell会话的子进程,且占用前台终端。一旦网络波动、关闭终端窗口或退出SSH连接,操作系统内核会向该会话下的进程组发送SIGHUP(挂起信号),导致Python脚本立即终止。
另一个常见现象是:即使在命令末尾加上&将脚本放入后台,再把输出重定向到文件,打开日志文件时却发现里面是空的。这是因为Python在检测到标准输出(stdout)的目标不是交互式终端而是普通文件时,会自动启用块缓冲(通常积累到4KB或8KB才写入磁盘一次)。如果脚本打印频率较低,或者进程在缓冲区写满前被强制终止,内存中的日志就会丢失。
二、准备工作:规范目录结构与示例脚本
在启动后台任务前,建议将脚本代码与日志文件分目录存放,并在脚本中使用绝对路径,避免因后台启动时的工作目录(Working Directory)不同而引发找不到文件的错误。本节统一使用以下目录和文件路径作为完整示例:
- 项目目录与脚本路径:
/opt/app/worker.py - Python虚拟环境解释器路径:
/opt/app/.venv/bin/python3 - 日志存放路径:
/var/log/app/worker.log
通过SSH登录Linux服务器后,先创建代码目录与日志目录,并将日志目录归属权赋给当前运行脚本的普通用户(此处以用户名ubuntu为例):
sudo mkdir -p /opt/app /var/log/app
sudo chown -R ubuntu:ubuntu /opt/app /var/log/app
python3 -m venv /opt/app/.venv
接着编写用于演示后台运行的脚本/opt/app/worker.py。为了在后续日志中清晰区分正常输出(stdout)和错误输出(stderr),脚本每隔5秒通过print()打印一条带时间戳的进度记录,并在第3次循环时向标准错误流输出一条告警信息:
import os
import sys
import time
from datetime import datetime
def main():
pid = os.getpid()
print(f"[{datetime.now():%Y-%m-%d %H:%M:%S}] 后台任务启动,当前进程 PID={pid}")
counter = 0
while True:
counter += 1
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{now_str}] 正在处理第 {counter} 批数据...")
if counter == 3:
sys.stderr.write(f"[{now_str}] [WARN] 第 {counter} 批数据出现超时重试记录\n")
time.sleep(5)
if __name__ == "__main__":
main()
三、使用 nohup 在后台运行 Python 脚本并保存日志
对于单次执行的数据处理、模型训练或临时巡检脚本,使用nohup配合Shell重定向是最轻量的方案,无需管理员权限即可直接运行。
1. 启动脚本并记录进程 PID
进入/opt/app目录,使用虚拟环境中的Python解释器启动worker.py,同时用echo $!把刚启动的后台进程PID保存到/opt/app/worker.pid文件中:
cd /opt/app
nohup /opt/app/.venv/bin/python3 -u /opt/app/worker.py >> /var/log/app/worker.log 2>&1 &
echo $! > /opt/app/worker.pid
[1] 28451
这条命令中各个部分的作用如下:
nohup:拦截SIGHUP信号,使SSH会话关闭后进程继续运行。-u:强制Python的stdout和stderr完全不使用缓冲,确保print()和报错信息实时写入日志文件(等同于设置环境变量PYTHONUNBUFFERED=1)。>> /var/log/app/worker.log:将标准输出(文件描述符1)以追加模式写入日志文件。如果改用单个>,每次重启脚本都会清空原有日志。2>&1:将标准错误(文件描述符2)重定向到标准输出当前指向的位置,使异常堆栈(Traceback)和普通日志保存在同一个文件中。注意2>&1必须写在>> 日志路径之后。&:将命令放入当前Shell后台执行,立即释放终端输入提示符。echo $! > /opt/app/worker.pid:$!是Shell内置变量,代表最近一个放入后台的进程PID。将其写入文件后,后续停止或检查进程时无需反复用ps筛选。
nohup ... &后,建议按一次回车键,并输入exit正常退出SSH会话,不要直接强行关闭终端软件窗口。部分SSH客户端在窗口被直接关闭时,若后台任务仍持有未重定向的标准输入(stdin),可能引发会话挂起。
2. 验证进程存活与实时查看日志
命令返回PID只代表进程成功创建,如果脚本存在语法错误或缺少依赖包,进程会在启动后几十毫秒内退出。因此启动后必须检查进程是否仍在运行,并查看日志头部输出:
ps -fp $(cat /opt/app/worker.pid)
tail -n 20 -f /var/log/app/worker.log
UID PID PPID C STIME TTY TIME CMD ubuntu 28451 1 0 16:10 ? 00:00:00 /opt/app/.venv/bin/python3 -u /opt/app/worker.py [2026-11-04 16:10:02] 后台任务启动,当前进程 PID=28451 [2026-11-04 16:10:02] 正在处理第 1 批数据... [2026-11-04 16:10:07] 正在处理第 2 批数据... [2026-11-04 16:10:12] 正在处理第 3 批数据... [2026-11-04 16:10:12] [WARN] 第 3 批数据出现超时重试记录
在tail -f持续刷新界面下,按Ctrl + C只会退出tail命令本身,后台的worker.py仍然继续运行。如果当时未保存worker.pid,也可以执行pgrep -af "/opt/app/worker.py"找回对应的PID。
3. 安全停止后台脚本
需要停止脚本时,根据保存的PID文件或pgrep查到的PID,发送SIGTERM(15号信号):
kill -15 $(cat /opt/app/worker.pid)
ps -fp $(cat /opt/app/worker.pid)
UID PID PPID C STIME TTY TIME CMD
只有当脚本卡死在底层C扩展或网络阻塞中、等待10秒以上仍未退出时,才使用kill -9 PID强制杀死进程。
四、在 Python 脚本内使用 logging 模块实现日志自动轮转
依靠Shell的>> /var/log/app/worker.log重定向有一个明显限制:只要后台进程不停止,日志文件就会无限增大;如果直接删除或移动正在被写入的worker.log,Linux内核并不会立即释放磁盘空间(因为进程仍持有该文件的打开句柄),直到重启Python进程才会释放。
如果脚本需要连续运行数周甚至数月,更稳妥的做法是在Python代码内使用标准库的logging.handlers.RotatingFileHandler(按大小切分)或TimedRotatingFileHandler(按天切分),由Python自身管理日志文件的创建与轮转。
修改/opt/app/worker.py,配置单文件上限为10MB、最多保留5个历史备份文件(即日志总占用不超过60MB):
import logging
from logging.handlers import RotatingFileHandler
import os
import time
LOG_FILE = "/var/log/app/worker.log"
def setup_logger():
logger = logging.getLogger("worker")
logger.setLevel(logging.INFO)
handler = RotatingFileHandler(
LOG_FILE,
maxBytes=10 * 1024 * 1024, # 单个文件最大 10MB
backupCount=5, # 最多保留 worker.log.1 至 worker.log.5
encoding="utf-8"
)
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)s] [PID:%(process)d] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def main():
logger = setup_logger()
logger.info("后台服务已启动")
counter = 0
while True:
counter += 1
logger.info("正在处理第 %d 批数据...", counter)
if counter == 3:
logger.warning("第 %d 批数据响应超时,已触发重试", counter)
time.sleep(5)
if __name__ == "__main__":
main()
当脚本自身已经通过RotatingFileHandler将日志写入/var/log/app/worker.log时,如果再用nohup ... >> /var/log/app/worker.log 2>&1 &指向同一个文件,会导致两种写入机制冲突。此时应将未捕获的底层崩溃信息单独指向另一个错误文件(如/var/log/app/worker-crash.log),或者交给下一节的systemd统一管理。
五、长期运行方案:用 systemd 托管 Python 脚本与日志
nohup适合临时任务,但如果Python脚本因未捕获异常崩溃退出,或者Linux服务器重启,nohup启动的进程不会自动恢复。对于需要7×24小时运行的生产脚本,应使用Linux发行版内置的systemd将其注册为系统服务。
1. 创建 systemd 服务单元文件
使用管理员权限创建/etc/systemd/system/python-worker.service文件,将工作目录、运行用户、环境变量PYTHONUNBUFFERED=1以及日志路径写入配置:
[Unit]
Description=Python Background Worker Service
After=network.target
[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=/opt/app
Environment="PYTHONUNBUFFERED=1"
ExecStart=/opt/app/.venv/bin/python3 /opt/app/worker.py
StandardOutput=append:/var/log/app/worker.log
StandardError=append:/var/log/app/worker.log
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
如果worker.py内部已经使用了上一节的RotatingFileHandler写/var/log/app/worker.log,则只需把配置中的StandardOutput和StandardError两行删掉(默认由journald接收底层标准输出),或改为StandardError=append:/var/log/app/worker-crash.log即可。
StandardOutput=append:/path/to/file语法要求systemd版本不低于 240(Ubuntu 20.04+、Debian 10+、AlmaLinux/Rocky Linux 8+ 均默认满足;可通过systemctl --version确认)。在更早的系统(如CentOS 7,systemd 219)中,建议删去这两行并通过journalctl -u python-worker -f查看日志,或在Python脚本内使用RotatingFileHandler。
2. 启动服务、设置开机自启与核验状态
保存配置文件后,依次执行重载配置、设置开机自启并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable --now python-worker.service
sudo systemctl status python-worker.service
● python-worker.service - Python Background Worker Service
Loaded: loaded (/etc/systemd/system/python-worker.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2026-11-04 16:25:10 CST; 6s ago
Main PID: 30112 (python3)
Tasks: 1 (limit: 4656)
Memory: 9.4M
CPU: 42ms
CGroup: /system.slice/python-worker.service
└─30112 /opt/app/.venv/bin/python3 /opt/app/worker.py
后续需要重启或停止脚本时,无需手动查找PID,直接执行sudo systemctl restart python-worker或sudo systemctl stop python-worker即可。
六、常见问题与故障排查
1. 日志文件已生成,但大小一直是 0 字节
原因:启动命令未加-u参数,且脚本中使用print()输出的内容尚未填满4KB~8KB的文件流缓冲区。
解决方法:启动时加上python3 -u,或在环境设置中加入export PYTHONUNBUFFERED=1;如果只想针对单条关键print立即写入,可在代码中写为print("关键状态", flush=True)。
2. 前台手动运行正常,放到后台或 systemd 中却立刻报错退出
原因:最常见的原因有两个:一是使用了默认的系统python3而非虚拟环境解释器,导致报ModuleNotFoundError;二是脚本内使用了相对路径(如open("config.json")),而后台启动时的工作目录不在/opt/app。
解决方法:始终使用/opt/app/.venv/bin/python3完整路径调用解释器;在Python脚本中通过os.path.dirname(os.path.abspath(__file__))定位脚本所在目录并拼接配置文件路径。
3. 日志中的中文字符变成乱码或报 UnicodeEncodeError
原因:非交互式后台环境缺少UTF-8区域设置变量(LANG或LC_ALL为POSIX或C)。
解决方法:在启动前设置export PYTHONIOENCODING=utf-8,或在systemd的[Service]段中添加一行Environment="PYTHONIOENCODING=utf-8"。
七、方案对比与选型总结
在实际运维中,可以根据脚本运行周期、是否需要交互式调试以及是否具备sudo权限,按以下标准选择对应方案:
| 对比维度 | nohup + Shell 重定向 | tmux / screen 会话 | systemd 服务托管 |
|---|---|---|---|
| 适用场景 | 几十分钟到数天的临时数据处理、导出、训练脚本 | 需要随时重连终端观察实时交互界面的调试任务 | 7×24 小时运行的生产采集器、消费队列、定时守护进程 |
| 是否需要 root 权限 | 不需要 | 不需要(需系统已安装对应工具) | 需要(或使用 systemctl --user 配合 loginctl enable-linger) |
| 异常崩溃自动重启 | 不支持 | 不支持 | 支持(Restart=on-failure) |
| 日志保存推荐做法 | python3 -u 配合 >> worker.log 2>&1 |
在会话内配合 tee -a worker.log 或脚本内写文件 |
Python RotatingFileHandler 或 StandardOutput=append: |
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









