如何在Linux中为特定文件设置最大大小限制

3
我有一个应用程序,用户会话期间它将写入特定的日志文件。我想要做的是对日志文件的大小进行限制,不让它超出一定的大小。以下两种情况可能有所帮助:
  1. 任何可以监视日志文件的工具,一旦它达到最大值,就从开头开始截断文件内容,这样应用程序就可以继续在结尾附加内容。
  2. 任何可以在创建文件时指定其最大大小的工具,当文件达到该最大值时,它将不再增长。
我不想要以下做法:
  1. 设置计划任务或脚本以在一定时间间隔(例如1小时)后监视文件大小,然后在那个时间删除其内容。

3
man logrotate 的翻译:日志轮换管理工具的手册。 - Cyrus
@NiallCosgrove 如何限制文件大小,使其不会超过特定的大小? - Anuj Shrivastava
@ Cyrus Logrotate每小时执行文件大小检查,这也是通过cron作业完成的。我想要的是如果文件大小超过分配的大小,立即采取行动的工具。 - Anuj Shrivastava
2
我认为这个问题在这里不太合适。Server Fault可能是一个更好的提问地点,但man logrotate是你最有可能从任何人那里得到的答案。 - Niall Cosgrove
@NiallCosgrove 确定,我会在Serverfault上发布这个问题。我已经使用了logrotate,但在这种情况下,如果我等待一个小时让logrotate检查文件大小,正在写入的应用程序有可能在短时间内写入大量数据,并且可能会填满磁盘直到那时。 - Anuj Shrivastava
你也可以使用cron每分钟启动你的logrotate任务。 - Cyrus
3个回答

2

作为一个shell脚本:

file=file_to_watch
maxsize=98765
truncsice=8765
while : ; do
    inotifywait -e modify "$file"
    filesize=$(du "$file")
    if  [ $filesize -gt $maxsize ] ; then
        tail -c $truncsize "$file" > /tmp/truncatedfile.$$
        mv /tmp/truncatedfile.$$ "$file"
    fi
done

请注意,您可能会遇到一些竞态条件,这可能会导致丢失日志数据。

1

你觉得使用 truncate -s 10M log.txt 怎么样?

更多细节请查看 man truncate


1
“只有”硬编码大小的问题是它很可能会在任何地方截断一行,而不是删除到某一行的结尾...也许可以使用 truncate -s \sed 1,10000 log.txt | wc -c` log.txt`。 - Alexis Wilke

0

几乎没有任何系统提供删除文件的一部分并允许您附加更多数据的过程,即使这是可能的,也不是人们经常做的事情。它可以在内核级别完成,并且非常高效,但我从未见过这样的操作。(即内核将简单地取消链接文件开始处的inode,并在文件的第一个inode中具有字节能力的偏移量--而不是页面能力。)

在Unix系统上,您可以使用mmap()unmap()来实现此目的。因此,当您的应用程序确定文件大小超过某个特定值时,它必须从文件开头读取,确定例如日志的第10,000行的位置,然后将其余部分memmove()到开头。最后,它会截断文件并以追加模式重新打开它。这最后一步非常重要...

// WARNING: code without any error checking
// if multiple processes may run in parallel, make sure to use a lock as well
int fd = open("/var/log/mylog.log", O_RDWR);
ssize_t size = lseek(fd.get(), 0, SEEK_END);
lseek(fd.get(), 0, SEEK_SET);
char * start = (char *)mmap(nullptr,
                            size,
                            PROT_READ | PROT_WRITE, MAP_SHARED,
                            fd,
                            0);
char * end = start + size;
char * l10000 = start; // search line 10,000
for(int line(0); line < 10000; ++line)
{
    for(; l10000 < end && *l10000 != '\n'; ++l10000);
    if(*l10000 == '\n')
    {
        ++l10000; // skip the '\n'
    }
}
ssize_t new_size = end - l10000;
memmove(start, l10000, new_size);
truncate(fd, new_size);
close(fd);

在GitHub上找到的示例(包括此处未找到的所有错误检查):sendmail::dequeue()

重要提示:调用memmove()会很,特别是在一个相当大的日志文件上。

请注意,大多数情况下,当进程打开一个日志文件时,它会保持打开状态,这意味着在其脚下更改文件不会有太大好处。实际上,在这里的mmap()示例中,如果您不确保关闭并重新打开日志文件(代码中未显示),则会创建许多零(\0字符)之间的间隙。

因此,这在代码中是可行的(在C++中,您可以轻松将其编译为C)。但是,如果您只想使用bash,那么logrotate肯定是您最好的选择。但是,默认情况下,至少在Ubuntu上,logrotate每天只运行一次。您可以针对使用您的应用程序或系统的用户更改该设置。

你至少可以通过移动或复制logrotate脚本来每小时运行它:

sudo cp /etc/cron.daily/logrotate /etc/cron.hourly/logrotate

您还可以设置每分钟运行该脚本的CRON文件。要编辑根crontab文件:

sudo crontab -u root -e

然后添加一行:

* * * * *   root    /etc/cron.daily/logrotate

确保测试并查看它是否按预期工作。如果您添加了这样的内容,还可以从中删除/etc/cron.daily/logrotate脚本,以便它不会尝试两次运行(一次在“每日”运行中,一次在每分钟运行中)。

请注意,CRON中存在一个悬而未决的错误,如我向Ubuntu提交的错误报告所示。当过度使用CRON(例如每分钟一次)时,它可能会导致内存问题。

此外,如上面的代码示例中所述,您必须重新打开日志文件。仅进行旋转是没有用的,除非应用程序每次要写入文件时都重新打开日志文件,或者被告知进行旋转(即关闭旧文件并打开新文件)。如果没有旋转操作,应用程序将继续将数据附加到旧文件中,无论它的名称是什么。Unix会记住因为它使用了inode,而不是文件名。 (在MS-Windows下,您将无法重命名文件,而不先关闭对该文件的所有访问...那很烦人!)

在许多情况下,你要么重新启动整个应用程序(因为它太愚蠢而不知道如何重新打开日志),要么向应用程序发送信号以便重新打开日志文件,或者应用程序知道文件已更改,以某种方式...
如果应用程序无法识别,重新启动将是您唯一的选择。如果应用程序具有用户界面,则这可能对用户来说很奇怪。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接