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

Posted

技术标签:

【中文标题】通过命名管道或 rsyslog 将时间戳添加到 systemd 托管服务的日志输出【英文标题】:Adding timestamps via named pipes or rsyslog to the log output of a systemd-managed service 【发布时间】: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 单元文件中,我们可以将StandardOutputStandardError 设置为syslog,然后将SyslogIdentifier 设置为可以与 rsyslog 一起使用的字符串,添加时间戳并写入最终结果排到日志文件中。

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

详情

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

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

命名管道应该在编写器应用程序启动之前存在。我可以事先用mkfifo 创建它们,所以这应该不是问题。没有必要使用 systemd 管理管道的存在,除非它以某种方式使单元文件更简单或更健壮。 执行时间戳扩充和日志文件写入的读取器进程应该在写入器应用程序启动之前启动并运行,我相信这是单元文件中Before/AfterRequires/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 单元文件中执行此操作,同时满足上述可重启性要求。

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

【问题讨论】:

【参考方案1】:

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

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

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

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

其他一些想法包括:

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

【讨论】:

呵呵,我以前从没听说过systemd-cat。在这里似乎非常有用。我需要做更多的工作,但这里的其他想法已经足够好了。接受并获得赏金。

以上是关于通过命名管道或 rsyslog 将时间戳添加到 systemd 托管服务的日志输出的主要内容,如果未能解决你的问题,请参考以下文章

将日期/时间戳附加到现有文件

通过套接字连接将 https 代理请求传递到命名管道(node.js)

C/S模型之命名管道

如何管道或重定向 curl -v 的输出?

如何使用 Unix(或 Windows)中的(最好是未命名的)管道将一个进程的标准输出发送到多个进程?

将VM的端口号映射到命名管道