File Coverage

blib/lib/FirePHP/Dispatcher.pm
Criterion Covered Total %
statement 22 100 22.0
branch 0 40 0.0
condition 0 6 0.0
subroutine 8 24 33.3
pod 16 16 100.0
total 46 186 24.7


line stmt bran cond sub pod time code
1             package FirePHP::Dispatcher;
2              
3 1     1   927 use strict;
  1         1  
  1         39  
4 1     1   5 use warnings;
  1         2  
  1         37  
5              
6 1     1   56 BEGIN { require 5.008001; }
7              
8 1     1   797 use version;
  1         2078  
  1         4  
9             our $VERSION = '0.02_01';
10              
11             =head1 NAME
12              
13             FirePHP::Dispatcher - sends log messages to a FirePHP console
14              
15             =head1 SYNOPSIS
16              
17             use FirePHP::Dispatcher;
18              
19             my $fire_php = FirePHP::Dispatcher->new(
20             $reference_to_http_headers_of_current_request
21             );
22              
23             $fire_php->log( 'Hello world' );
24              
25             $fire_php->start_group( 'Levels:' );
26             $fire_php->info ( 'Log informational message' );
27             $fire_php->warn ( 'Log warning message' );
28             $fire_php->error( 'Log error message' );
29             $fire_php->end_group;
30              
31             $fire_php->start_group( 'Propably empty:' );
32             $fire_php->dismiss_group;
33              
34             $fire_php->finalize;
35              
36             =head1 DESCRIPTION
37              
38             B<FirePHP::Dispatcher> implements the basic interface
39             for logging to a FirePHP console. It is no logger on its own
40             but rather a basic API that can be used by front-end loggers to
41             divert or copy messages to a FirePHP console.
42              
43             =cut
44              
45 1     1   73 use base qw/Class::Accessor::Fast/;
  1         1  
  1         759  
46              
47 1     1   226617 use Carp;
  1         3  
  1         74  
48 1     1   6 use Scalar::Util qw/looks_like_number blessed/;
  1         2  
  1         74  
49 1     1   836 use JSON::Any;
  1         385270  
  1         7  
