File Coverage

blib/lib/POE/Component/SimpleLog.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             # Declare our package
2             package POE::Component::SimpleLog;
3 1     1   102968 use strict; use warnings;
  1     1   3  
  1         168  
  1         6  
  1         27  
  1         62  
4              
5             # Initialize our version $LastChangedRevision: 16 $
6             our $VERSION = '1.05';
7              
8             # Import what we need from the POE namespace
9 1     1   489 use POE;
  0            
  0            
10              
11             # Other miscellaneous modules we need
12             use Carp;
13              
14             # Set some constants
15             BEGIN {
16             # Debug fun!
17             if ( ! defined &DEBUG ) {
18             eval "sub DEBUG () { 0 }";
19             }
20             }
21              
22             # Set things in motion!
23             sub new {
24             # Get the OOP's type
25             my $type = shift;
26              
27             # Sanity checking
28             if ( @_ & 1 ) {
29             croak( 'POE::Component::SimpleLog->new needs even number of options' );
30             }
31              
32             # The options hash
33             my %opt = @_;
34              
35             # Our own options
36             my ( $ALIAS, $PRECISION );
37              
38             # You could say I should do this: $Stuff = delete $opt{'Stuff'}
39             # But, that kind of behavior is not defined, so I would not trust it...
40              
41             # Get the session alias
42             if ( exists $opt{'ALIAS'} ) {
43             $ALIAS = $opt{'ALIAS'};
44             delete $opt{'ALIAS'};
45             } else {
46             # Debugging info...
47             if ( DEBUG ) {
48             warn 'Using default ALIAS = SimpleLog';
49             }
50              
51             # Set the default
52             $ALIAS = 'SimpleLog';
53             }
54              
55             # Get the precision
56             if ( exists $opt{'PRECISION'} ) {
57             $PRECISION = $opt{'PRECISION'};
58             delete $opt{'PRECISION'};
59              
60             # Check if it is defined
61             if ( defined $PRECISION ) {
62             # Use Time::HiRes
63             require Time::HiRes;
64             }
65             } else {
66             # Set it to regular
67             $PRECISION = undef;
68             }
69              
70             # Anything left over is unrecognized
71             if ( DEBUG ) {
72             if ( keys %opt > 0 ) {
73             croak 'Unrecognized options were present in POE::Component::SimpleLog->new -> ' . join( ', ', keys %opt );
74             }
75             }
76              
77             # Create a new session for ourself
78             POE::Session->create(
79             # Our subroutines
80             'inline_states' => {
81             # Maintenance events
82             '_start' => \&StartLog,
83             '_stop' => sub {},
84              
85             # Register a log
86             'REGISTER' => \&Register,
87              
88             # Unregister a log
89             'UNREGISTER' => \&UnRegister,
90             'REMOVESESSION' => \&UnRegisterSession,
91              
92             # LOG SOMETHING!
93             'LOG' => \&Log,
94              
95             # We are done!
96             'SHUTDOWN' => \&StopLog,
97             },
98              
99             # Set up the heap for ourself
100             'heap' => {
101             # The logging relation table
102             'LOGS' => {},
103              
104             # Precision
105             'PRECISION' => $PRECISION,
106              
107             # Who wants to get *ALL* logs?
108             'ALLLOGS' => {},
109              
110             'ALIAS' => $ALIAS,
111             },
112             ) or die 'Unable to create a new session!';
113              
114             # Return success
115             return 1;
116             }
117              
118             # Registers a new log to watch
119             sub Register {
120             # Get the arguments
121             my %args = @_[ ARG0 .. $#_ ];
122              
123             # Validation - silently ignore errors
124             if ( ! defined $args{'LOGNAME'} ) {
125             if ( DEBUG ) {
126             warn 'Did not get any arguments';
127             }
128             return undef;
129             }
130              
131             if ( ! defined $args{'SESSION'} ) {
132             if ( DEBUG ) {
133             warn "Did not get a TargetSession for LogName: $args{'LOGNAME'}";
134             }
135             return undef;
136             } else {
137             # Convert actual POE::Session objects to their ID
138             if ( UNIVERSAL::isa( $args{'SESSION'}, 'POE::Session') ) {
139             $args{'SESSION'} = $args{'SESSION'}->ID;
140             }
141             }
142              
143             if ( ! defined $args{'EVENT'} ) {
144             if ( DEBUG ) {
145             warn "Did not get an Event for LogName: $args{'LOGNAME'} -> Target Session: $args{'SESSION'}";
146             }
147             return undef;
148             }
149              
150             # Check if we are registering an *ALL* logger or not
151             if ( $args{'LOGNAME'} eq 'ALL' ) {
152             # Put this in ALL
153             if ( ! exists $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} } ) {
154             $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} } = {};
155             }
156              
157             # Put it in the hash!
158             if ( exists $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} }->{ $args{'EVENT'} } ) {
159             # Duplicate record...
160             if ( DEBUG ) {
161             warn "Tried to register a duplicate! -> LogName: $args{'LOGNAME'} -> Target Session: $args{'SESSION'} -> Event: $args{'EVENT'}";
162             }
163             } else {
164             $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} }->{ $args{'EVENT'} } = 1;
165             }
166             } else {
167             # Verify our data structure
168             if ( ! exists $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} } ) {
169             $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} } = {};
170             }
171              
172             if ( ! exists $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} } ) {
173             $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} } = {};
174             }
175              
176             # Finally put it in the hash :)
177             if ( exists $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} }->{ $args{'EVENT'} } ) {
178             # Duplicate record...
179             if ( DEBUG ) {
180             warn "Tried to register a duplicate! -> LogName: $args{'LOGNAME'} -> Target Session: $args{'SESSION'} -> Event: $args{'EVENT'}";
181             }
182             } else {
183             $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} }->{ $args{'EVENT'} } = 1;
184             }
185             }
186              
187             # All done!
188             return 1;
189             }
190              
191             # Delete a watcher
192             sub UnRegister {
193             # Get the arguments
194             my %args = @_[ ARG0 .. $#_ ];
195              
196             # Validation - silently ignore errors
197             if ( ! defined $args{'LOGNAME'} ) {
198             if ( DEBUG ) {
199             warn 'Did not get any arguments';
200             }
201             return undef;
202             }
203              
204             if ( ! defined $args{'SESSION'} ) {
205             if ( DEBUG ) {
206             warn "Did not get a TargetSession for LogName: $args{'LOGNAME'}";
207             }
208             return undef;
209             } else {
210             # Convert actual POE::Session objects to their ID
211             if ( UNIVERSAL::isa( $args{'SESSION'}, 'POE::Session') ) {
212             $args{'SESSION'} = $args{'SESSION'}->ID;
213             }
214             }
215              
216             if ( ! defined $args{'EVENT'} ) {
217             if ( DEBUG ) {
218             warn "Did not get an Event for LogName: $args{'LOGNAME'} -> Target Session: $args{'SESSION'}";
219             }
220             return undef;
221             }
222              
223             # Check if this is the special *ALL* log
224             if ( $args{'LOGNAME'} eq 'ALL' ) {
225             # Scan it for targetsession
226             if ( exists $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} } ) {
227             # Scan for the proper event!
228             foreach my $evnt ( keys %{ $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} } } ) {
229             if ( $evnt eq $args{'EVENT'} ) {
230             # Found a match, delete it!
231             delete $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} }->{ $evnt };
232             if ( scalar keys %{ $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} } } == 0 ) {
233             delete $_[HEAP]->{'ALLLOGS'}->{ $args{'SESSION'} };
234             }
235              
236             # Return success
237             return 1;
238             }
239             }
240             }
241             } else {
242             # Search through the logs for this specific one
243             if ( exists $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} } ) {
244             # Scan it for targetsession
245             if ( exists $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} } ) {
246             # Scan for the proper event!
247             foreach my $evnt ( keys %{ $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} } } ) {
248             if ( $evnt eq $args{'EVENT'} ) {
249             # Found a match, delete it!
250             delete $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} }->{ $evnt };
251             if ( scalar keys %{ $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} } } == 0 ) {
252             delete $_[HEAP]->{'LOGS'}->{ $args{'LOGNAME'} }->{ $args{'SESSION'} };
253             }
254              
255             # Return success
256             return 1;
257             }
258             }
259             }
260             }
261             }
262              
263             # Found nothing...
264             return undef;
265             }
266              
267             # UnRegisters a whole session
268             sub UnRegisterSession {
269             # ARG0 = Session ID
270             my $TargetSession = $_[ARG0];
271              
272             # Validation
273             if ( ! defined $TargetSession ) {
274             # Hmpf
275             if ( DEBUG ) {
276             warn 'Did not get any arguments!';
277             }
278             }
279              
280             # Go through all of the logs, searching for this session
281             foreach my $logname ( keys %{ $_[HEAP]->{'LOGS'} } ) {
282             # Another loop!
283             foreach my $session ( keys %{ $_[HEAP]->{'LOGS'}->{ $logname } } ) {
284             # Check if they match
285             if ( $session eq $TargetSession ) {
286             # Remove this!
287             delete $_[HEAP]->{'LOGS'}->{ $logname }->{ $TargetSession };
288             }
289             }
290             }
291              
292             # Go through the *ALL* logs
293             foreach my $session ( keys %{ $_[HEAP]->{'ALLLOGS'} } ) {
294             # Check if they match
295             if ( $session eq $TargetSession ) {
296             # Remove this!
297             delete $_[HEAP]->{'ALLLOGS'}->{ $TargetSession };
298             }
299             }
300              
301             # All done!
302             return 1;
303             }
304              
305             # The core part of this module :)
306             sub Log {
307             # ARG0 = LogName, ARG1 = Message
308             my( $logname, $message ) = @_[ ARG0, ARG1 ];
309              
310             # Check if this is an *ALL* log...
311             if ( $logname eq 'ALL' ) {
312             # Should not do this!
313             if ( DEBUG ) {
314             warn 'Sending a log named ALL is not allowed, read the documentation';
315             }
316             }
317              
318             # Figure out the time
319             my $time;
320             if ( defined $_[HEAP]->{'PRECISION'} ) {
321             $time = [ Time::HiRes::gettimeofday ];
322             } else {
323             $time = time();
324             }
325              
326             # Search for this log!
327             if ( exists $_[HEAP]->{'LOGS'}->{ $logname } ) {
328             # Okay, loop over each targetsession, checking if it is valid
329             foreach my $TargetSession ( keys %{ $_[HEAP]->{'LOGS'}->{ $logname } } ) {
330             # Find out if this session exists
331             if ( ! $_[KERNEL]->ID_id_to_session( $TargetSession ) ) {
332             # Argh...
333             if ( DEBUG ) {
334             warn "TargetSession ID $TargetSession does not exist";
335             }
336             } else {
337             # Fire off all the events
338             foreach my $event ( keys %{ $_[HEAP]->{'LOGS'}->{ $logname }->{ $TargetSession } } ) {
339             # We call events with 5 arguments
340             # ARG0 -> CALLER_FILE
341             # ARG1 -> CALLER_LINE
342             # ARG2 -> Time::HiRes [ gettimeofday ] or plain time()
343             # ARG3 -> LOGNAME
344             # ARG4 -> Message
345             $_[KERNEL]->post( $TargetSession,
346             $event,
347             $_[CALLER_FILE],
348             $_[CALLER_LINE],
349             $time,
350             $logname,
351             $message,
352             );
353             }
354             }
355             }
356             } else {
357             # Check if we have any *ALL* handlers
358             if ( keys %{ $_[HEAP]->{'ALLLOGS'} } > 0 ) {
359             # Oh boy, send the log!
360             foreach my $TargetSession ( keys %{ $_[HEAP]->{'ALLLOGS'} } ) {
361             # Find out if this session exists
362             if ( ! $_[KERNEL]->ID_id_to_session( $TargetSession ) ) {
363             # Argh...
364             if ( DEBUG ) {
365             warn "TargetSession ID $TargetSession does not exist";
366             }
367             } else {
368             # Get all the events
369             foreach my $event ( keys %{ $_[HEAP]->{'ALLLOGS'}->{ $TargetSession } } ) {
370             # We call events with 5 arguments
371             # ARG0 -> CALLER_FILE
372             # ARG1 -> CALLER_LINE
373             # ARG2 -> Time::HiRes [ gettimeofday ] or plain time()
374             # ARG3 -> LOGNAME
375             # ARG4 -> Message
376             $_[KERNEL]->post( $TargetSession,
377             $event,
378             $_[CALLER_FILE],
379             $_[CALLER_LINE],
380             $time,
381             $logname,
382             $message,
383             );
384             }
385             }
386             }
387             } else {
388             # Ignore this logname
389             if ( DEBUG ) {
390             warn "Got a LogName: $logname -> Ignoring it because it is not registered";
391             }
392             }
393             }
394              
395             # All done!
396             return 1;
397             }
398              
399             # Starts the logger!
400             sub StartLog {
401             # Create an alias for ourself
402             $_[KERNEL]->alias_set( $_[HEAP]->{'ALIAS'} );
403              
404             # All done!
405             return 1;
406             }
407              
408             # Stops the logger
409             sub StopLog {
410             # Remove our alias
411             $_[KERNEL]->alias_remove( $_[HEAP]->{'ALIAS'} );
412              
413             # Clear our data
414             delete $_[HEAP]->{'LOGS'};
415             delete $_[HEAP]->{'ALLLOGS'};
416              
417             # All done!
418             return 1;
419             }
420              
421             # End of module
422             1;
423              
424             __END__
425            
426             =head1 NAME
427            
428             POE::Component::SimpleLog - Perl extension to manage a simple logging system for POE.
429            
430             =head1 SYNOPSIS
431            
432             use POE;
433             use POE::Component::SimpleLog;
434            
435             # We don't want Time::HiRes
436             POE::Component::SimpleLog->new(
437             ALIAS => 'MyLog',
438             PRECISION => undef,
439             ) or die 'Unable to create the Logger';
440            
441             # Create our own session to communicate with SimpleLog
442             POE::Session->create(
443             inline_states => {
444             _start => sub {
445             # Register for various logs
446             $_[KERNEL]->post( 'MyLog', 'REGISTER',
447             LOGNAME => 'FOO',
448             SESSION => $_[SESSION],
449             EVENT => 'GotFOOlog',
450             );
451            
452             $_[KERNEL]->post( 'MyLog', 'REGISTER',
453             LOGNAME => 'BAZ',
454             SESSION => $_[SESSION],
455             EVENT => 'GotBAZlog',
456             );
457            
458             # Log something!
459             $_[KERNEL]->post( 'MyLog', 'LOG', 'FOO', 'Wow, what a FOO!' );
460            
461             # This will be silently discarded -> nobody registered for it
462             $_[KERNEL]->post( 'MyLog', 'LOG', 'BOO', 'Wow, what a BAZ!' );
463            
464             # OK, enough logging!
465             $_[KERNEL]->post( 'MyLog', 'UNREGISTER',
466             LOGNAME => 'FOO',
467             SESSION => $_[SESSION],
468             EVENT => 'GotFOOlog',
469             );
470            
471             # Now, this log will go nowhere as we just unregistered for it
472             $_[KERNEL]->post( 'MyLog', 'LOG', 'FOO', 'Wow, what a FOO!' );
473            
474             # Completely remove all registrations!
475             $_[KERNEL]->post( 'MyLog', 'UNREGISTERSESSION', $_[SESSION] );
476            
477             # Now, this log will go nowhere as we just removed all logs pertaining to our session
478             $_[KERNEL]->post( 'MyLog', 'LOG', 'BAZ', 'Wow, what a BAZ!' );
479            
480             # We want to eat all we can!
481             $_[KERNEL]->post( 'MyLog', 'REGISTER',
482             LOGNAME => 'ALL',
483             SESSION => $_[SESSION],
484             EVENT => 'GotLOG',
485             );
486            
487             # Now, *ANY* log issued to SimpleLog will go to GotLOG
488             $_[KERNEL]->post( 'MyLog', 'LOG', 'LAF', 'Wow, what a LAF!' );
489            
490             # We are done!
491             $_[KERNEL]->post( 'MyLog', 'SHUTDOWN' );
492             },
493            
494             'GotFOOlog' => \&gotFOO,
495             },
496             );
497            
498             sub gotFOO {
499             # Get the arguments
500             my( $file, $line, $time, $name, $message ) = @_[ ARG0 .. ARG4 ];
501            
502             # Assumes PRECISION is undef ( regular time() )
503             print STDERR "$time ${name}-> $file : $line = $message\n";
504             }
505            
506             =head1 ABSTRACT
507            
508             Very simple, and flexible logging system tailored for POE.
509            
510             =head1 DESCRIPTION
511            
512             This module is a vastly simplified logging system that can do nice stuff.
513             Think of this module as a dispatcher for various logs.
514            
515             This module *DOES NOT* do anything significant with logs, it simply routes them
516             to the appropriate place ( Events )
517            
518             You register a log that you are interested in, by telling SimpleLog the target session
519             and target event. Once that is done, any log messages your program generates ( sent to SimpleLog of course )
520             will be massaged, then sent to the target session / target event for processing.
521            
522             This enables an interesting logging system that can be changed during runtime and allow
523             pluggable interpretation of messages.
524            
525             One nifty idea you can do with this is:
526            
527             Your program generally creates logs with the name of 'DEBUG'. You DCC Chat your IRC bot, then
528             tell it to show all debug messages to you. All the irc bot have to do is register itself for all
529             'DEBUG' messages, and once you disconnect from the bot, it can unregister itself.
530            
531             NOTE: There is no pre-determined log levels ( Like Log4j's DEBUG / INFO / FATAL / etc )
532             Arbitrary names can be used, to great effect. Logs with the names 'CONNECT', 'DB_QUERY', etc can be created.
533            
534             The standard way to use this module is to do this:
535            
536             use POE;
537             use POE::Component::SimpleLog;
538            
539             POE::Component::SimpleLog->new( ... );
540            
541             POE::Session->create( ... );
542            
543             POE::Kernel->run();
544            
545             =head2 Starting SimpleLog
546            
547             To start SimpleLog, just call it's new method:
548            
549             POE::Component::SimpleLog->new(
550             'ALIAS' => 'MyLogger',
551             'PRECISION' => 1,
552             );
553            
554             This method will die on error or return success.
555            
556             This constructor accepts only 2 options.
557            
558             =over 4
559            
560             =item C<ALIAS>
561            
562             This will set the alias SimpleLog uses in the POE Kernel.
563             This will default TO "SimpleLog"
564            
565             =item C<PRECISION>
566            
567             If this value is defined, SimpleLog will use Time::HiRes to get the timestamps.
568            
569             =back
570            
571             =head2 Events
572            
573             SimpleLog is so simple, there are only 5 events available.
574            
575             =over 4
576            
577             =item C<REGISTER>
578            
579             This event accepts 3 arguments:
580            
581             LOGNAME -> The name of the log to register for
582             SESSION -> The session where the log will go ( Also accepts Session ID's )
583             EVENT -> The event that will be called
584            
585             The act of registering for a log can fail if one of the above values are undefined.
586            
587             If the LOGNAME eq 'ALL', then that registration will get *ALL* the logs SimpleLog processes
588            
589             There is no such thing as an "non-existant" log, registration just makes sure that you will get this log *WHEN* it comes.
590            
591             Events that receive the logs will get these:
592             ARG0 -> CALLER_FILE
593             ARG1 -> CALLER_LINE
594             ARG2 -> Time::HiRes [ gettimeofday ] or time()
595             ARG3 -> LOGNAME
596             ARG4 -> Message
597            
598             Here's an example:
599            
600             $_[KERNEL]->post( 'SimpleLog', 'REGISTER',
601             LOGNAME => 'CONNECTION',
602             SESSION => $_[SESSION],
603             EVENT => 'GotLOG',
604             );
605            
606             This is the subroutine that will get the GotLOG event
607             sub gotlog {
608             # Get the arguments
609             my( $file, $line, $time, $name, $message ) = @_[ ARG0 .. ARG4 ];
610            
611             # Assumes PRECISION is undef ( regular time() )
612             print STDERR "$time ${name}-> $file : $line = $message\n";
613            
614             # PRECISION = true ( Time::HiRes )
615             print STDERR "$time->[0].$time->[1] ${name}-> $file : $line = $message\n";
616             }
617            
618             =item C<UNREGISTER>
619            
620             This event accepts 3 arguments:
621            
622             LOGNAME -> The name of the log to unregister for
623             SESSION -> The session where the log will go ( Also accepts Session ID's )
624             EVENT -> The event that will be called
625            
626             Unregistering for a log will fail if the exact 3 arguments were not found in our registry.
627            
628             The act of unregistering will mean the session/event no longer receives any log messages.
629            
630             NOTE: There might be some logs still traversing POE's queue...
631            
632             Here's an example:
633            
634             $_[KERNEL]->post( 'SimpleLog', 'UNREGISTER',
635             LOGNAME => 'CONNECTION',
636             SESSION => $_[SESSION]->ID,
637             EVENT => 'GotLOG',
638             );
639            
640             =item C<UNREGISTERSESSION>
641            
642             This event accepts 1 argument:
643            
644             ARG0 -> The session ( Also accepts Session ID's )
645            
646             This is useful for removing all the registrations for a specific session.
647            
648             Here's an example:
649            
650             $_[KERNEL]->post( 'SimpleLog', 'UNREGISTERSESSION', $_[SESSION] );
651            
652             =item C<LOG>
653            
654             This event accepts 2 arguments:
655            
656             ARG0 -> Logname
657             ARG1 -> Message
658            
659             This is where SimpleLog does it's work, sending the log to the proper events.
660            
661             The Logname can be anything, if there is no events registered for it, the message will simply be discarded.
662            
663             Here's an example:
664            
665             $_[KERNEL]->post( 'SimpleLog', 'LOG', 'CONNECTION', 'A Client just connected!' );
666            
667             =item C<SHUTDOWN>
668            
669             This is the generic SHUTDOWN routine, it will stop all logging.
670            
671             Here's an example:
672            
673             $_[KERNEL]->post( 'SimpleLog', 'SHUTDOWN' );
674            
675             =back
676            
677             =head2 SimpleLog Notes
678            
679             This module is very picky about capitalization!
680            
681             All of the options are uppercase, to avoid confusion.
682            
683             You can enable debugging mode by doing this:
684            
685             sub POE::Component::SimpleLog::DEBUG () { 1 }
686             use POE::Component::SimpleLog;
687            
688             =head2 EXPORT
689            
690             Nothing.
691            
692             =head1 SEE ALSO
693            
694             L<POE>
695            
696             =head1 AUTHOR
697            
698             Apocalypse E<lt>apocal@cpan.orgE<gt>
699            
700             =head1 COPYRIGHT AND LICENSE
701            
702             Copyright 2008 by Apocalypse
703            
704             This library is free software; you can redistribute it and/or modify
705             it under the same terms as Perl itself.
706            
707             =cut