我的系统上有一项服务正在发送大量日志并生成大量数据。我不知道是哪项服务。
使用常规日志文件,我可以用来ls -lha
打印目录中所有文件的大小/var/log
,这将指向罪魁祸首。但是使用 systemd,所有日志都位于一个“日志”文件中。
如何生成一个包含相应日志大小的单位列表?我想要类似这样的列表;
+----------------------+-------+
| UNIT | SIZE |
+----------------------+-------+
| system.slice | 35.2M |
| cron.service | 17.0M |
| zabbix-agent.service | 8.9M |
| ... | ... |
通过调整此处的答案;https://unix.stackexchange.com/questions/727066/how-to-get-only-the-unit-names-using-systemctl我创建了这个相当慢的方法,顺便说一下,它在单位名称上也有一个 RCE 漏洞;
systemctl show '*' --state=loaded --property=Id --value --no-pager | grep . | sort | uniq | xargs -i sh -c ' journalctl -u {} | wc -c | tr -d "\n" && echo -n " " && echo {}' | sort -n
打破这一点;
systemctl show '*'## show all units
--state=loaded ## Only active units
--property=Id ## Show the name.
--value ## Display the value
--no-pager ## Don't invoke 'more'
| grep . ## Get all results
| sort ## Sort by name
| uniq ## If a name occurs more than once, remove it.
| xargs -i sh -c '## Invoke, for each result, the following, and open (1);
journalctl -u {} ## Run journalctl to print the journal.
| wc -c ## Count the number of bytes in the output.
| tr -d "\n" ## Strip the trailing newline from the output of 'wc'.
&& echo -n " " ## Add a space for 'sort' later.
&& echo {}' ## Add the name of the unit. Close (1)
| sort -n ## Sort the whole output from the commands above by size, asc.
它“有效”,但显然非常非常慢,必须爬过整个日志。如果日志很大,这可能需要很长时间。我想知道是否有更好的方法可以做到这一点?
答案1
您无需轮询systemctl
已加载的单元,然后journalctl
分别在每个单元上运行,而是可以journalctl
只输出每条消息的单元名称,然后对其进行排序和计数。例如
$ journalctl --output=cat --output-fields=UNIT | sort | uniq -c | sort -nr | head
14195 anacron.service
4104 NetworkManager-dispatcher.service
2627 apt-daily.service
2580 motd-news.service
2499 fwupd.service
2415 cups.service
2356 fwupd-refresh.service
1884 cups.path
1876 cups.socket
或者如果你更喜欢 awk
journalctl --output=cat --output-fields=UNIT | gawk '{count[$0]++}
END {
PROCINFO["sorted_in"] = "@val_num_desc";
for(unit in count) printf "%8d %s\n", count[unit], unit
}' | head
foo
请注意,计算单位日记帐分录和计算输出行数之间略有不同journalctl -u foo
:
steeldriver@steeldriver-virtualbox:~$ journalctl -u motd-news | wc -l
2668
steeldriver@steeldriver-virtualbox:~$ journalctl -u motd-news | grep -c -v '^-- Boot'
2580
如果你想积累消息的大小而不是日志条目数然后你就可以通过 JSON 输出获得它,例如
journalctl --output=json --output-fields=UNIT,SYSLOG_IDENTIFIER,MESSAGE | jq -r '
[.UNIT // .SYSLOG_IDENTIFIER, (.MESSAGE|length)] | @tsv' | gawk -F'\t' '{messlen[$1] += $2}
END {
PROCINFO["sorted_in"] = "@val_num_desc";
for (unit in messlen) printf "%8d\t%s\n", messlen[unit], unit
}' | head
或全部jq
journalctl --output=json --output-fields=UNIT,SYSLOG_IDENTIFIER,MESSAGE | jq -r --slurp '
map({name: (.UNIT // .SYSLOG_IDENTIFIER), size: (.MESSAGE|length)}) | group_by(.name) |
map({name: .[0].name, size: map(.size) | add}) | sort_by(.size) | reverse[] | [.size,.name] | @tsv
' | head
当我将其限制为单次启动(即)时,纯jq
版本的运行速度与jq
+版本相当,但当我尝试使用完整日志时,它显然被杀死了 - 这可能只是由于我在测试它的 VM 上的内存限制。awk
journalctl -b --output=json ...
答案2
好吧,我犯了一个错误,在相对较新的安装上运行测试,这并没有完全显示修改后的命令版本运行速度有多快。我更新了答案,将测试结果替换为在较旧的“日志密集”机器上运行的测试结果。
造成缓慢的罪魁祸首是journalctl
命令。
我相信,对于您的目的而言,您不需要日志大小的列表,而是需要指示日志记录最多的内容。
journalctl
因此,为了在减小的输入大小1上进行工作,应该适当减少检查时间窗口的大小。
例如,可以通过添加-S "$(date -d "- 4 hours" +"%Y-%m-%d %T")"
以下journalctl
命令将检查的时间窗口大小减少到过去 4 小时:
journalctl -S "$(date -d "- 4 hours" +"%Y-%m-%d %T")" -u {}
我还擅自对您的脚本进行了进一步的稍微优化(--no-pager
管道中不需要 ,也不需要grep . | sort | uniq
;systemctl show '*' --state=loaded --property=Id --value
只会输出唯一的行和空行,并且空行将被xargs
; 丢弃,我还将其更改tr -d "\n" && echo -n " " && echo {}
为更简单、更易理解的awk "{print \$1,\"{}\"}"
):
systemctl show '*' --state=loaded --property=Id --value |
xargs -i sh -c 'journalctl -S "$(date -d "- 4 hours" +"%Y-%m-%d %T")" -u {} | wc -c | awk "{print \$1,\"{}\"}"' |
sort -n
这将输出与您的命令输出完全相同的输出(即,对于每个活动单元,其大小(journalctl -u <unit>
以字节为单位)后跟单元名称,按大小排序),我认为您会喜欢这个输出,因为您没有抱怨它(它可以优化为仅输出相关的行,但是我理解这只是一个快速找出谁行为不端的工具,所以也许保持这种状态就可以了);这些是我的系统上该命令输出的最后 10 行:
17 uuidd.service
17 uuidd.socket
17 veritysetup.target
17 vgauth.service
69 session-250.scope
266 smartmontools.service
402 user-1000.slice
550 systemd-logind.service
560 motd-news.service
811 ssh.service
1808 phpsessionclean.service
2466 init.scope
2466 -.slice
2524 cron.service
4151 system.slice
在我的系统上,将检查的时间窗口大小减少到过去 4 个小时可将执行时间减少约 8 倍(从约 2 分钟到约 15 秒);我编译了这个丑陋的脚本,它会在运行这两个命令之前删除任何缓存的内容,然后我运行它以进行比较:
#!/usr/bin/env bash
sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time {
systemctl show '*' --state=loaded --property=Id --value |
grep . |
sort |
uniq |
xargs -i sh -c ' journalctl -u {} | wc -c | tr -d "\n" && echo -n " " && echo {}' |
sort -n
} &>/dev/null
sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time {
systemctl show '*' --state=loaded --property=Id --value |
xargs -i sh -c 'journalctl -S "$(date -d "- 4 hours" +"%Y-%m-%d %T")" -u {} | wc -c | awk "{print \$1,\"{}\"}"' |
sort -n
} &>/dev/null
exit 0
以下是结果:
% ./script.sh
real 1m58.843s
user 1m46.212s
sys 0m5.423s
real 0m13.133s
user 0m2.175s
sys 0m2.922s
./script.sh 108.40s user 8.36s system 88% cpu 2:12.38 total
检查的时间窗口可能会根据您的需要进行微调(如果问题非常突出,甚至会进一步减小窗口的大小)。
1. 我仍在寻找更好的方法来解决这个问题。
答案3
确实,您自己的脚本运行良好。如果执行时间过长,您可以先减小日志大小,然后再运行它。
检查尺寸:
journalctl --disk-usage
将大小缩减到 100M
journalctl --vacuum-size=100M
再次运行你的单行程序,现在速度会很快。这样你应该很容易就能找到你的“垃圾邮件发送者”。