50              
51              
52             __PACKAGE__->mk_accessors(
53             qw/http_headers message_index stash json group_stack/
54             );
55              
56              
57             =head1 GENERAL METHODS
58              
59             =head2 $class->new( $http_headers )
60              
61             Creates a new instance of C<FirePHP::Dispatcher> and binds it
62             to the L<HTTP::Headers> object given as parameter.
63              
64             Returns: a new C<FirePHP::Dispatcher> object
65              
66             =cut
67              
68             sub new {
69 0     0 1   my ( $class, $http_headers ) = @_;
70 0 0 0       croak "FirePHP::Dispatcher needs a HTTP::Headers object"
71             unless blessed( $http_headers ) and $http_headers->isa( 'HTTP::Headers' );
72 0           $class->SUPER::new({
73             http_headers => $http_headers,
74             message_index => 0,
75             group_stack => [],
76             stash => {},
77             json => JSON::Any->new(),
78             });
79             }
80              
81              
82             =head2 $self->finalize
83              
84             Add the needed protocol headers and meta infos to the
85             L<HTTP::Headers> object if anything has been logged to it.
86             Without C<finalize>, FirePHP will ignore all messages.
87              
88             =cut
89              
90             sub finalize {
91 0     0 1   my $self = shift;
92 0 0         my $http = $self->http_headers or return;
93 0 0         return unless $self->message_index;
94              
95 0           $http->header(
96             'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/' .
97             'Protocol/JsonStream/0.2',
98             'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/' .
99             'Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.2.0',
100             'X-Wf-1-Structure-1' => 'http://meta.firephp.org/' .
101             'Wildfire/Structure/FirePHP/FirebugConsole/0.1',
102             'X-Wf-1-Index' => $self->message_index,
103             );
104             }
105              
106              
107             =head1 LOGGING METHODS
108              
109             =head2 $self->log( $message )
110              
111             Log a plain message to the FirePHP console
112              
113             =cut
114              
115             sub log {
116 0     0 1   my ( $self, $message ) = @_;
117 0           $self->send_headers( $self->format_message({ Type => 'LOG' }, $message ));
118             }
119              
120              
121             =head2 $self->info( $message )
122              
123             Log a informational message to the FirePHP console
124              
125             Returns: Return value
126              
127             =cut
128              
129             sub info {
130 0     0 1   my ( $self, $message ) = @_;
131 0           $self->send_headers( $self->format_message({ Type => 'INFO' }, $message ));
132             }
133              
134              
135             =head2 $self->warn( $message )
136              
137             Log a warning message to the FirePHP console
138              
139             =cut
140              
141             sub warn {
142 0     0 1   my ( $self, $message ) = @_;
143 0           $self->send_headers( $self->format_message({ Type => 'WARN' }, $message ));
144             }
145              
146              
147             =head2 $self->error( $message )
148              
149             Log a error message to the FirePHP console
150              
151             =cut
152              
153             sub error {
154 0     0 1   my ( $self, $message ) = @_;
155 0           $self->send_headers( $self->format_message({ Type => 'ERROR' }, $message ));
156             }
157              
158             =head1 TABLE METHODS
159              
160             =head2 $self->table( $label, $table )
161              
162             Prints the L<FirePHP::SimpleTable> or L<Text::SimpleTable> object
163             to the FirePHP console
164              
165             =cut
166              
167             sub table {
168 0     0 1   my ( $self, $label, $table ) = @_;
169 0 0         $label = '' unless defined $label;
170              
171 0           my $report;
172 0 0 0       if ( blessed $table and $table->isa( 'Text::SimpleTable' ) ) {
    0          
173 0 0         if ( not $table->isa('FirePHP::SimpleTable') ) {
174 0           require FirePHP::SimpleTable;
175 0           bless $table, 'FirePHP::SimpleTable';
176             }
177 0           $report = $table->draw;
178             } elsif ( ref $table eq 'ARRAY' ) {
179 0           $report = $table;
180             } else {
181 0           die "$table is neither an instance of Text::SimpleTable nor an array ref";
182             }
183              
184 0           $self->send_headers(
185             $self->format_message({ Type => 'TABLE' }, [ $label, $report ] )
186             );
187             }
188              
189              
190             =head1 GROUPING METHODS
191              
192             =head2 $self->start_group( $name )
193              
194             Starts a new, collapsable logging group named C<$name>.
195             Nesting groups is entirly possible.
196              
197             =cut
198              
199             sub start_group {
200 0     0 1   my ( $self, $label ) = @_;
201 0 0         croak 'A group needs a label' unless $label;
202 0 0         my $http = $self->http_headers
203             or return;
204 0           my $hdr = $self->next_message_header;
205 0           push @{ $self->group_stack }, $self->message_index;
  0            
206 0           my $msg = $self->format_message({ Type => 'GROUP_START', Label => $label });
207 0           $http->header( $hdr => sprintf( '%d|%s|', length( $msg ), $msg ) );
208             }
209              
210              
211             =head2 $self->dismiss_group
212              
213             Dismisses the current group. In later versions this will most propable
214             delete contained messages. Right now just a warning is issued and the current
215             group is closed with C<end_group>.
216              
217             =cut
218              
219             sub dismiss_group {
220 0     0 1   my $self = shift;
221 0 0         my $current_group = ${ $self->group_stack }[ -1 ]
  0            
222             or return;
223 0 0         if ( $current_group < $self->message_index ) {
224 0           carp "Dismissing a group with content is not implemented right now, " .
225             "just closing it instead now";
226 0           $self->end_group;
227             } else {
228 0           $self->rollback_last_message;
229             }
230 0           pop @{ $self->group_stack };
  0            
231             }
232              
233              
234             =head2 $self->end_group
235              
236             Closes the current group and reenter the parent group if available.
237              
238             =cut
239              
240             sub end_group {
241 0     0 1   my $self = shift;
242 0 0         my $current_group = ${ $self->group_stack }[ -1 ]
  0            
243             or die "no current group";
244 0 0         my $http = $self->http_headers
245             or return;
246 0           my $hdr = $self->next_message_header;
247 0           my $msg = $self->format_message({ Type => 'GROUP_END' });
248 0           $http->header( $hdr => sprintf( '%d|%s|', length( $msg ), $msg ) );
249 0           pop @{ $self->group_stack };
  0            
250             }
251              
252              
253             =head2 $self->end_or_dismiss_group
254              
255             Close the current group if it containes messages, otherwise just dismiss it.
256              
257             =cut
258              
259             sub end_or_dismiss_group {
260 0     0 1   my $self = shift;
261 0 0         my $current_group = ${ $self->group_stack }[ -1 ]
  0            
262             or die "no current group";
263 0 0         if ( $current_group < $self->message_index ) {
264 0           $self->end_group;
265             } else {
266 0           $self->dismiss_group;
267             }
268             }
269              
270              
271             =head1 INTERNAL METHODS
272              
273             =head2 $self->format_message( $attr, $message )
274              
275             Renders the message with the given attributs into a
276             message string that is understood by FirePHP. In
277             version 0.2 of the FirePHP protocol this means just
278             an ordered L<JSON> dump.
279              
280             =cut
281              
282             sub format_message {
283 0     0 1   my ( $self, $attr, $message ) = @_;
284 0           $self->json->objToJson( [ $attr, $message ] );
285             }
286              
287              
288             =head2 $self->next_message_header
289              
290             Iterator for FirePHP headers. Calling it advances
291             the internal message cursor so ensure that you either
292             fill it or rollback the message.
293              
294             Returns: the next header field name for messages
295              
296             =cut
297              
298             sub next_message_header {
299 0     0 1   my $self = shift;
300 0           return sprintf( "X-Wf-1-1-1-%d", ++$self->{message_index} );
301             }
302              
303              
304             =head2 $self->rollback_last_message
305              
306             Rolls back the last message and decreases the message cursor.
307             This can be used to dismiss groups and delete recent messages
308             from the stack.
309              
310             CAVEAT: currently doesn't work correctly for multi-part messages
311             that contain more than 5000 characters.
312              
313             =cut
314              
315             sub rollback_last_message {
316 0     0 1   my $self = shift;
317 0 0         my $http = $self->http_headers or return;
318 0 0         return unless $self->{message_index};
319 0           my $hdr = sprintf( "X-Wf-1-1-1-%d", $self->{message_index}-- );
320 0           $http->remove_header( $hdr );
321             }
322              
323              
324             =head2 %headers = $self->build_message_headers( $message )
325              
326             Builds the full header structure for the given message string
327             automatically splitting it into multipart messages when the
328             character limit of 5000 is reached. The message cursor will be
329             advanced accordingly.
330              
331             Returns: a hash containing all HTTP headers representing the given message
332              
333             =cut
334              
335             sub build_message_headers {
336 0     0 1   my ( $self, $message ) = @_;
337 0           my $len = length $message;
338              
339             # split message into handable chunks
340 0           my @parts = grep{$_} split /(.{5000})/, $message;
  0            
341              
342 0           my %headers;
343 0           for ( 0 .. $#parts ) {
344 0 0         $headers{ $self->next_message_header } =
    0          
345             (!$_ ? $len : '') . '|' . $parts[$_] . '|' . ($_ < $#parts ? '\\' : '');
346             }
347              
348 0           return %headers;
349             }
350              
351              
352             =head2 $self->send_headers( $message )
353              
354             Just a small wrapper that builds and sends all headers for the given message.
355              
356             =cut
357              
358             sub send_headers {
359 0     0 1   my ( $self, $message ) = @_;
360 0 0         my $http = $self->http_headers or return;
361 0           $http->header( $self->build_message_headers( $message ) );
362             }
363              
364             1;
365              
366             __END__
367              
368              
369             =head1 ACCESSORS
370              
371             =head2 $self->http_headers
372              
373             The bound L<HTTP::Headers> object
374              
375             =head2 $self->message_index
376              
377             The number of messages already send (actually the message header cursor,
378             you are responsible to ensure this is correct if you don't use the logging
379             or iterator functions provided by this class)
380              
381             =head2 $self->stash
382              
383             A hasref that can be used by clients to store information about this
384             logging session
385              
386             =head2 $self->json
387              
388             The C<JSON> parser in use to format messages
389              
390             =head2 $self->group_stack
391              
392             Internal stack used to track groups
393              
394             =head1 DEVELOPER NOTES
395              
396             =head2 PROTOCOL NOTES
397              
398             Header:
399             X-Wf-1-[ STRUCTURE TYPE INDEX ]-1-[ MESSAGE INDEX ]
400              
401             Structure type index:
402             1 - LOG ( and most others? )
403             2 - DUMP
404              
405              
406             Content:
407             [TOTAL LENGTH] \| \[ \{ [JSON MESSAGE PARAMS] \} \]
408              
409             Json message params:
410             Type: LOG|TRACE|EXCEPTION|TABLE|DUMP
411              
412             =head1 SEE ALSO
413              
414             L<http://www.firephp.org>, L<HTTP::Headers>
415              
416             =head1 AUTHOR
417              
418             Sebastian Willert, C<willert@cpan.org>
419              
420             =head1 COPYRIGHT AND LICENSE
421              
422             Copyright 2009 by Sebastian Willert E<lt>willert@cpan.orgE<gt>
423              
424             This library is free software; you can redistribute it and/or modify
425             it under the same terms as Perl itself.
426              
427             =cut
428