进一步阅读

进一步阅读

我有一个基于 Raspberry-Pi 的设备,它从连接到其 GPIO 的开关和传感器以及通过 TTY 串行连接的 Arduino 获取输入。管理这一切的主应用程序是用 Python3 编写的。这是启动 shell 脚本launcher.sh

cd /home/pi/ProjectName
venv/bin/python main.py &

并且这个shell脚本被注册到crontab中以便在启动时自动执行:

$ sudo crontab -e

添加到 crontab 文件:

@reboot sudo su pi /home/pi/ProjectName/launcher.sh >> /home/pi/crontab.log 2>&1

这样,输出到 stdout 或 stderr 的任何内容都会被捕获在日志文件中。

很少,在某些奇怪的情况下,Python 代码会失败,并在日志文件中写入未捕获的错误crontab.log。但如果没有每行添加到日志文件的时间戳,就很难分析问题。只有文字,没有时间信息。此外,每次设备重新启动时都会重置日志文件。

我当然可以从当前日期时间生成一些动态日志文件名,所以这不是主要问题。但是,我不知道如何设置日志记录,以便对日志文件的每个贡献都用时间戳进行补充。

答案1

早在 20 世纪 90 年代,世界就发明了实现这一目标的工具。没有必要在 shell 脚本中重新发明它们,这很糟糕。它们为您提供严格的大小限制、自动轮换、按需轮换、时间戳命名、日志文件以及每行输出开头的时间戳。

multilog行时间戳是、s6-logsvlogd和中的非默认选项tinylog,由命令行选项/参数打开。 cyclog无条件添加时间戳。

由其中几个工具添加的 TAI64N 时间戳是(如https://unix.stackexchange.com/a/294276/5132)不受时区和 DST 变化的影响,易于机器处理,并且可以通过tai64nlocal过滤器简单地转换为人类可读的形式。

调整 Kusalananda 的 cron 表条目:

@reboot sudo -u pi sh -c '~pi/ProjectName/launcher.sh 2>&1 | cyclog ~pi/log/crontab'

或者:

@reboot sudo -u pi sh -c '~pi/ProjectName/launcher.sh 2>&1 |多日志 t ~pi/log/crontab'

有一种较差的做法一些这个,用过tai64n滤器:

@reboot sudo -u pi sh -c '~pi/ProjectName/launcher.sh 2>&1 | tai64n >> ~pi/crontab.log'

但是,这不会执行日志文件的大小上限、自动轮换或时间戳命名。下一步就是缩短不可避免的重新发明(糟糕的是,在 shell 脚本中)logrotate,然后必须解决logrotate固有的不可靠性问题,然后跳到 1990 年代;如上。

另一种跳到 20 世纪 90 年代的方法是从服务经理,它涉及防止多个实例、守护进程上下文和允许系统操作员在运行时停止/启动服务的适当管理机制;而不是使用cron@reboot

一些服务管理器具有服务定义机制,完全避免launcher.sh对包装脚本和其他东西的任何需要sudo。这是一个转换为 nosh 服务包(展示另一种服务定义)的 systemd 单元(展示一种服务定义):

%cat 项目名称.service
[单元]
描述=https://unix.stackexchange.com/a/559920/5132
[服务]
用户=pi
工作目录=/home/pi/%p
ExecStart=venv/bin/python main.py
[安装]
WantedBy=多用户.target
%
%系统控制转换systemd-units ./ProjectName.service
%
%系统控制打印服务脚本./ProjectName
开始:#!/bin/nosh
start:#从./ProjectName.service生成的启动文件
开始:真
停止:#!/bin/nosh
stop:#停止从./ProjectName.service生成的文件
停止:真
运行:#!/bin/nosh
run:#运行./ProjectName.service生成的文件
运行:#https://unix.stackexchange.com/a/559920/5132
运行:envuidgid --补充 -- pi
运行:userenv-fromenv
运行:chdir /home/pi/项目名称
运行:setuidgid --补充 -- pi
运行:venv/bin/python main.py
重新启动:#!/bin/sh
restart:#重新启动./ProjectName.service生成的文件
重新启动:睡眠0.1
restart:exec false # 忽略脚本参数
%

cyclog@ProjectName对于 nosh 服务管理和一般的 daemontools 系列服务管理,还为日志记录(运行cyclogmultilog等)设置一个并行(或类似的)服务包,并告诉服务管理器后者是日志记录为前者服务。

像 nosh per-user-manager、 systemd 和 Upstart 这样的服务管理器甚至为用户提供了配置、运行和管理自己的非特权服务的机制。

进一步阅读

答案2

当设备重新启动时,日志文件不应在您追加内容时重置。如果它确实被重置,那么还有一些其他机制可以清空或删除文件。

要重定向到文件名包含当前时间戳的文件,您可以使用以下方法date生成日志文件名:

$ date +'/home/pi/crontab-%Y%m%d.log'
/home/pi/crontab-20200101.log

在你的 crontab 计划中使用它:

@reboot sudo -u pi /home/pi/ProjectName/launcher.sh >> "$(date +'/home/pi/crontab-\%Y\%m\%d.log')" 2>&1

请注意,每个都%必须转义,因为\%它们在 crontab 文件格式中是特殊的。另外,我选择使用sudu -u pi而不是切换到 root 和然后使用.pisu将调度程序添加到pi用户的 crontab 显然会完全消除对调度程序的需要sudo

如果作业触发时您的设备时钟已同步,则此操作可行。如果不是,您会得到错误的时间戳。对此的补救措施可能是sleep在实际调用之前稍等一下date

至于从 Python 代码中获取带有单独时间戳的输出行:这很可能是 Python 代码本身必须支持的。

一个简单的解决方法是时不时地从另一个 cron 作业向日志文件输出时间戳,也许每 15 分钟左右一次,但这变得有点困难,因为日志文件名称现在取决于 Python 脚本的启动时间。

解决方案可能是有一个指向当前日志文件的符号链接:

@reboot sudo -u pi sh -c 'logfile=$(date +'/home/pi/crontab-\%Y\%m\%d.log'); ln -s -f "$logfile" /home/pi/crontab.log; exec /home/pi/ProjectName/launcher.sh >>"$logfile" 2>&1'

*/15 * * * * date >>/home/pi/crontab.log

这将/home/pi/crontab.log在启动包装器脚本之前创建一个到带时间戳的日志文件的符号链接(不过,您可能希望将重定向移动到实际的包装器脚本中,因为它对于单行 cron 计划来说变得有点笨拙。

第二个作业只是将结果输出date到符号链接当前恰好指向的文件中,每 15 分钟一次。

相关内容