File Coverage

blib/lib/Log/Dispatch/Jabber.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             =head1 NAME
2              
3             Log::Dispatch::Jabber - Log messages via Jabber
4              
5             =head1 SYNOPSIS
6              
7             use Log::Dispatch;
8             use Log::Dispatch::Jabber;
9              
10             my $dispatcher = Log::Dispatch->new();
11             my $jabber = Log::Dispatch::Jabber->new(
12             name=>"jabber",
13             min_level=>"debug",
14             login=>{
15             hostname => "some.jabber.server",
16             port => 5222,
17             username => "logger",
18             password => "*****",
19             resource => "logger",
20             },
21              
22             to=>["webmaster\@a.jabber.server",chief_honco\@a.jabber.server"],
23              
24             check_presence=>1,
25              
26             # Send a message to this address even if their
27             # presence indicates they are not available.
28             force=>"webmaster\@a.jabber.server",
29              
30             # Buffer 5 messages before sending.
31             buffer => "5",
32             );
33              
34             $dispatcher->add($jabber);
35              
36             $dispatcher->log(
37             level => 'debug',
38             message => 'Hello. Programmer. This is '.ref($jabber)
39             );
40              
41             =head1 DESCRIPTION
42              
43             Log messages via Jabber.
44              
45             =head1 ERRORS
46              
47             All internal errors that the package encounters connecting to or authenticating with the Jabber server are logged to STDERR via I.
48              
49             =cut
50              
51 1     1   156782 use strict;
  1         3  
  1         1596  
52              
53             package Log::Dispatch::Jabber;
54 1     1   14 use base qw (Log::Dispatch::Output);
  1         4  
  1         20546  
55              
56             $Log::Dispatcher::Jabber::VERSION = '0.3';
57              
58 1     1   8932 use Net::Jabber qw (Client Presence);
  0            
  0            
59              
60             my %presence;
61              
62             =head1 PACKAGE METHODS
63              
64             =head2 __PACKAGE__->new(%args)
65              
66             Valid arguments are
67              
68             =over 4
69              
70             =item *
71              
72             B
73              
74             String.
75              
76             The name of the object.
77              
78             required
79              
80             =item *
81              
82             B
83              
84             String or Int.
85              
86             The minimum logging level this object will accept. See the Log::Dispatch documentation for more information.
87              
88             required
89              
90             =item *
91              
92             B
93              
94             A hash reference containting the following keys
95              
96             =over 4
97              
98             =item *
99              
100             I
101              
102             String.
103              
104             The name of the Jabber server that your object will connect to.
105              
106             Required
107              
108             =item *
109              
110             I
111              
112             Int.
113              
114             The port of the Jabber server that your object will connect to.
115              
116             Required
117              
118             =item *
119              
120             I
121              
122             String.
123              
124             The username that your object will use to log in to the Jabber server.
125              
126             Required
127              
128             =item *
129              
130             I
131              
132             String.
133              
134             The password that your object will use to log in to the Jabber server.
135              
136             Required
137              
138             =item *
139              
140             I
141              
142             String.
143              
144             The name of the resource that you object will pass to the Jabber server.
145              
146             Required
147              
148             =back
149              
150             =item *
151              
152             B
153              
154             A string or an array reference.
155              
156             A list of Jabber addresses that messages should be sent to.
157              
158             Required
159              
160             =item *
161              
162             B
163              
164             Boolean.
165              
166             If this flag is true then a message will only be sent if a recipient's
167             presence is I or I
168              
169             =item *
170              
171             B
172              
173             A string or an array reference.
174              
175             A list of Jabber addresses that messages should be sent to regardless of
176             their current presence status.
177              
178             This attribute is ignored unless the I attribute is true.
179              
180             =item *
181              
182             B
183              
184             String. The number of messages to buffer before sending.
185              
186             If the argument passed is "-" messages will be buffered until the object's destructor is called.
187              
188             =item *
189              
190             B
191              
192             Int. Net::Jabber debugging level; consult docs for details.
193              
194             =item *
195              
196             B
197              
198             String. Where to write Net::Jabber debugging; default is STDOUT.
199              
200             =back
201              
202             Returns an object.
203              
204             =cut
205              
206             sub new {
207             my $pkg = shift;
208             my $class = ref $pkg || $pkg;
209             my %args = @_;
210              
211             my $self = {};
212             bless $self, $class;
213              
214             $self->_basic_init(%args);
215              
216             $self->{'__client'} = Net::Jabber::Client->new(
217             debuglevel=>$args{debuglevel},
218             debugfile=>($args{debugfile} || "stdout"),
219             );
220              
221             if (! $self->{'__client'}) {
222             $self->_error($!);
223             return undef;
224             }
225              
226             $self->{'__login'} = $args{login};
227             $self->{'__to'} = (ref($args{to}) eq "ARRAY") ? $args{to} : [ $args{to}];
228             $self->{'__force'} = (ref($args{force}) eq "ARRAY") ? $args{force} : [ $args{force}];
229             $self->{'__bufto'} = $args{buffer};
230             $self->{'__presence'} = $args{'check_presence'};
231             $self->{'__buffer'} = [];
232              
233             return $self;
234             }
235              
236             =head1 OBJECT METHODS
237              
238             This package inherits from I.
239              
240             Please consult the docs for details.
241              
242             =cut
243              
244             sub log_message {
245             my $self = shift;
246             my $log = { @_ };
247              
248             push @{$self->{'__buffer'}},$log->{message};
249              
250             if ((! $self->{'__bufto'}) ||
251             (($self->{'__bufto'}) && (scalar(@{$self->{'__buffer'}}) == $self->{'__bufto'}))) {
252             $self->_send();
253             }
254              
255             return 1;
256             }
257              
258             sub _send {
259             my $self = shift;
260              
261             #
262              
263             my $im = Net::Jabber::Message->new();
264             $im->SetMessage(body=>join("",@{$self->{'__buffer'}}),type=>"chat");
265              
266             foreach my $addr (@{$self->{'__to'}}) {
267             $im->SetTo($addr);
268              
269             #
270              
271             my $ok = $self->{'__client'}->Connect(
272             hostname => $self->{'__login'}->{'hostname'},
273             port => $self->{'__login'}->{'port'},
274             );
275              
276             if (! $ok) {
277             $self->_error("Failed to connect to Jabber server:$!\n");
278             return 0;
279             }
280              
281             my @auth = $self->{'__client'}->AuthSend(
282             username => $self->{'__login'}->{'username'},
283             password => $self->{'__login'}->{'password'},
284             resource => $self->{'__login'}->{'resource'},
285             );
286              
287             if ($auth[0] ne "ok") {
288             $self->_error("Failed to ident/auth with Jabber server:($auth[0]) $auth[1]. Message not sent.\n");
289             return 0;
290             }
291              
292             #
293              
294             if (($self->{'__presence'}) && (! grep /^($addr)$/,@{$self->{'__force'}})) {
295              
296             $self->{'__client'}->SetCallBacks("presence"=>\&_presence);
297             $self->{'__client'}->PresenceSend();
298              
299             unless(defined($self->{'__client'}->Process(2))) {
300             $self->_error("There was a problem with the client's connection, $!\n");
301             return 0;
302             }
303              
304             unless ($presence{$addr} =~ /^(normal|chat)$/) {
305             $self->_error("Did not notify $addr : $presence{$addr}\n");
306             next;
307             }
308             }
309              
310             #
311              
312             $self->{'__client'}->Send($im);
313             $self->{'__client'}->Disconnect();
314             }
315              
316              
317             $self->{'__buffer'} = [];
318             return 1;
319             }
320              
321             # Shamelessly pilfered from the mighty mighty D.J. Adams
322             # http://www.pipetree.com/jabber/extended_notify.html#Presence
323              
324             sub _presence {
325             my $id = shift;
326             my $presence = shift;
327              
328             if (ref($presence) ne "Net::Jabber::Presence") {
329             return undef;
330             }
331              
332             # remove any resource suffix from JID
333             (my $jid = $presence->GetFrom()) =~ s!\/.*$!!;
334              
335             $presence{$jid} = $presence->GetShow() || 'normal';
336             }
337              
338             sub _error {
339             my $self = shift;
340              
341             if (! $self->{'__logger'}) {
342             require Log::Dispatch::Screen;
343             $self->{'__logger'} = Log::Dispatch->new();
344             $self->{'__logger'}->add(Log::Dispatch::Screen->new(name=>__PACKAGE__,
345             stderr=>1,
346             min_level=>"error"));
347             }
348              
349             $self->{'__logger'}->error(@_);
350             }
351              
352             sub DESTROY {
353             my $self = shift;
354              
355             if (scalar(@{$self->{'__buffer'}})) {
356             $self->_send();
357             }
358              
359             if ($self->{'__client'}->Connected()) {
360             $self->{'__client'}->Disconnect();
361             }
362              
363             return 1;
364             }
365              
366             =head1 VERSION
367              
368             0.3
369              
370             =head1 DATE
371              
372             November 25, 2002
373              
374             =head1 AUTHOR
375              
376             Aaron Straup Cope
377              
378             =head1 SEE ALSO
379              
380             L
381              
382             L
383              
384             =head1 TO DO
385              
386             =over 4
387              
388             =item *
389              
390             Figure out if it is possible to maintain a connection to the Jabber server between calls to I.
391              
392             If the package does not disconnect between messages but also doesn't do the AuthSend thing, anything after the first message is not sent. (Where does it go?)
393              
394             If the package does not disconnect and does the AuthSend thing, the Jabber server returns a '503' error which is a flag that something is wrong. Except, you can still send the message if you ignore the fact that everything is not 'ok'.
395              
396             Go figure.
397              
398             =back
399              
400             =head1 BUGS
401              
402             =over 4
403              
404             =item *
405              
406             Sending messages to multiple recipients:
407              
408             I've made some progress, in a two-steps forward, one-step back kind of
409             way.
410              
411             Specifically, I can get the package to send messages to multiple
412             addresses by connecting/disconnecting for every single address sent
413             instead of logging in just once for every message.
414              
415             Then the problem becomes that if too many notices are sent in rapid
416             succession (unlikely but who I am to say) the jabberd for the sender
417             will likely start to limit the connection rate and all the subsequent
418             connections will fail.
419              
420             I've tried this with both Net::Jabber and Jabber::Connection and the
421             results were the same.
422              
423             Ideally, I'd like to simply create one connection and send a bunch of
424             messages to different addresses. I can go through the motions without
425             generating any errors but the messages themselves are only ever received
426             by the first address...
427              
428             B
429             in a short enough period time to freak out your jabberd.>
430              
431             It is recommended that you set the I attribute in the object
432             constructor.
433              
434             In the meantime, I'm workin' on it.
435              
436             sub _send {
437             my $self = shift;
438              
439             my $im = Jabber::NodeFactory->newNode("message");
440             $im->insertTag('body')->data(...);
441              
442             # Where &_connect() and &_disconnect()
443             # are simply wrapper methods that DWIM
444              
445             # $self->_connect();
446             # The above works great except that only
447             # the first address in $self->{'__to'}
448             # ever receives any messages
449              
450             # This would be my preferred way of doing
451             # things since there's no point in creating
452             # a gazillion connetions - unless I've spaced
453             # on some important Jabber fundamentals....
454              
455             foreach my $addr (@{$self->{'__to'}}) {
456             $im->attr("to",$addr);
457              
458             $self->_connect();
459             # The above works so long as not too many
460             # messages are sent in rapid succession
461              
462             # Log::Dispatch::Jabber has hooks to
463             # buffer messages but if I send (4)
464             # successive notices with nothing in
465             # between, the server I'm testing against
466             # (and out-of-the-box FreeBSD port) starts
467             # to carp with 'is being connection rate limited'
468             # errors after the third notice.
469              
470             # I suppose I could sleep(n) but that seems
471             # like sort of rude behaviour for a log thingy.
472              
473             # Happy happy
474             $self->{'__client'}->send($im);
475             $self->_disconnect();
476             }
477              
478             # $self->_disconnect()
479             }
480              
481             =back
482              
483             Please report all bugs to http://rt.cpan.org/NoAuth/Dists.html?Queue=Log::Dispatch::Jabber
484              
485             =head1 LICENSE
486              
487             Copyright (c) 2002, Aaron Straup Cope. All Rights Reserved.
488              
489             This is free software, you may use it and distribute it under the same terms as Perl itself.
490              
491             =cut
492              
493             return 1;