我有一个基于 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
从守护进程工具,或布鲁斯·冈特的multilog
从daemontools-encore,或者 Adam Sampsonmultilog
的弗里特 - 洛朗·贝尔科特
s6-log
从s6 - 格里特·佩普的
svlogd
从运行 - 韦恩·马歇尔的
tinylog
从罪犯 - 我的
cyclog
从开胃菜
multilog
行时间戳是、s6-log
、svlogd
和中的非默认选项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 系列服务管理,还为日志记录(运行cyclog
、multilog
等)设置一个并行(或类似的)服务包,并告诉服务管理器后者是日志记录为前者服务。
像 nosh per-user-manager
、 systemd 和 Upstart 这样的服务管理器甚至为用户提供了配置、运行和管理自己的非特权服务的机制。
进一步阅读
- 乔纳森·德博因·波拉德 (2015)。 ”记录”。守护进程工具家族。常见答案。
- 乔纳森·德博因·波拉德 (2016)。在本世纪不要使用 logrotate 或 newsyslog。。常见答案。
- 乔纳森·德博因·波拉德 (2016)。 ”日志后处理“。 小吃指南。软件。
- https://unix.stackexchange.com/a/392924/5132
- https://unix.stackexchange.com/a/326166/5132
- https://unix.stackexchange.com/a/326166/5132
答案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 和然后使用.pi
su
将调度程序添加到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 分钟一次。