我有一个长时间运行的命令,会在标准输出上生成大量输出。例如,我希望能够仅保留最后三天或最后 1 GB(避免在中间切割行),并且如果可能的话,保留不大于 20 MiB 的文件块。每个文件块都用数字后缀或时间戳命名。
就像是:
my-cmd | magic-command --output-file-template=my-cmd-%t \
--keep-bytes=1G \
--keep-time=3d \
--max-chunk-size=20M \
--compress=xz
会写:
my-cmd-2014-09-05T10:04:23Z
当达到20M时,它会压缩它并打开一个新文件,依此类推,过了一会儿它会开始删除最旧的文件。
这样的命令存在吗?
我知道logrotate
它具有管理其他应用程序编写的文件的能力,但我正在寻找更简单的东西,不需要设置 cron 作业、指定规则、挂起进程等。
答案1
您可以通过以下方式获得您想要的一些东西管道日志,它“允许通过响应外部信号的中间体来旋转或清除正在运行的进程的日志”,例如:
spewstuff | pipelog spew.log -p /tmp/spewpipe.pid -x "gzip spew.log.1"
然后您可以从 和 获取 pid /tmp/spewpipe.pid
:
kill -s USR1 $(</tmp/spewpipe.pid)
但你必须用 cron 之类的东西来设置。然而,这有一个问题。注意我gzip spew.log.1
——这是因为该-x
命令是在日志轮换后执行的。因此spew.log.1.gz
,除非您编写一个简短的脚本来执行 gzip 并随后移动文件,并将其用作命令-x
,否则您还会遇到每次覆盖的进一步问题。
完全披露:我写了这个,所以它当然有效完美。 ;) 对于 0.2 版本,我会记住一个压缩选项,或者更好地促进它的东西(其预期目的-x
有些不同,但它会像上面一样工作)。自动翻转也是一个好主意...第一个版本故意是最小的,因为我抵制住了添加不必要功能的诱惑(毕竟为此设置一个 cron 作业并不难)。
请注意,它的目的是文本输出;如果存在潜在的空字节,您应该使用-z
-- 它将零替换为其他内容。这是为了简化实施而做出的权衡。
答案2
丹·伯恩斯坦的多日志显然可以做到这一点 - 或者也许是大部分,同时通过文件描述符提供一个出口!处理器根据您的喜好来弥补差异 - 尽管 20M/1G 大小规格可能需要一些欺骗,因为似乎 16M 是每个日志的外部限制。接下来的大部分内容是从上面的链接中进行的复制+粘贴选择,尽管该链接还详细介绍了其他选项,例如每行时间戳、维护仅包含最新行匹配的其他文件图案和更多。
界面
multilog script
...脚本由任意数量的参数组成。每个参数指定一个操作。对每行输入按顺序执行这些操作。
选择线路
每行都是最初选择的。那个行动...
-pattern
...如果模式与该行匹配,则取消选择该行。那个行动...
+pattern
选择该行,如果图案与行匹配。
...图案是一串星形和非星形。它匹配由所有星号和非星号以相同顺序匹配的任何字符串串联。非明星与自身相匹配。模式末尾之前的星号与不包含模式中下一个字符的任何字符串匹配。模式末尾的星号与任何字符串匹配。
自动轮换日志
如果目录以点或斜杠开头,然后执行操作...
dir
...将每个选定的行附加到名为的日志中目录。如果目录不存在,multilog
创造它。
日志格式如下:
目录是一个包含一定数量旧日志文件的目录,日志文件名为当前的
multilog
,以及其他用于跟踪其操作的文件。每个旧日志文件的名称以@,继续显示文件完成时间的精确时间戳,并以以下代码之一结束:
- .s:此文件已完全处理并安全写入磁盘。
- .u:该文件是在中断时创建的。它可能已被截断。它尚未被处理。
那个行动...
ssize
...设置后续的最大文件大小目录行动。multilog
将决定当前的足够大,如果当前的有尺寸字节。(multilog
如果它在最大文件大小的 2000 字节内看到换行符,也会确定 current 足够大;它会尝试在行边界处完成日志文件。)必须介于 4096 和 16777215 之间。默认最大文件大小为 99999。
在 0.75 及更高版本中:如果multilog
收到ALRM信号,它立即决定当前的足够大,如果当前的是非空的。
(注意:我怀疑如果需要的话zsh
schedule
,可以很容易地说服内置函数ALRM
以指定的时间间隔发送。)
那个行动...
nnum
...设置后续日志文件的数量目录行动。重命名后当前的, 如果multilog
看到编号或者更多旧的日志文件,它会删除时间戳最小的旧日志文件。编号必须至少为 2。日志文件的默认数量为 10。
那个行动...
!processor
...为后续设置处理器目录行动。multilog
会喂食当前的通过处理器并将输出保存为旧日志文件而不是当前的。multilog
还将保存处理器写入描述符 5 的任何输出,并在下一个日志文件上运行处理器时使该输出在描述符 4 上可读。为了可靠性,处理器如果在创建输出时遇到任何问题,则必须退出非零值;multilog
然后将再次运行它。请注意,运行处理器可能会阻止任何程序向 提供输入multilog
。
答案3
这是一个经过修改的 python 脚本,用于执行类似您所要求的操作:
#!/bin/sh
''':'
exec python "$0" "$@"
'''
KEEP = 10
MAX_SIZE = 1024 # bytes
LOG_BASE_NAME = 'log'
from sys import stdin
from subprocess import call
log_num = 0
log_size = 0
log_name = LOG_BASE_NAME + '.' + str(log_num)
log_fh = open(log_name, 'w', 1)
while True:
line = stdin.readline()
if len(line) == 0:
log_fh.close()
call(['gzip', '-f', log_name])
break
log_fh.write(line)
log_size += len(line)
if log_size >= MAX_SIZE:
log_fh.close()
call(['gzip', '-f', log_name])
if log_num < KEEP:
log_num += 1
else:
log_num = 0
log_size = 0
log_name = LOG_BASE_NAME + '.' + str(log_num)
log_fh = open(log_name, 'w', 1)
答案4
在带有 systemd 的 Linux 中,如果你有 root 权限,你可以启动一个专用的日志实例。它需要LogNamespace=
在一个单元文件中
/etc/systemd/[email protected]
:
[Journal]
Storage=volatile
RuntimeMaxUse=20M
/etc/systemd/system/myprogram.service
:
[Unit]
Description=My Test Service
[Service]
ExecStart=/home/myuser/myprogram
LogNamespace=myprogram
User=myuser
_
systemctl daemon-reload
systemctl start myprogram
journalctl --namespace=myprogram --follow
看https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html#Journal%20Namespaces