File Coverage

blib/lib/PAUSEx/Log.pm
Criterion Covered Total %
statement 46 153 30.0
branch 0 34 0.0
condition 0 3 0.0
subroutine 10 31 32.2
pod 4 4 100.0
total 60 225 26.6


line stmt bran cond sub pod time code
1 2     2   694359 use v5.36;
  2         9  
2              
3             package PAUSEx::Log;
4              
5 2     2   12 use warnings;
  2         4  
  2         137  
6 2     2   13 no warnings;
  2         3  
  2         129  
7              
8 2     2   1701 use Mojo::Util qw(dumper);
  2         416161  
  2         298  
9 2     2   20 use Carp qw(croak);
  2         2  
  2         99  
10 2     2   1231 use Digest::SHA1;
  2         1971  
  2         366  
11              
12             our $VERSION = '0.002';
13              
14       0     sub DESTROY {}
15              
16             sub AUTOLOAD {
17 0     0     our $AUTOLOAD;
18 0           my $method = $AUTOLOAD =~ s/.*:://r;
19              
20 0           my( $self , @rest ) = @_;
21 0 0         croak "Method <$method> not found" unless $self->can($method);
22              
23 2     2   1086 use experimental qw(builtin);
  2         3313  
  2         11  
24 0           my $class = builtin::blessed($self);
25              
26 2     2   256 no strict 'refs';
  2         5  
  2         1351  
27 0     0     *{"${class}::$method"} = sub { return $_[0]->{$method} };
  0            
  0            
28 0           goto &{"${class}::$method"};
  0            
29             }
30              
31             =encoding utf8
32              
33             =head1 NAME
34              
35             PAUSEx::Log - Access the PAUSE log
36              
37             =head1 SYNOPSIS
38              
39             use v5.36;
40             use PAUSEx::Log;
41              
42             my $start = time;
43              
44             FETCH: while( 1 ) {
45             last if time - $start > 10 * 60;
46              
47             my $entries = PAUSEx::Log->fetch_log();
48              
49             MESSAGE: foreach my $entry ( $entries->@* ) {
50             next unless $entry->is_for_pauseid( 'BDFOY' );
51             say $entry->message;
52             last FETCH if ...
53             }
54              
55             sleep 5*60;
56             }
57              
58             =head1 DESCRIPTION
59              
60             The Perl Authors Upload Server provides a tail of its log file so
61             module authors can check the progress of their modules through the
62             PAUSE process. This might take several minutes from the time of upload,
63             and I want to monitor the log until I know my latest release has been
64             seen by PAUSE.
65              
66             This module fetches that log and digests it in various ways.
67              
68             =head1 Class methods
69              
70             =over 4
71              
72             =item fetch_log( PAUSE_USER, PAUSE_PASS )
73              
74             Fetch the PAUSE log, using your PAUSE ID and password. You can also
75             set these in the C and C environment variables, which
76             this function will automatically pick up.
77              
78             =cut
79              
80 0     0 1   sub fetch_log ( $class, $user = $ENV{CPAN_USER}, $pass = $ENV{CPAN_PASS} ) {
  0            
  0            
  0            
  0            
81 0           state $rc = require Mojo::UserAgent;
82 0           state $ua = Mojo::UserAgent->new;
83 0           state $url_template = 'https://%s:%s@pause.perl.org/pause/authenquery?ACTION=tail_logfile&pause99_tail_logfile_1=5000&pause99_tail_logfile_sub=Tail+characters';
84 0           state $url = sprintf $url_template, $user, $pass;
85              
86 0           my $tx = $ua->get( $url );
87              
88             my $entries = $tx->res->dom
89             ->find( 'div#logs table.table tbody.list tr td.log' )
90             ->map( 'text' )
91 0     0     ->map( sub { PAUSEx::Log->_parse_log_line($_) } )
92 0           ;
93             }
94              
95 0     0     sub _new ( $class, $hash, @values ) {
  0            
  0            
  0            
  0            
96 0           my @names = $class->names;
97 0 0         if( @names != @values ) {
98 0           croak "Names mismatch for: $hash->{message}\n (@names) <- (@values)"
99             }
100              
101 0           $hash->@{@names} = @values;
102              
103 0           bless $hash, $class;
104             }
105              
106             =back
107              
108             =head2 Instance methods
109              
110             =over 4
111              
112             =item can( METHOD )
113              
114             Returns true if the message contains that information since different
115             types of message have different things they record. For example,
116             not all messages contain the PAUSE ID
117              
118             if( $entry->can('pause_id') ) { ... }
119              
120             =cut
121              
122 0     0 1   sub can ($either, $method) {
  0            
  0            
  0            
123             state $class_methods = {
124 0           map { $_, 1 } qw(new can parse_log_line parse_message)
  0            
125             };
126             state $common_methods = {
127 0           map { $_, 1 } qw(date time huh version level message id type)
  0            
128             };
129              
130 0 0         if( ref $either ) {
131 0           my $instance = { map { $_, 1 } $either->names };
  0            
132             return 1 if(
133 0 0 0       exists $common_methods->{$method} or exists $instance->{$method}
134             );
135             }
136             else {
137 0 0         return 1 if exists $class_methods->{$method};
138             }
139             }
140              
141             =item date
142              
143             (Common) The date of the log line, in YYYY-MM-DD
144              
145             =item distname
146              
147             The distribution name (Foo-Bar-1.23.tgz), if the message refers to one.
148              
149             =item for_pause_id( PAUSE_ID )
150              
151             Returns true if the log message is about PAUSE_ID.
152              
153             foreach my $entry ( fetch()->@* ) {
154             next unless $entry->for_pause_id( 'BDFOY' );
155             ...
156             }
157              
158             =cut
159              
160 0     0 1   sub for_pause_id ( $self, $pause_id ) {
  0            
  0            
  0            
161 0 0         return unless defined $pause_id;
162 0 0         return unless $self->can('pause_id');
163 0           return $self->pause_id eq uc $pause_id;
164             }
165              
166             =item id
167              
168             (Common) A made up unique ID for the log message so you can tell if you've
169             seen that log line before.
170              
171             =item level
172              
173             (Common) The log level
174              
175             =item message
176              
177             (Common) The log message
178              
179             =item pause_id
180              
181             The PAUSE ID of the message, if the message refers to one
182              
183             =item time
184              
185             (Common) The time of the log entry
186              
187             =item type
188              
189             (Common) The type of message
190              
191             =cut
192              
193 0     0 1   sub type ( $self ) {
  0            
  0            
194 2     2   43 use experimental(qw(builtin));
  2         6  
  2         8  
195 0           builtin::blessed($self) =~ s/.*:://r;
196             }
197              
198 0     0     sub _parse_log_line ($class, $log_line) {
  0            
  0            
  0            
199             # No idea what $huh represents
200 0           my( $date, $time, $huh, $version, $level, $message )
201             = split /\s+/, $log_line, 6;
202              
203 0           $version =~ s/:\z//;
204 0           $level =~ s/:\z//; $level = lc($level);
  0            
205              
206 0           my $paused_line;
207 0 0         if( $message =~ s/\s+\(paused:(\d+)\)\z// ) {
208 0           $paused_line = $1;
209             }
210              
211 0           my %hash = (
212             date => $date,
213             time => $time,
214             huh => $huh,
215             version => $version,
216             level => $level,
217             paused_line => $paused_line,
218             message => $message,
219             id => Digest::SHA1::sha1_hex($message),
220             );
221              
222 0           PAUSEx::Log->_parse_message(\%hash),
223             }
224              
225 0     0     sub _parse_message ( $class, $hash ) {
  0            
  0            
  0            
226 0           local $_ = $hash->{message};
227              
228 0           do {
229 0           state $uri_re = qr| (?
230             (? .+ )? /?
231             (? [A-Z] ) /
232             (? \g{first} [A-Z] ) /
233             (? \g{second} [A-Z]+ ) /
234             (? [^/]+ )
235             )
236             |x;
237              
238 0 0         if( /\ANeed to get uriid\[$uri_re\]/ ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
239 0           PAUSEx::Log::enqueue->_new( $hash, @+{qw(full_path pause_id distname)} );
240             }
241             elsif( /\AGoing to fetch uriid\[$uri_re\]/ ) {
242 0           PAUSEx::Log::fetch->_new( $hash, @+{qw(full_path pause_id distname)} );
243             }
244             elsif( /\ARequesting a GET on uri \[(.+)\]/ ) {
245 0           PAUSEx::Log::get->_new( $hash, $1 );
246             }
247             elsif( /\Arenamed '(.+?)' to '$uri_re'/ ) {
248 0           PAUSEx::Log::renamed->_new( $hash, $1, @+{qw(full_path pause_id distname)} );
249             }
250             elsif( /\AGot $uri_re \(size (?\d+)\)/ ) {
251 0           PAUSEx::Log::received->_new( $hash, @+{qw(full_path pause_id distname size)} );
252             }
253             elsif( /\ASent 'has entered' email about uriid\[$uri_re\]/ ) {
254 0           PAUSEx::Log::entered->_new( $hash, @+{qw(full_path pause_id distname)} );
255             }
256             elsif( /\AVerified $uri_re/ ) {
257 0           PAUSEx::Log::verified->_new( $hash, @+{qw(full_path pause_id distname)} );
258             }
259             elsif( /\AStarted mldistwatch for lpath\[$uri_re\] with pid\[(?\d+)\]/ ) {
260 0           PAUSEx::Log::mldistwatch_start->_new( $hash, @+{qw(full_path pause_id distname pid)} );
261             }
262             elsif( /\AReaped child\[(\d+)\]/ ) {
263 0           PAUSEx::Log::reaped->_new( $hash, $1 );
264             }
265             else {
266 0           PAUSEx::Log::unknown->_new( $hash );
267             }
268             };
269             }
270              
271              
272              
273             BEGIN {
274 2     2   4243 package PAUSEx::Log::enqueue { our @ISA = qw(PAUSEx::Log); sub names { qw(uri_id pause_id distname) } }
  2     0   49  
  0         0  
  0            
275 2     0   6 package PAUSEx::Log::fetch { our @ISA = qw(PAUSEx::Log); sub names { qw(uri_id pause_id distname) } }
  2         21  
  0         0  
  0            
276 2     0   5 package PAUSEx::Log::get { our @ISA = qw(PAUSEx::Log); sub names { qw(uri) } }
  2         1726  
  0         0  
  0            
277 2     0   8 package PAUSEx::Log::renamed { our @ISA = qw(PAUSEx::Log); sub names { qw(tmp dest pause_id distname) } }
  2         18  
  0         0  
  0            
278 2     0   5 package PAUSEx::Log::received { our @ISA = qw(PAUSEx::Log); sub names { qw(uri_id pause_id distname size) } }
  2         46  
  0         0  
  0            
279 2     0   18 package PAUSEx::Log::entered { our @ISA = qw(PAUSEx::Log); sub names { qw(uri_id pause_id distname) } }
  2         20  
  0         0  
  0            
280 2     0   22 package PAUSEx::Log::verified { our @ISA = qw(PAUSEx::Log); sub names { qw(uri_id pause_id distname) } }
  2         18  
  0         0  
  0            
281 2     0   24 package PAUSEx::Log::mldistwatch_start { our @ISA = qw(PAUSEx::Log); sub names { qw(lpath pause_id distname pid) } }
  2         24  
  0         0  
  0            
282 2     0   17 package PAUSEx::Log::reaped { our @ISA = qw(PAUSEx::Log); sub names { qw(pid) } }
  2         18  
  0         0  
  0            
283 2     0   4 package PAUSEx::Log::unknown { our @ISA = qw(PAUSEx::Log); sub names { qw() } }
  2         121  
  0            
  0            
284             }
285              
286              
287             =back
288              
289             =head1 TO DO
290              
291              
292             =head1 SEE ALSO
293              
294              
295             =head1 SOURCE AVAILABILITY
296              
297             This source is in Github:
298              
299             http://github.com/briandfoy/pausex-log
300              
301             =head1 AUTHOR
302              
303             brian d foy, C<< >>
304              
305             =head1 COPYRIGHT AND LICENSE
306              
307             Copyright © 2023-2025, brian d foy, All Rights Reserved.
308              
309             You may redistribute this under the terms of the Artistic License 2.0.
310              
311             =cut
312              
313             1;