轮转日志
日志轮转在服务器管理中经常使用,最近学习raku,试着写了一个简单的轮转
#!/usr/bin/env perl6
# 轮转日志
class SizeParseAction {
method TOP($/) {
make $<number> * $<unit>.made;
}
method unit($/) {
my $rate = do given $/.lc {
when .contains: 'g' {
when .contains: 'i' {10 ** 9}
default {1 +< 30}
}
when .contains: 'm' {
when .contains: 'i' {10 ** 6}
default {1 +< 20}
}
when .contains: 'k' {
when .contains: 'i' {10 ** 3}
default {1 +< 10}
}
when .contains: 'b' {1}
default { say 'wrong unit: ' ~ $_; fail}
};
make $rate
}
}
grammar MemSize {
token TOP { <number><unit> }
token number { \d+[\.\d+]? }
token unit { <[gmkb]>[i?b]? }
}
role SizeToNum {
method Num() {
MemSize.parse(self, :actions(SizeParseAction)).made.Num
}
}
# 轮转
class Rotate {
has Str $.file;
has Int $.days;
has Str $.size;
has Bool $.verbose;
has Num $!bytes = Num;
has Str $.pid = Str;
method !file-exists {
$!file = $!file.IO.absolute;
$!file.IO ~~ :e & :f
}
method check-file-exists {
say 'No Such File!' andthen exit(2) unless self!file-exists
}
method !file-size {
unless $!bytes.defined {
$!bytes = ($!size does SizeToNum).Num;
}
$!file.IO.s >= $!bytes
}
method check-file-size {
say '文件大小不足以开始轮转...' andthen exit(0) unless self!file-size;
}
method remove-last-file {
my $last-file = "{$!file}-{$!days}.gz";
if $last-file.IO ~~ :e & :f {
unlink $last-file.IO;
say 'removed file: ' ~ $last-file if $!verbose;
CATCH {
when X::IO::Unlink {
say 'unlink file failed: ' ~ $last-file andthen say .message andthen exit(3);
}
default {
.say andthen say 'Unknown error when unlink file: ' ~ $last-file andthen exit(4);
}
}
} else {
say "last file not exists. ignore it: $last-file" if $!verbose;
}
}
method move-backup-files {
for $!days - 1 ... 1 {
my $file-name = "{$!file}-{$_}.gz";
my $next-file = "{$!file}-{$_ + 1}.gz";
if $file-name.IO ~~ :e & :f {
say "start move $file-name to $next-file" if $!verbose;
$file-name.IO.move: $next-file;
CATCH {
when X::IO::Move {
say 'move file failed: ' ~ $file-name andthen say .message andthen exit(5);
}
default {
.say andthen say 'Unknown error when move file: ' ~ $file-name andthen exit(6);
}
}
say "move $file-name to $next-file success!" if $!verbose;
} else {
say "$file-name does not exists. ignore it" if $!verbose;
}
}
}
method last-work {
# 将当前正在用的文件,移动为第一个gzip
$!file.IO.move: "{$!file}-1";
say "current file $!file move to {$!file}-1" if $!verbose;
# 然后将这个文件压缩
run 'gzip', "{$!file}-1" andthen
do if $_ == 0 {say "compress file {$!file}-1 to {$!file}-1.gz successed!" if $!verbose}
else {
$*ERR.say: qq:to/END/;
gzip returns $_ , failed... Maybe you should gzip {$!file}-1 to {$!file}-1.gz by yourself.
don't run this file again.
cuz last file will be moved again.
so you will lose last 2 files..
END
run 'touch', $!file andthen exit(7);
};
# 新建一个同名的新文件
if run 'touch', $!file {
say 'create a new file: ' ~ $!file ~ ' Successed!' if $!verbose;
} else {
say 'create a new file: ' ~ $!file ~ ' Failed!' andthen exit(1);
}
True
}
method refresh-log {
if $!pid.defined and $!pid.IO ~~ all(:e :f) {
shell "kill -USR1 `cat {$!pid.IO.absolute}`"
}
}
}
multi MAIN(Str:D() $file, Int() :d(:$days) = 7, Str() :s(:$size) = '10m',Str() :p(:$pid), Bool :v(:$verbose)) {
my $rotate = Rotate.new: :$file, :$days, :$size, :$verbose, :$pid;
$rotate.check-file-exists;
$rotate.check-file-size;
say 'start rotating...' if $verbose;
$rotate.remove-last-file;
$rotate.move-backup-files;
if $rotate.last-work {
say 'Rorate Successed!';
} else {
say 'Rorate Failed';
}
$rotate.refresh-log;
}
sub USAGE() {
say qq:to/END/;
Usage:
{$*PROGRAM-NAME} [-d|--days=Int] [-s|--size=Str] [-p|--pid=Str] [-v|--verbose] <file>
Options:
-d, --days = Int 要保存的天数,默认是7
-s, --size = Str 最低要轮转的大小,默认10M
-p, --pid = Str pid文件地址,需要刷新日志写入,如果不是软件的日志,可以忽略此参数
-v, --verbose = Bool 是否显示详细信息, 默认false
Arguments:
file = Str 要轮转的文件
Examples:
{$*PROGRAM} -d=5 -s=20.5m -p=pid_path ./xxx
ReturnCodes:
1 创建新的文件失败
2 目标文件不存在
3 删除过期的文件失败
4 删除文件未知错误
5 移动备份文件失败
6 移动备份未见未知错误
7 压缩第一个备份文件失败
8 pid文件不存在
END
}
用法, 例如轮转nginx日志: ./rorate.p6 -d=5 -s=20.5m -p=/usr/local/nginx/nginx.pid /usr/local/nginx/logs/access.log