如何找到按单位列出的 journalctl 日志大小列表?

如何找到按单位列出的 journalctl 日志大小列表?

我的系统上有一项服务正在发送大量日志并生成大量数据。我不知道是哪项服务。

使用常规日志文件,我可以用来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 上的内存限制。awkjournalctl -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 | uniqsystemctl 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

再次运行你的单行程序,现在速度会很快。这样你应该很容易就能找到你的“垃圾邮件发送者”。

相关内容