自动切割日志

Autorotating logs

当我把我的备份脚本从 bash 翻译到 Raku 的时候,我偷看了一下 /var/log/,震惊地发现有 650MB 的东西我从来没有看过。不出所料,/var/log/journal 是最大的犯罪者。遗憾的是,我必须说 systemd 一贯不太好用。2008 年我就出过糗。我之所以还在继续用它完全是因为 Debian 系统。尽管它没有 xrdp.loglogrotate。在自己的软件中内置旋转和压缩日志的功能有多难呢?

这就是60行代码的事。

sub open-logfile(
    IO() $log-file-path = IO::Path,
    :$flush = False,
    :$max-size = 2**20 * 50,
    :$max-backlog = 7 --> Routine
    ) {
    my $log-channel = Channel.new;
    my $log-promise = start {
        my $log-handle = $log-file-path ?? open $log-file-path, :a !! $*ERR;
        my $log-file-dir = $log-file-path.dirname;
        my $log-file-basename = $log-file-path.basename;

        sub compress-file($path) {
            my $gz = run <gzip -9>, $path;
            warn „could not compress $path“ unless $gz;
        }

        my $log-file-length = $log-file-path ?? $log-file-path.s !! 0;
        sub rotate-log {
            return without $log-file-path;
            note ‚rotating logfile‘;
            $log-handle.close;

            my @siblings = dir($log-file-dir).grep(*.basename.starts-with($log-file-basename));
            for @siblings».Str.sort.skip.head($max-backlog - 1).reverse -> $path {
                my $n = $path.match(/log '.' (\d+)/)[0].Int + 1;
                compress-file($path) if $n == 2;
                my $new-name = $log-file-path ~ '.' ~ ($n > 1 ?? $n ~ ‚.gz‘ !! $n);
                ($n == 2 ?? $path ~ ‚.gz‘ !! $path).IO.rename($new-name);
            }

            $log-file-path.rename($log-file-path ~ '.1');
            $log-handle = open $log-file-path, :a;
            $log-file-length = 0;
        }

        react whenever $log-channel -> Str() $line {
            my $utf8 = ($line ~ "\n").encode;
            $log-file-length += $utf8.bytes;
            rotate-log if $log-file-length > $max-size;
            $log-handle.write: $utf8;
            $log-handle.flush if $flush;
        }
    }

    my $last-message = "";
    my int $dupe-counter = 0;
    sub ($message --> Nil) {
        my $now = now.DateTime;
        my $timestamp = $now.yyyy-mm-dd ~ ' ' ~ $now.hh-mm-ss;

        if $message eq $last-message {
            $dupe-counter++;
        } else {
            if $dupe-counter > 0 {
                $log-channel.send: $timestamp ~ ' Last message repeated ' ~ $dupe-counter ~ ' times.';
            } else {
                $log-channel.send: $timestamp ~ ' ' ~ $message;
            }
            $dupe-counter = 0;
        }

        $last-message = $message;
    } but role :: { method close { $log-channel.close; await $log-promise } }
}

如果没有每行后的 .flush,性能还是不错的。有了它,性能就很差,因为我们无法使用花哨的 linux 标志来打开,因此不会真的那么用力地刷新。

由于旋转日志和压缩它们可能需要一些时间,我把实际写入文件的工作放到了自己的线程中。所以使用变得有点间接,我们必须告诉 logger 完成写入并关闭文件。如果我们不这样做,我们就会丢失数据。我不知道后者是不是一个 bug。

my &log = open-logfile(‚/tmp/test.log‘, :max-size(2**20));

my $lore = ‚Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.‘;

loop {
    log(100.rand > 50 ?? $lore !! 42.rand);
    last if (now - ENTER now) > 60 * 60 * 2;
}

&log.await;

经过深思熟虑,我得出的结论是,将 Perl 6 改名为 Raku 是一个错误。它应该被命名为 Exceedingly Simple。

by gfldex

comments powered by Disqus