root/lang/perl/Parse-Syslog-Period/trunk/lib/Parse/Syslog/Period/Parser.pm @ 14835

Revision 14835, 9.2 kB (checked in by lamanotrama, 5 years ago)

a bit modify

Line 
1package Parse::Syslog::Period::Parser;
2
3use strict;
4use warnings;
5use Carp qw/croak/;
6use base qw/ Class::Data::Inheritable Class::Accessor /;
7use DateTime;
8use Tie::IxHash;
9use Path::Class qw();
10use UNIVERSAL::require;
11use Data::Dumper;
12
13our $VERSION = '0.01';
14
15__PACKAGE__->mk_classdata( parse_delay => 10 );
16
17__PACKAGE__->mk_ro_accessors(qw/
18    file
19    from
20    to
21    rotate
22    rotate_at
23    rotate_statefile
24    patterned_file
25    logfiles
26/);
27
28
29tie( my %macro_map, 'Tie::IxHash' );
30%macro_map = (
31    '$YEAR'  => { pattern => '%Y', rotate => 'daily'  },
32    '$MONTH' => { pattern => '%m', rotate => 'monthly' }, 
33    '$WEEK'  => { pattern => '%W', rotate => 'weekly' },
34    '$DAY'   => { pattern => '%d', rotate => 'daily'  },
35    '$HOUR'  => { pattern => '%H', rotate => 'hourly' },
36);
37
38my %increment_map = (
39    yearly  => { years => 1 },
40    monthly => { months=> 1 },
41    weekly  => { weeks => 1 },
42    daily   => { days  => 1 },
43    hourly  => { hours => 1 },
44);
45
46sub new {
47    my $class = shift;
48    my $self = bless $_[0], $class;
49    $self->_init;
50    return $self;
51}
52
53sub _init {
54    my $self = shift;
55    my $class = ref $self;
56    $self->reset;
57
58    $self->{parse_module}->require or croak $@;
59
60    $self->from or croak "required 'from'";
61    $self->from->set_time_zone('local');
62    DateTime->compare( $self->from, DateTime->now ) == 1
63        and croak "'from' must be past from now";
64    $self->{from_time} = $self->from->epoch;
65
66    if ( $self->to ) {
67        $self->to->set_time_zone( 'local' );
68        DateTime->compare( $self->from, $self->to ) == 1
69            and croak "must be future 'to' from 'from'";
70        $self->{to_time}    = $self->to->epoch;
71        $self->{delayed_to} = $self->to->clone->add( seconds => $class->parse_delay );
72        $self->{delayed_to_time} = $self->{delayed_to}->epoch;
73    }
74
75    $self->rotate eq 'macro'? $self->set_logfiles_by_macro
76                            : $self->set_logfiles_by_legacy;
77    return $self;
78}
79
80sub reset {
81    my $self = shift;
82    $self->{sp}         = undef;
83    $self->{exit_flag}  = 0;
84    $self->{target_idx} = 0;
85    return $self;
86}
87   
88sub set_logfiles_by_macro {
89    my $self = shift;
90
91    my $from = $self->from;
92    my $to   = $self->{delayed_to} ||= DateTime->now->set_time_zone( 'local' );
93    $self->{logfiles} = [];
94    $self->{rotate} = undef;
95    $self->{patterned_file} = $self->file;
96
97    for my $macro ( keys %macro_map ) {
98        my ($pattern, $rotate) = @{ $macro_map{$macro} }{qw/ pattern rotate /};
99        $self->{patterned_file} =~ s/\Q$macro\E/$pattern/g
100            and $self->{rotate} = $rotate;
101    }
102
103    if ( my $rotate = $self->rotate ) {
104        my $increment = $increment_map{$rotate};
105        ( my $truncate_to = (keys %$increment)[0] ) =~ s/s$//;
106        my $rotated_on = $from->clone->truncate( to => $truncate_to );
107
108        while ( DateTime->compare( $rotated_on, $to ) != 1 ) {
109            my $logfile = {
110                name => $rotated_on->strftime( $self->patterned_file ),
111                year => $rotated_on->year,
112            };
113            push @{ $self->logfiles }, $logfile;
114            $rotated_on->add( $increment );
115        }
116
117    } else {
118        $self->{logfiles} = [
119            {   name => $self->patterned_file,
120                year => $from->year,
121            },
122        ];
123   }
124
125    return $self;
126}
127
128sub set_logfiles_by_legacy {
129    my $self = shift;
130
131    my $from = $self->from;
132    my $to   = $self->{delayed_to} ||= DateTime->now->set_time_zone( 'local' );
133    $self->{logfiles} = [];
134
135    my $rotate = $self->rotate
136        or croak "required rotate";
137    ( $rotate =~  /^(monthly|weekly|daily)$/ )
138        or croak "illegal rotate type '$rotate'";
139
140    my $at = $self->rotate_at
141        or croak "required rotate_at(hour and minute)";
142    my ($rotate_hour, $rotate_minute) = @$at{qw/ hour minute /};
143    ( $rotate_hour and $rotate_minute )
144        or croak "rotate_at must contain hour and minute";
145
146    my ($unit_type) = %{ $increment_map{$rotate} };
147
148    my $last_rotated_date = $self->_parse_rotatestatus(
149        $self->file,
150        $self->rotate_statefile,
151    );
152
153    if ( !$last_rotated_date ) {
154        $self->{logfiles} = [
155            {   name => $self->file,
156                year => DateTime->now->set_time_zone( 'local' )->year,
157            },
158        ];
159        return;
160    }
161
162    my $last_rotated_on = DateTime->new(
163        %{ $last_rotated_date },
164        hour      => $rotate_hour,
165        minute    => $rotate_minute,
166        time_zone => 'local',
167    );
168
169    if ( DateTime->compare( $from, $last_rotated_on ) == 1 ) {
170        $self->{logfiles} = [
171            { name => $self->file,
172              year => $last_rotated_on->year,
173            },
174        ];
175        return;
176    }
177
178    my $from_dur = $self->_dur_by_unittype( $last_rotated_on, $from, $unit_type );
179    my $from_suffix = $from_dur + 1;
180
181    my $to_suffix;
182    if ( DateTime->compare( $to, $last_rotated_on ) == 1) {
183        $to_suffix = 0;
184    } else {
185        my $to_dur = $self->_dur_by_unittype( $last_rotated_on, $to, $unit_type );
186        $to_suffix = $to_dur + 1;
187    }
188
189    my $rotated_on = $last_rotated_on->clone->subtract( $unit_type => $from_dur + 1 );
190
191    for my $num ( reverse $to_suffix .. $from_suffix ) {
192        my $name = ( $num == 0 )? $self->file : sprintf( "%s.%d", $self->file, $num );
193        my $logfile = {
194            name => $name,
195            year => $rotated_on->year,
196        };
197        push @{ $self->logfiles }, $logfile;
198        $rotated_on->add( $unit_type => 1 );
199    }       
200}
201
202sub _dur_by_unittype {
203    my ( $self, $dt1, $dt2, $type ) = @_;
204    return $type eq 'days'  ? $dt1->delta_days( $dt2 )->in_units( $type )
205         : $type eq 'weeks' ? $dt1->delta_days( $dt2 )->in_units( $type )
206         : $type eq 'months'? $dt1->delta_md( $dt2 )->in_units( $type )
207         :                    croak;
208}
209
210sub _parse_rotatestatus {
211    my ( $self, $file, $statefile ) = @_;
212    $statefile ||= '/var/lib/logrotate.status';
213    -f $statefile
214        or croak "can't find logrotate statefile. please set rotate_statefile";
215    my $state_h = Path::Class::file( $statefile )->open( 'r' ) or croak $!;
216    my ( $year, $month, $day );
217
218    while ( my $line = <$state_h> ) {
219        chomp $line;
220        my ( $name, $status ) = $line =~ /^"(.*?)"[ ](.*)$/mxs;
221        ( $name and $status ) or next;
222        $name eq $file or next;
223        ( $year, $month, $day ) = split "-", $status;
224        ( $year and $month and $day )
225            or croak "parsing $statefile failed";
226        last;
227    }
228
229    close $state_h or croak $!;
230       
231    return {
232        year   => $year,
233        month  => $month,
234        day    => $day,
235    };
236}
237
238sub next {
239    my $self = shift;
240    return if $self->{exit_flag};
241
242    while ( my $sp = $self->{sp} ||= $self->get_next_sp ) {
243        my $sl = $sp->next or do { undef $self->{sp}; next; };
244
245        ## parse : from_time <= timestamp < delayed_to_time
246        ## return: from_time <= timestamp < to_time
247        next if $sl->{timestamp} < $self->{from_time};
248        return $sl unless $self->{to_time};
249
250        if ( $self->{delayed_to_time} <= $sl->{timestamp} ) {
251            $self->{exit_flag} = 1;
252            return;
253        }
254     
255        return $sl if $sl->{timestamp} < $self->{to_time};
256    }
257
258    return;
259}
260
261sub get_next_sp {
262    my $self = shift;
263
264    while ( my $logfile = $self->logfiles->[ $self->{target_idx}++ ] ) {
265        my ($name, $year) = @$logfile{qw/ name year /};
266        next unless -r $name;
267
268        return $self->{parse_module}->new(
269            $name,
270            year => $year,
271            %{ $self->{for_parse_module} },
272        );
273    }
274
275    return;
276}
277
278
2791;
280
281
282=head1 NAME
283
284Parse::Syslog::Period::Parser - The great new Parse::Syslog::Period::Parser!
285
286=head1 VERSION
287
288Version 0.01
289
290
291=head1 SYNOPSIS
292
293Quick summary of what the module does.
294
295Perhaps a little code snippet.
296
297    use Parse::Syslog::Period::Parser;
298
299    my $foo = Parse::Syslog::Period::Parser->new();
300    ...
301
302=head1 EXPORT
303
304A list of functions that can be exported.  You can delete this section
305if you don't export anything, such as for a purely object-oriented module.
306
307=head1 FUNCTIONS
308
309=head2 function1
310
311
312=head2 function2
313
314
315=head1 AUTHOR
316
317ryo kuroda, C<< <lamanotrama at gmail.com> >>
318
319=head1 BUGS
320
321Please report any bugs or feature requests to C<bug-parse-syslog-period-parser at rt.cpan.org>, or through
322the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Parse-Syslog-Period>.  I will be notified, and then you'll
323automatically be notified of progress on your bug as I make changes.
324
325
326
327
328=head1 SUPPORT
329
330You can find documentation for this module with the perldoc command.
331
332    perldoc Parse::Syslog::Period
333
334
335You can also look for information at:
336
337=over 4
338
339=item * RT: CPAN's request tracker
340
341L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Parse-Syslog-Period>
342
343=item * AnnoCPAN: Annotated CPAN documentation
344
345L<http://annocpan.org/dist/Parse-Syslog-Period>
346
347=item * CPAN Ratings
348
349L<http://cpanratings.perl.org/d/Parse-Syslog-Period>
350
351=item * Search CPAN
352
353L<http://search.cpan.org/dist/Parse-Syslog-Period>
354
355=back
356
357
358=head1 ACKNOWLEDGEMENTS
359
360
361=head1 COPYRIGHT & LICENSE
362
363Copyright 2008 ryo kuroda, all rights reserved.
364
365This program is free software; you can redistribute it and/or modify it
366under the same terms as Perl itself.
367
368
369=cut
370
3711; # End of Parse::Syslog::Period::Parser
Note: See TracBrowser for help on using the browser.