简单回顾一些Linux上日志系统的一些背景,并配置一个rsyslog的日志结构化落db的系统。
背景
push模型:应用程序自己主动推日志到消息系统
pull模型:消息系统监控应用程序的日志,自己去query
syslog 系:syslog->rsyslog or syslog-ng
push 模型,监听/dev/log,收到的日志一般存放在/var/log 下面。配置在/etc/rsyslog.conf
日志格式
有 2 个 rfc
rfc 3164 BSD style
这个更简单一点,最大长度 1024
<35>Oct 12 22:14:15 client_machine su: 'su root' failed for joe on /dev/pts/2
field | explain |
---|---|
<35> | priority = facility + severity |
Oct 12 22:14:15 | 日期 格式只能是这个 |
client_machine | 主机名称、ip 地址 |
su: | tag,最多 32B,以[ : space 结束 |
‘su root’ fail。。。 | 消息正文 |
rfc 5424
2009 年出的,最大长度 2048
<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8
field | explain |
---|---|
<34> | pri |
1 | version |
2003-10-11T22:14:15.003Z | 时间 |
mymachine.example.com | 机器 |
su | app name |
- | 应该是进程 pid |
ID47 | 消息 ID |
- | 结构化数据 |
BOM’su root’ failed for… | 消息正文 |
再来一个有结构化数据的例子
[exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"][examplePriority@32473 class="high"][sigSig ver="1" rsID="1234" ... signature="..."]
格式就是这么个格式 ‘exampleSDID@32473’ 是个 SD-ID,有一些标准化的 ID,比如这里的’sigSig’。
进阶功能
- 接收(顾名思义)
- imudp
- imtcp
- imfile
- 处理
- mmjson rfc3164 的简单 json 格式化
- mmnormalize 用 liblognorm 自定义解析规则,出来是个 json
- 输出(顾名思义)
- ommysql
- omelastic
- omfile
- omkafka
- omprog: 调用自定义的处理程序,用 stdin/stdout 处理
linux 系:systemd-journald
push 模型,监听/run/system/journal/syslog,日志存放/var/log/journal,配置在/etc/systemd/journald.conf
非明文存储,利于搜索。
结构化消息,下划线开头是 journald 自己添加的消息。用 journalctl 读取。例子:
$ journalctl -n1 -o verbose
-- Logs begin at Tue 2022-02-08 19:21:10 CST, end at Mon 2022-03-28 08:05:23 CST. --
Mon 2022-03-28 08:05:23.517238 CST [s=5a9ef8f0b27749e8be1fb46418165a0c;i=1ccd;b=1c4958a7b26240628713f627042e87f8;m=f2d223e7b;t=5db3c119c1b36;x=fc7b3dd29adfe7b8]
PRIORITY=6
_UID=1000
_GID=1000
_CAP_EFFECTIVE=0
_AUDIT_SESSION=3
_AUDIT_LOGINUID=1000
_SYSTEMD_OWNER_UID=1000
_SYSTEMD_UNIT=user@1000.service
_SYSTEMD_SLICE=user-1000.slice
_SYSTEMD_USER_SLICE=-.slice
_BOOT_ID=1c4958a7b26240628713f627042e87f8
_MACHINE_ID=2ce2d4feff754f93ab21e7355f77e4c6
_HOSTNAME=ybaipc
_TRANSPORT=stdout
_COMM=chrome
_EXE=/snap/chromium/1945/usr/lib/chromium-browser/chrome
_SELINUX_CONTEXT=snap.chromium.chromium (enforce)
_SYSTEMD_CGROUP=/user.slice/user-1000.slice/user@1000.service/snap.chromium.chromium.990dd0f1-5415-48d7-b34d-0847ae198b0b.scope
_SYSTEMD_USER_UNIT=snap.chromium.chromium.990dd0f1-5415-48d7-b34d-0847ae198b0b.scope
_SYSTEMD_INVOCATION_ID=f24c28562e634bd98b4dcf7bbe178952
_STREAM_ID=c8846ca93bce46b7903aaa6881b310d9
SYSLOG_IDENTIFIER=chromium_chromium.desktop
_PID=8770
_CMDLINE=/snap/chromium/1945/usr/lib/chromium-browser/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --change-stack-guard-on-fork=enable --shared-files=v8_context_sna>
MESSAGE=[8770:8773:0328/080523.517107:ERROR:ssl_client_socket_impl.cc(995)] handshake failed; returned -1, SSL error code 1, net_error -101
信息是真的很丰富了
进阶功能
journal的进程为system-journald.service
这也可以配置日志转发,但这里叫systemd-journal-upload.service
接收端是systemd-journal-remote.service
还有个可以通过http进行日志记录的systemd-journal-gatewayd.service
但是!:没有过滤!
elk:logstash+filebeat
Pull 模型,日志系统主动拉取消息。
本来是直接部署 logstash 的,但是资源占用堪忧(java 写的),现在直接部署 filebeat(golang 写的)只用作日志搜集。logstash 用作格式转换和 es 写入。
存储一般就到了 elasticsearch 里,展示就用 kibana。
进阶功能
对接 elastic 的生态,尤其是各种日志生成的 visualize+dashboard
以及监控,很大的可玩性
就是有点费机器
Kernel Log
kernel的log是硬编码在内核里的一个ringbuffer的。一般发行版会起一个klogd的进程,不断的读取这个ringbuffer,然后发给syslogd。syslogd把消息再存到磁盘上。
kernel的log一般就是printk出来的。
用户空间和kernel的log相关的系统调用就2个
- syslog(就叫这个,和glibc的syslog同名)
- klogctl
Rsyslog in action
rsyslog兼容通用的配置格式,也就是$ModLoad/$WorkDirectory这种美元符号开头的脚本式格式。但后续增添了Rainer Script(作者叫Rainer)的新型配置格式,还能混着用。我看了下代码,解析这块就是传统的flex+bison写的一个解析器。这个有明确的作用域,可以用花括号包起来,写复杂逻辑推荐用这个新的格式。
小目标
当前的某个应用里有不少流水数据,每次查询的时候需要登陆到机器上手动从日志中grep;难处在程序有几十个实例,分散在多台机器上,手动查找IP并一路cd的操作效率很低。现在希望可以将这些数据格式化的送入数据库中,后续就可以借助丰富的数据库前端或者各种BI(metabase之类)来进行查询。
需要做的一些步骤
- 监控日志文件: 自带模块 imfile
- 日志提取与结构化: mmnormalize,利用liblognorm从消息中提取字段,规整化到json中,供后续步骤使用
- 发送到数据库: ommysql模块可以提供支持,也有ommongo/omelastic等后端
关联模块准备
数据库就MySQL了,默认这个已经足够了。
Centos 7里默认的Rsyslog还是有点老,好在官网提供了最新的仓库,自己添加一个repo就能用(Install rsyslog on RHEL/CENTOS)。
$ yum install rsyslog-mmnormalize rsyslog-mysql
配置文件
默认文件为/etc/rsyslog.conf
,文件中大部分发行版默认配置一条$IncludeConfig /etc/rsyslog.d/*.conf
,因此可以将自己的配置放在该目录下即可。这次的配置可以全部集中到/etc/rsyslog.d/myconf.conf
中。
全局使用的配置尽量放在/etc/rsyslog.conf
,比如启用模块的指令,就需要放在文件的最开始。
Rainer script中,作为key的名字大小写无关。
# /etc/rsyslog.conf
module(load="imfile")
module(load="mmnormalize")
module(load="ommysql")
# /etc/rsyslog.d/myconf.conf
input(
type="imfile"
File="/tmp/mylog.txt"
Tag="mylog:"
RuleSet="rs_me" # 绑定的ruleset
)
# 示例日志
# 2021-12-13 12:12:12.132435,Quantity: 23
template(name="tpl_biz" type="string" option.sql="on"
string="insert into biz_table (created_at, quantity) values (\"%$!gentime%\", \"%$!quantity%\")")
# 这2个template是等价的
# template(name="tpl_biz" type="list") {
# constant(value="insert into biz_table (quantity) values (")
# property(name="$!gentime")
# constant(value=", ")
# property(name="$!quantity")
# constant(value=")")
# }
ruleset(name="rs_me") {
action(type="mmnormalize"
rulebase="/etc/rsyslog.d/me.rulebase"
)
if $!event.tags contains "qty" then { # 这里的tags是从rulebase里来的
action(type="ommysql"
Template="tpl_biz"
server="localhost" serverport="3306" db="Syslog"
uid="aaa" pwd="aaa"
)
} else { # else不要也是可以的,就相当于丢弃了
action(type="omfile"
File="/tmp/me.out"
)
}
}
日志文件监听
数据源需要用input
来指明,tag
属性是必需的。
格式解析:liblognorm
mmnormalize只是简单调用了一下lognormalizer的功能。lognormalizer可以先从shell里调用来确认自己写的规则是不是正确。
举例,现在的rulebase是这样的
# me.rulebase
rule=qty:%gentime:char-sep:,%,Quantity: %quantity:number%
需要这样调用,默认输出是json,-T
输出event.tags。
$ echo '2021-12-13 12:12:12.132435,Quantity: 23' | lognormalizer -r me.rulebase -T
{ "quantity": "23", "gentime": "2021-12-13 12:12:12.132435", "event.tags": [ "qty" ] }
格式解析的模块是mmnormalize
,是绑定的ruleset:rs_me的一部分。
注意: 在centos7上默认的rsyslog版本,如果rule里的key是大写开头(quantity->Quantity),有概率是没有办法在action的template里去到这个值。所以尽量把抽取出来的key按照全部小写命名。
输出到数据库
- 时间格式
- 为了能直接送到db里,时间格式需要调整到MySQL直接识别的格式,也就是
YYYY-MM-DD hh:mm:ss[.fraction]
- 为了能直接送到db里,时间格式需要调整到MySQL直接识别的格式,也就是
- 若可以直接调整代码是最好不过了
- 时间精度
- 用
DATETIME(6)
来定义,可以精确到微妙(10^-6)。这里的6就是fraction。
- 用
需要事先在db中新建一张表
CREATE TABLE `biz_table` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`created_at` datetime(6) DEFAULT NULL,
`quantity` bigint DEFAULT '0',
PRIMARY KEY (`id`),
);
template里option.sql="on"
是必须的,可能是出于编译模板的需要。
排错指南
- rulebase需要事先在shell里验证好
- module的加载放在
/etc/rsyslog.conf
开头 - 发行版默认是用的systemd,写好配置后用
systemctl restart rsyslog
重启,然后systemctl status rsyslog
看下启动的输出。这个输出只有几行,如果出错的地方很多,可以用journalctl -u syslog
调用默认pager(也就是less)看完整的启动日志。
展望
ansible: syslog是有标准的role的,把配置集成到自己的playbook中,能大大提升自动化效率。
开发相关
不找依赖的话,估计现在没有开箱即用的 rfc5424。glibc 的 syslog 还是太弱了点。
glibc 里 syslog 相关的实现
openlog(tag, flag)
closelog()
syslog(level, fmt, …)
代码在glibc/misc/syslog.c
里,不复杂,但是只能 Log 到”/dev/log"里。不过倒是线程安全的。
这个是符合 rfc3164 的
systemd service
如果包装成systemd service,用systemd接管程序的起停,那么自动获得送入journald的功能。
sd-journal
开发时依赖一个 systemd 的库 libsystemd-dev,运行时只需要一个libsystemd.so就行了。
systemd for Developers III (点进去看例子)
只需要几个函数
- sd_journal_send 可自己添加kv
- sd_journal_print 类似 syslog
如何用到实际项目里
需要自己包装一个吗?
push/pull区别在哪儿?
- 开发、运维
- 流量
- 结构化在哪儿做(生产者机器还是集中消费的地方)
想法:日志记录时进行标准化的结构,直接落es库。
落es之前的步骤如何选择?