我有一个作为 systemd 服务运行的 shell 脚本,我想记录消息具有细粒度的优先级进入该服务的 systemd 日志。
当我使用 时logger(1)
,journald 仅记录一些消息并丢弃其余消息。哪条消息最终被记录到服务日志中似乎是完全随机的;有时只记录一两条消息,有时根本不记录任何消息。
起初我以为这是启动顺序/依赖性问题,但情况似乎并非如此,因为所有消息确实出现在系统日志(即journalctl --system
)中,但不是服务日志(即journalctl -u SERVICE.service
)中。我也尝试过systemd-cat
,但不幸的是它的行为很相似。
基于脚本的服务将具有优先级的消息记录到其自己的 systemd 日志中的正确方法是什么?
答案1
systemd 所面临的问题logger
与其自己的systemd-notify
工具所面临的问题相同。该协议是基于数据报的异步协议,工具只完成一项工作。调用者分叉一个进程来运行该工具;它发出一个数据报并忘记它;并且进程退出。
systemd
日志记录和就绪通知协议的服务器进程想要知道发送者属于哪个服务,前者是为了将正确的服务名称字段添加到日志条目,后者是为了知道正在谈论什么服务。他们从 Linux 获取数据报发送者的进程 ID,然后进入进程表查找该进程属于哪个控制组,从而查找该进程属于哪个服务。
如果发送进程已完成其工作并立即退出,则这不起作用(受竞争条件影响)。该进程不再存在于进程表中。
systemd-notify
通知失败;logger
信息不会被标记为属于相关服务。切换到流协议(例如使用logger
's--tcp
选项)无法解决此问题除非日志记录协议本身是还进行了更改,以便客户端在关闭流并退出之前等待服务器的响应,但事实并非如此。 RFC 5426 没有发送回客户端的服务器确认。
因此,虽然日志信息在日志中,但它并没有标记服务名称,并且当您通过服务名称查询时也不会被拉出。 (顺便说一句,这些日志并不像您想象的那样是单独的日志。
journalctl
只是将过滤器应用于一个大日志。
-u
是一个过滤器。)
这是一个长期存在且广为人知的错误。
人们systemd
形容这是Linux的一个缺陷。它没有可用于封装和跟踪流程集的适当作业对象;它的数据报套接字机制也不AF_LOCAL
传输此类信息。如果确实如此,systemd
则可以将所有服务进程放入一个作业中,并且其日志记录和就绪通知服务器可以在收到数据报时提取客户端作业信息,即使客户端进程已经退出。
有一种特定于 的特殊协议system-journald
,某些版本logger
甚至可以使用该协议。否,_SYSTEMD_UNIT
是服务器端设置的“可信字段”,客户端尝试设置它会被忽略;这也是一个基于数据报的异步协议,无需确认。它有完全相同的问题。
为了可靠地用正确的服务标记日志条目,写入标准错误。这是长期存在的,并且可以更可靠地连接到服务器端的服务单元名称。是的,您不能指定旧设施和优先级;这就是你必须做出的权衡。
进一步阅读
- 乔纳森·德博因·波拉德 (2015)。 ”提取客户端凭据时使用同步协议”Unix 守护进程的就绪协议问题。常见答案。
- 乔纳森·德博因·波拉德 (2016)。Linux 控制组不是工作。常见答案。
- https://unix.stackexchange.com/a/383575/5132
- 达维德·利马·道姆 (2017-02-23)。 Journalctl 未能显示来自单元的日志。红帽错误#1426152。
- https://unix.stackexchange.com/a/294206/5132
答案2
通过@JdeBP非常有用的答案,我能够找到一种方法让我的消息出现在正确的服务日志中:由于问题源于 和 之间的竞争条件journald
和 收获logger
,你可以有一个长期存在的中间数据报中继对于每个脚本/服务。这样,journald
总会找到发送进程。
socat
如果尚未启动,以下函数将启动中继,然后设置logger
将消息发送到中继套接字而不是默认值。当父脚本退出时,中继会自动终止并删除其套接字。
# Usage: builtin_logger TAG SEVERITY MESSAGE
builtin_logger() {
if [[ "${SHELL_SERVICE_LOG_SOCK-}" == "" ]]; then
declare -g SHELL_SERVICE_LOG_SOCK
SHELL_SERVICE_LOG_SOCK="/tmp/service-log.$$"
sh -c "socat UNIX-RECV:'$SHELL_SERVICE_LOG_SOCK' UNIX-SENDTO:'/dev/log' &
SOCAT_PID=\$!
trap \"if [ -e '$SHELL_SERVICE_LOG_SOCK' ]; then rm '$SHELL_SERVICE_LOG_SOCK'; kill \$SOCAT_PID; fi\" EXIT INT TERM
tail --pid=$$ -f /dev/null" &
sleep 0.1 # waiting for socat to run (TODO inotify)
fi
logger -u "$SHELL_SERVICE_LOG_SOCK" -t "$1" -p user."$2" "$3"
}