通过命名管道或 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 单元文件中,我们可以将 StandardOutput
和 StandardError
设置为 syslog
,然后将 SyslogIdentifier
设置为我们可以使用的某个字符串使用 rsyslog,添加时间戳并将最后一行写入日志文件。
不幸的是,我还没有找到让 systemd 区分 stdout 和 stderr 的方法:它们都将被发送到 rsyslog 而没有任何关于哪个是来源的信息。 (我知道 SyslogLevelPrefix
,但这需要来自我们黑盒应用程序的传入日志消息包含 sd-daemon(3)
样式前缀,而这目前不可行。)
详情
由于选项 2 固有的问题,我将进一步讨论选项 1。如果您知道 syslog 方法的解决方法,请分享。
所以,命名管道很棒,但我不知道如何在 systemd 单元文件中实现它们。我想我必须有两个单独的 reader 进程,一个用于 stdout 管道,一个用于 stderr 管道,因此有两个新的单元文件。一些进一步的说明:
- 命名管道应该在编写器应用程序启动之前存在。我可以事先用
mkfifo
创建它们,所以这应该不是问题。没有必要使用 systemd 管理管道的存在,除非它以某种方式使单元文件更简单或更健壮。
- 执行时间戳增加和日志文件写入的 reader 进程应该在 writer 应用程序启动之前启动并 运行,这是
Before/After
和 Requires/Wants
在单元文件中,我相信。
- 整个设备应该在写入器应用程序或 reader 进程重启后仍然存在。例如。如果
cat
在 reader 进程中使用,编写器退出并且 cat
看到 EOF 并退出, reader 进程应该恢复并在编写器进程到来时准备好备份。
- 当相应的 reader 进程关闭时,应用程序写入命名管道的任何日志行都将丢失,但这是可以接受的。
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
我不得不处理一项专有的遗留服务(运行 在 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 单元文件中,我们可以将 StandardOutput
和 StandardError
设置为 syslog
,然后将 SyslogIdentifier
设置为我们可以使用的某个字符串使用 rsyslog,添加时间戳并将最后一行写入日志文件。
不幸的是,我还没有找到让 systemd 区分 stdout 和 stderr 的方法:它们都将被发送到 rsyslog 而没有任何关于哪个是来源的信息。 (我知道 SyslogLevelPrefix
,但这需要来自我们黑盒应用程序的传入日志消息包含 sd-daemon(3)
样式前缀,而这目前不可行。)
详情
由于选项 2 固有的问题,我将进一步讨论选项 1。如果您知道 syslog 方法的解决方法,请分享。
所以,命名管道很棒,但我不知道如何在 systemd 单元文件中实现它们。我想我必须有两个单独的 reader 进程,一个用于 stdout 管道,一个用于 stderr 管道,因此有两个新的单元文件。一些进一步的说明:
- 命名管道应该在编写器应用程序启动之前存在。我可以事先用
mkfifo
创建它们,所以这应该不是问题。没有必要使用 systemd 管理管道的存在,除非它以某种方式使单元文件更简单或更健壮。 - 执行时间戳增加和日志文件写入的 reader 进程应该在 writer 应用程序启动之前启动并 运行,这是
Before/After
和Requires/Wants
在单元文件中,我相信。 - 整个设备应该在写入器应用程序或 reader 进程重启后仍然存在。例如。如果
cat
在 reader 进程中使用,编写器退出并且cat
看到 EOF 并退出, reader 进程应该恢复并在编写器进程到来时准备好备份。 - 当相应的 reader 进程关闭时,应用程序写入命名管道的任何日志行都将丢失,但这是可以接受的。
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