通过命名管道或 rsyslog 添加时间戳到 systemd 管理服务的日志输出

Adding timestamps via named pipes or rsyslog to the log output of a systemd-managed service

我不得不处理一项专有的遗留服务(运行 在 Debian 10 上),它将各种(大致)信息和通知级别的内容记录到标准输出中,以及(大致)警告和错误级别的内容进入标准错误。两者都可以使用 --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

为 stdout 和 stderr 创建具有 mkfifo 的命名管道,并使遗留应用程序将它们用作 --stdout--stderr 目标。然后,设置一个进程来读取这些管道,将每一行管道传输到 ts 命令,这会添加一个很好的时间戳,如下所示:

$ echo foo | ts '[%Y-%m-%d %H.%M.%.S]'
[2021-08-10 16.16.21.506571] foo

这些带时间戳的行随后将被写入日志文件,一个用于 stdout,一个用于 stderr,可能通过管道。

选项 2

在 systemd 单元文件中,我们可以将 StandardOutputStandardError 设置为 syslog,然后将 SyslogIdentifier 设置为我们可以使用的某个字符串使用 rsyslog,添加时间戳并将最后一行写入日志文件。

不幸的是,我还没有找到让 systemd 区分 stdout 和 stderr 的方法:它们都将被发送到 rsyslog 而没有任何关于哪个是来源的信息。 (我知道 SyslogLevelPrefix,但这需要来自我们黑盒应用程序的传入日志消息包含 sd-daemon(3) 样式前缀,而这目前不可行。)

详情

由于选项 2 固有的问题,我将进一步讨论选项 1。如果您知道 syslog 方法的解决方法,请分享。

所以,命名管道很棒,但我不知道如何在 systemd 单元文件中实现它们。我想我必须有两个单独的 reader 进程,一个用于 stdout 管道,一个用于 stderr 管道,因此有两个新的单元文件。一些进一步的说明:

writer 进程具有参数 --stdout /path/to/outfifo --stderr /path/to/errfifo,理论上,reader 进程可以像这样简单:

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 单元文件中执行此操作,同时满足上述可重启性要求。

如果您能帮助构建单元文件,或提出替代方法,我将不胜感激。

journald可以为您添加时间戳等。

一个简单的解决方案是使用 systemd-cat:

ExecStart=/bin/systemd-cat \
  --priority=info \
  --stderr-priority=warning \
  --identifier=thing \
  /usr/local/bin/thing

如果您的 thing 为优先级发出记录器前缀(例如,对于 warning 消息,该行以 <4> 开头),那么您可以使用 --level-prefix 标志手动设置优先级。

其他一些想法包括:

  • 在你的单元中使用 stream logging
  • 正在编写自定义拆分器以读取日志并将它们通过管道传输到具有正确级别的 systemd-cat。您可以 运行 将此作为 thing 与之交谈的服务。
  • 变体:编写自定义解析器并通过 sd_journal_send(3) 将解析后的日志直接发送到 journald。您将使用 systemd 将 thing 的输出通过管道传输到 parser.
  • 使用 systemd 通过 StandardOutput=StandardError=.
  • 将 stdout 和 stderr 写入 a socket unit