从 docker 容器中到 journald 的结构化日志记录
Structured logging to journald from within a docker container
从 docker 容器中将结构化日志写入 journald 的最佳方法是什么?
例如,我有一个使用 sd_journal_send 编写的应用程序
我没有更改应用程序,而是尝试通过
-v /var/log/systemd/journal:/var/log/systemd/journal
它适用于我的 Ubuntu 16.04 桌面,但不适用于运行该应用程序的 CoreOS 实例(使用 Ubuntu 16.04 基础映像)。我不太明白为什么。也许有更好的方式发送给期刊?
docker journald 输出日志选项有什么限制?它似乎不支持应用程序编写的不仅仅是消息字段。
--
所以我发现我需要 -v /dev/log:/dev/log
但是还有一个问题就是没有关联到启动docker容器的服务文件。手动添加 UNIT: servicename.service 没有解决。因此,在查看和传送服务日志时,它与 exe 相关联,但与容器或服务无关。谁遇到过这些问题,你是如何解决的?
--
好的,让我扩展一下。
C 程序可以像这样写入 systemd 日志:
#include <systemd/sd-journal.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
sd_journal_send("MESSAGE=Hello World!",
"MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555",
"PRIORITY=5",
"HOME=%s", getenv("HOME"),
"TERM=%s", getenv("TERM"),
"PAGE_SIZE=%li", sysconf(_SC_PAGESIZE),
"N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
NULL);
return 0;
}
这会写入日志并添加自定义字段,如 HOME、TERM、PAGE_SIZE 等。当我使用 journalbeat 将它们发送到 ELK 堆栈时,这些字段最终会很好地出现在 elasticsearch 中,我可以搜索他们直接。
但是,docker 似乎只是简单地获取应用程序的标准输出,并将其提供给 journald,其中仅包含它自己添加的几个字段。例如CONTAINER_ID。
在 docker 容器中使用这样的程序,然后 运行 从服务文件中使用它们会产生一个小问题。
1) 我必须通过一些目录和设备文件才能使其写入 sd_journal_send。
2) 如果您从 systemd .service 文件启动容器并期望使用 journalctl -u servicename 并查看消息,则看不到这些日志消息,因为它们以不同的路径进入日志并且不无法与 运行 他们的服务相关联。
3) 您可以使用 docker 的 journald 日志记录驱动程序添加一些任意 fields/tags,它们是固定的,一次性添加将出现在每条发送的消息中并且不会改变。它们不是我从上面的 C 代码中想要的动态字段。
本质上,journald 日志驱动程序在我的情况下是不够的。
关于如何 link 服务名称以便 journalctl -u 显示来自 sd_journal_send 的日志消息的任何建议?因为那会解决它。
--
我找到了解决方案。我会把答案放在下面,以防其他人对我的解决方法感兴趣。
最终的解决方案非常简单。
我改用纯 json 来写我的消息。所以 journalctl -u 现在可以工作并显示 MESSAGE 字段现在包含 json 数据。
然后我使用 journalbeat 将其发送到 logstash。
我给 logstash.conf 添加了:
filter {
json {
source => "message"
}
}
这样做是将 json 数据从消息字段扩展到顶层的单独字段,然后再将它们发送到 elasticsearch。
有关 logstash 的 json 过滤器的详细信息,请参见 here
您需要安装 journald
侦听的套接字。在 ubuntu 的情况下,它是 /run/systemd/journal/socket
。映射这个内幕你 docker 容器,它会工作正常
想出在您的示例代码中使用 strace 的方法
sendmsg(3, {msg_name(29)={sa_family=AF_LOCAL, sun_path="/run/systemd/journal/socket"},
msg_iov(23)=[{"CODE_FILE=test.c", 16}, {"\n", 1}, {"CODE_LINE=13", 12}, {"\n", 1}, {"CODE_FUNC=main", 14}, {"\n", 1},
{"MESSAGE=Hello World!", 20}, {"\n", 1}, {"MESSAGE_ID=52fb62f99e2c49d89cfbf"..., 43}, {"\n", 1}, {"PRIORITY=5", 10}, {"\n", 1},
{"HOME=/home/vagrant", 18}, {"\n", 1}, {"TERM=xterm-256color", 19}, {"\n", 1}, {"PAGE_SIZE=4096", 14}, {"\n", 1},
{"N_CPUS=1", 8}, {"\n", 1}, {"SYSLOG_IDENTIFIER=", 18}, {"a.out", 5}, {"\n", 1}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 208
并在 ubuntu docker 容器中使用以下
进行测试
docker run -v /run/systemd/journal/socket:/run/systemd/journal/socket -v $PWD:/jd -it -w /jd ubuntu:16.04 ./a.out
然后我在 journalctl -f
中获得了一个条目(在主机上)
Aug 15 21:40:33 vagrant a.out[11263]: Hello World!
从 docker 容器中将结构化日志写入 journald 的最佳方法是什么?
例如,我有一个使用 sd_journal_send 编写的应用程序 我没有更改应用程序,而是尝试通过
-v /var/log/systemd/journal:/var/log/systemd/journal
它适用于我的 Ubuntu 16.04 桌面,但不适用于运行该应用程序的 CoreOS 实例(使用 Ubuntu 16.04 基础映像)。我不太明白为什么。也许有更好的方式发送给期刊?
docker journald 输出日志选项有什么限制?它似乎不支持应用程序编写的不仅仅是消息字段。
--
所以我发现我需要 -v /dev/log:/dev/log
但是还有一个问题就是没有关联到启动docker容器的服务文件。手动添加 UNIT: servicename.service 没有解决。因此,在查看和传送服务日志时,它与 exe 相关联,但与容器或服务无关。谁遇到过这些问题,你是如何解决的?
-- 好的,让我扩展一下。
C 程序可以像这样写入 systemd 日志:
#include <systemd/sd-journal.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
sd_journal_send("MESSAGE=Hello World!",
"MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555",
"PRIORITY=5",
"HOME=%s", getenv("HOME"),
"TERM=%s", getenv("TERM"),
"PAGE_SIZE=%li", sysconf(_SC_PAGESIZE),
"N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
NULL);
return 0;
}
这会写入日志并添加自定义字段,如 HOME、TERM、PAGE_SIZE 等。当我使用 journalbeat 将它们发送到 ELK 堆栈时,这些字段最终会很好地出现在 elasticsearch 中,我可以搜索他们直接。
但是,docker 似乎只是简单地获取应用程序的标准输出,并将其提供给 journald,其中仅包含它自己添加的几个字段。例如CONTAINER_ID。
在 docker 容器中使用这样的程序,然后 运行 从服务文件中使用它们会产生一个小问题。
1) 我必须通过一些目录和设备文件才能使其写入 sd_journal_send。
2) 如果您从 systemd .service 文件启动容器并期望使用 journalctl -u servicename 并查看消息,则看不到这些日志消息,因为它们以不同的路径进入日志并且不无法与 运行 他们的服务相关联。
3) 您可以使用 docker 的 journald 日志记录驱动程序添加一些任意 fields/tags,它们是固定的,一次性添加将出现在每条发送的消息中并且不会改变。它们不是我从上面的 C 代码中想要的动态字段。
本质上,journald 日志驱动程序在我的情况下是不够的。
关于如何 link 服务名称以便 journalctl -u 显示来自 sd_journal_send 的日志消息的任何建议?因为那会解决它。
-- 我找到了解决方案。我会把答案放在下面,以防其他人对我的解决方法感兴趣。
最终的解决方案非常简单。
我改用纯 json 来写我的消息。所以 journalctl -u 现在可以工作并显示 MESSAGE 字段现在包含 json 数据。
然后我使用 journalbeat 将其发送到 logstash。
我给 logstash.conf 添加了:
filter {
json {
source => "message"
}
}
这样做是将 json 数据从消息字段扩展到顶层的单独字段,然后再将它们发送到 elasticsearch。
有关 logstash 的 json 过滤器的详细信息,请参见 here
您需要安装 journald
侦听的套接字。在 ubuntu 的情况下,它是 /run/systemd/journal/socket
。映射这个内幕你 docker 容器,它会工作正常
想出在您的示例代码中使用 strace 的方法
sendmsg(3, {msg_name(29)={sa_family=AF_LOCAL, sun_path="/run/systemd/journal/socket"},
msg_iov(23)=[{"CODE_FILE=test.c", 16}, {"\n", 1}, {"CODE_LINE=13", 12}, {"\n", 1}, {"CODE_FUNC=main", 14}, {"\n", 1},
{"MESSAGE=Hello World!", 20}, {"\n", 1}, {"MESSAGE_ID=52fb62f99e2c49d89cfbf"..., 43}, {"\n", 1}, {"PRIORITY=5", 10}, {"\n", 1},
{"HOME=/home/vagrant", 18}, {"\n", 1}, {"TERM=xterm-256color", 19}, {"\n", 1}, {"PAGE_SIZE=4096", 14}, {"\n", 1},
{"N_CPUS=1", 8}, {"\n", 1}, {"SYSLOG_IDENTIFIER=", 18}, {"a.out", 5}, {"\n", 1}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 208
并在 ubuntu docker 容器中使用以下
进行测试docker run -v /run/systemd/journal/socket:/run/systemd/journal/socket -v $PWD:/jd -it -w /jd ubuntu:16.04 ./a.out
然后我在 journalctl -f
中获得了一个条目(在主机上)
Aug 15 21:40:33 vagrant a.out[11263]: Hello World!