【发布时间】:2021-08-10 14:17:53
【问题描述】:
我不得不处理一个专有的遗留服务(在 Debian 10 上运行),它将各种(大致)INFO 和 NOTICE 级别的东西记录到 stdout,以及(大致)WARNING 和 ERROR 级别的东西记录到 stderr。两者都可以使用--stdout <path> 和--stderr <path> 命令行参数写入文件。我们这个守护进程的 systemd 单元文件看起来像这样(精简到最基本的部分):
[Unit]
Description=Some legacy proprietary thing
After=network.target
[Service]
ExecStart=/usr/local/bin/thing \
--stdout /var/log/thing/stdout.log \
--stderr /var/log/thing/stderr.log
[Install]
WantedBy=multi-user.target
问题是写入 stdout.log 和 stderr.log 的行不包含我们现在需要的时间戳。该应用程序对我们来说是一个黑盒子,修改其日志输出以包含时间戳是不可行的。
在我的研究中,我遇到了两种可能适用于此的选项。如果你还有其他人,我很想听听他们的意见。
选项 1
使用mkfifo 为stdout 和stderr 创建命名管道,并使遗留应用程序将它们用作--stdout 和--stderr 目标。然后,设置一个进程来读取这些管道,将每一行传送到 ts 命令,这会添加一个很好的时间戳,如下所示:
$ echo foo | ts '[%Y-%m-%d %H.%M.%.S]'
[2021-08-10 16.16.21.506571] foo
这些带时间戳的行随后将被写入日志文件,一个用于 stdout,一个用于 stderr,可能通过管道。
选项 2
在 systemd 单元文件中,我们可以将StandardOutput 和StandardError 设置为syslog,然后将SyslogIdentifier 设置为可以与 rsyslog 一起使用的字符串,添加时间戳并写入最终结果排到日志文件中。
不幸的是,我还没有找到一种方法来让 systemd 区分 stdout 和 stderr:它们都只会被发送到 rsyslog,而没有任何关于哪个是来源的信息。 (我知道SyslogLevelPrefix,但这要求来自我们黑盒应用程序的传入日志消息包含sd-daemon(3) 样式前缀,目前这是不可行的。)
详情
由于选项 2 固有的问题,我将进一步讨论选项 1。如果您知道 syslog 方法的解决方法,请务必分享。
所以,命名管道很棒,但我不知道如何在 systemd 单元文件中实现它们。我想我必须有两个单独的读取器进程,一个用于 stdout 管道,一个用于 stderr 管道,因此需要两个新的单元文件。一些进一步的说明:
- 命名管道应该在编写器应用程序启动之前存在。我可以事先用
mkfifo创建它们,所以这应该不是问题。没有必要使用 systemd 管理管道的存在,除非它以某种方式使单元文件更简单或更健壮。 - 执行时间戳扩充和日志文件写入的读取器进程应该在写入器应用程序启动之前启动并运行,我相信这是单元文件中
Before/After和Requires/Wants的工作。 - 整个设备应该能够在写入器应用程序或读取器进程的重新启动后继续存在。例如。如果
cat用于读取器进程,写入器退出,cat看到 EOF 并退出,读取器进程应该恢复并在写入器进程恢复时准备好。 - 当相应读取器进程关闭时,应用程序写入命名管道的任何日志行都将丢失,但这是可以接受的。
写入器进程具有参数--stdout /path/to/outfifo --stderr /path/to/errfifo,读取器进程理论上可以像这样简单:
cat /path/to/outfifo | ts '[%Y-%m-%d %H.%M.%.S]' > /var/log/thing/stdout_ts.log
cat /path/to/errfifo | ts '[%Y-%m-%d %H.%M.%.S]' > /var/log/thing/stderr_ts.log
将logrotate 规则与copytruncate 一起添加到该规则之上,我们就完美了!除了我不知道如何在 systemd 单元文件中执行此操作,同时满足上述可重启性要求。
如果您可以帮助构建单元文件,或提出替代方法,我将不胜感激。
【问题讨论】:
标签: logging systemd named-pipes mkfifo