Featured image of post 轮转日志

轮转日志

日志轮转在服务器管理中经常使用,最近学习raku,试着写了一个简单的轮转

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/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

comments powered by Disqus