File Coverage

blib/lib/Log/Log4perl/Appender/RabbitMQ.pm
Criterion Covered Total %
statement 25 67 37.3
branch 1 22 4.5
condition 0 4 0.0
subroutine 8 10 80.0
pod 2 2 100.0
total 36 105 34.2


line stmt bran cond sub pod time code
1             # ABSTRACT: Log to RabbitMQ
2              
3 1     1   2903 use 5.008008;
  1         5  
  1         57  
4 1     1   6 use strict;
  1         2  
  1         39  
5 1     1   5 use warnings;
  1         1  
  1         95  
6              
7             package Log::Log4perl::Appender::RabbitMQ;
8             $Log::Log4perl::Appender::RabbitMQ::VERSION = '0.141470';
9             our @ISA = qw/ Log::Log4perl::Appender /;
10              
11 1     1   935 use Net::AMQP::RabbitMQ 0.004002;
  1         2433  
  1         43  
12 1     1   1090 use Readonly;
  1         3528  
  1         511  
13              
14             Readonly my $CHANNEL => 1;
15              
16             my $RabbitMQClass = 'Net::AMQP::RabbitMQ';
17              
18             ##################################################
19             sub new {
20             ##################################################
21 1     1 1 42 my($class, %args) = @_;
22              
23             # For testing use the Test::Net::RabbitMQ class
24 1 50       7 if ($args{TESTING}) {
25 1         2 $RabbitMQClass = 'Test::Net::RabbitMQ';
26 1         796 require Test::Net::RabbitMQ;
27             }
28              
29 0   0       my $self = bless {
      0        
30             host => $args{host} || 'localhost',
31             routing_key => $args{routing_key} || '%c' ,
32             declare_exchange => $args{declare_exchange},
33             }, $class;
34              
35             # set a flag that tells us to do routing_key interpolation
36             # only if there are things to interpolate.
37 0 0         $self->{interpolate_routing_key} = 1 if $self->{routing_key} =~ /%c|%p/;
38              
39             # Store any given exchange options for declaring an exchange
40 0           my %exchange_options;
41 0           for my $name (qw/
42             exchange_type
43             passive_exchange
44             durable_exchange
45             auto_delete_exchange
46             /) {
47             # convert from the param name we require in args to the name
48             # exchange_declare() will look for by stripping off the _exchange
49 0           (my $declare_param_name = $name) =~ s/(.*)_exchange$/$1/;
50 0 0         $exchange_options{$declare_param_name} = $args{$name} if exists $args{$name};
51             }
52 0           $self->{exchange_options} = \%exchange_options;
53              
54             # Store any given publish options for use when log is called
55 0           my %publish_options;
56 0           for my $name (qw/
57             exchange
58             mandatory
59             immediate
60             /) {
61 0 0         $publish_options{$name} = $args{$name} if exists $args{$name};
62             }
63 0           $self->{publish_options} = \%publish_options;
64              
65             # use any given connect options in connect
66 0           my %connect_options;
67 0           for my $name (qw/
68             user
69             password
70             port
71             vhost
72             channel_max
73             frame_max
74             heartbeat
75             /) {
76 0 0         $connect_options{$name} = $args{$name} if exists $args{$name};
77             }
78              
79             # Create a new connection
80             eval {
81 0           $self->{mq} = _connect_cached($self->{host}, \%connect_options);
82              
83             # declare the exchange if declare_exchange is set
84 0 0         $self->{mq}->exchange_declare(
85             $CHANNEL,
86             $self->{publish_options}{exchange},
87             $self->{exchange_options},
88             ) if $self->{declare_exchange};
89              
90 0           1;
91 0 0         } or do {
92 0           warn "ERROR creating $class: $@\n";
93             };
94              
95 0           return $self;
96             }
97              
98              
99             ##################################################
100             # this closure provides a private class method
101             # that will connect to RabbitMQ and cache that
102             # connection. The next time it's called with
103             # the same params it returns the cached connection.
104             {
105             my %connection_cache;
106              
107             ##################################################
108             sub _connect_cached {
109             ##################################################
110 0     0     my $host = shift;
111 0           my $connect_options = shift;
112              
113             # Create a cache key from the connection options and the host
114 1     1   10 no warnings 'uninitialized';
  1         2  
  1         67  
115 0           my $cache_key = join(':', $host, sort %$connect_options);
116 1     1   5 use warnings;
  1         2  
  1         411  
117              
118 0 0         return $connection_cache{$cache_key} if $connection_cache{$cache_key};
119              
120             # Create new RabbitMQ object & connection, open channel 1
121 0           my $mq = $RabbitMQClass->new();
122 0           $mq->connect($host, $connect_options);
123 0           $mq->channel_open($CHANNEL);
124              
125             # Cache RabbitMQ object
126 0           $connection_cache{$cache_key} = $mq;
127              
128             # Return the RabbitMQ object and the channel we used
129 0           return $mq;
130             }
131             }
132              
133             ##################################################
134             sub log {
135             ##################################################
136 0     0 1   my ($self, %args) = @_;
137              
138 0           my $mq = $self->{mq};
139              
140             # do nothing if the Net::AMQP::RabbitMQ object is missing
141 0 0         return unless $mq;
142              
143             # customize the routing key for this message by
144             # inserting category and level if interpolate_routing_key
145             # flag is set
146 0           my $routing_key = $self->{routing_key};
147 0 0         if ($self->{interpolate_routing_key}) {
148 0           $routing_key =~ s/%c/$args{log4p_category}/g;
149 0           $routing_key =~ s/%p/$args{log4p_level}/g;
150             }
151              
152             # publish the message to the specified group
153             eval {
154 0           $mq->publish($CHANNEL, $routing_key, $args{message}, $self->{publish_options});
155 0           1;
156 0 0         } or do {
157             # If you got an error warn about it and clear the
158             # Net::AMQP::RabbitMQ object so we don't keep trying
159 0           warn "ERROR logging to RabbitMQ via ".ref($self).": $@\n";
160 0           $self->{mq} = undef;
161             };
162              
163 0           return;
164             }
165              
166             1;
167              
168             __END__
169              
170             =pod
171              
172             =encoding UTF-8
173              
174             =head1 NAME
175              
176             Log::Log4perl::Appender::RabbitMQ - Log to RabbitMQ
177              
178             =head1 VERSION
179              
180             version 0.141470
181              
182             =head1 SYNOPSIS
183              
184             use Log::Log4perl;
185              
186             my $log4perl_config = q{
187             log4perl.logger = DEBUG, RabbitMQ
188              
189             log4perl.appender.RabbitMQ = Log::Log4perl::Appender::RabbitMQ
190             log4perl.appender.RabbitMQ.exchange = myexchange
191             log4perl.appender.RabbitMQ.routing_key = mykey
192             log4perl.appender.RabbitMQ.layout = Log::Log4perl::Layout::PatternLayout
193             };
194              
195             Log::Log4perl::init(\$log4perl_config);
196              
197             my $log = Log::Log4perl->get_logger();
198              
199             $log->warn('this is my message');
200              
201             =head1 DESCRIPTION
202              
203             This is a L<Log::Log4perl> appender for publishing log messages to RabbitMQ
204             using L<Net::AMQP::RabbitMQ>.
205             Defaults for unspecified options are provided by L<Net::AMQP::RabbitMQ> and
206             can be found in it's documentation.
207              
208             =head1 CONFIG OPTIONS
209              
210             All of the following options can be passed to the constructor, or be
211             specified in the Log4perl config file. Unless otherwise stated, any options
212             not specified will get whatever defaults L<Net::AMQP::RabbitMQ> provides.
213             See the documentation for that module for more details.
214              
215             =head3 Connection Options
216              
217             These options are used in the call to
218             L<Net::AMQP::RabbitMQ::connect()|Net::AMQP::RabbitMQ/"Methods"> when the
219             appender is created.
220              
221             =over 4
222              
223             =item user
224              
225             =item password
226              
227             =item host
228              
229             Defaults to localhost.
230              
231             =item port
232              
233             =item vhost
234              
235             =item channel_max
236              
237             =item frame_max
238              
239             =item heartbeat
240              
241             =back
242              
243             =head3 Exchange Options
244              
245             Except for L<declare_exchange>, these options are used in a call to
246             L<Net::AMQP::RabbitMQ::exchange_declare()|Net::AMQP::RabbitMQ/"Methods"> to
247             declare the exchange specified on the L<exchange> option
248             (See L<Publish Options>).
249             If L<declare_exchange> is false (the default) the exchange will not be
250             declared and must already exist.
251              
252             =over 4
253              
254             =item declare_exchange
255              
256             Declare the exchange, or just trust that it already exists?
257             Boolean, defaults to 0.
258              
259             =item exchange_type
260              
261             'direct, 'topic', etc.
262              
263             =item durable_exchange
264              
265             Should the exchange survive a restart? Boolean, defaults to 0.
266              
267             =item auto_delete_exchange
268              
269             Delete the exchange when this proccess disconnects? Boolean, defaults to 1.
270              
271             =back
272              
273             =head3 Publish Options
274              
275             These options are used in the call to
276             L<Net::AMQP::RabbitMQ::publish()|Net::AMQP::RabbitMQ/"Methods"> for each
277             message.
278              
279             =over 4
280              
281             =item routing_key
282              
283             The routing key for messages. If the routing key contains a C<%c> or a C<%p>
284             it will be interpolated for each message. C<%c> will be replaced with the
285             Log4perl category.
286             C<%p> will be replaces with the Log4perl priority.
287              
288             Defaults to C<%C>
289              
290             =item exchange
291              
292             The exchange to publish the message too. This exchange must already exist
293             unless declare_exchange is set to true.
294              
295             =item mandatory
296              
297             boolean. Flag published messages mandatory.
298              
299             =item immediate
300              
301             boolean. Flag published messages immediate.
302              
303             =back
304              
305             =head1 METHODS
306              
307             This is a subclass of L<Log::Log4perl::Appender>. It overrides the following
308             methods:
309              
310             =over 4
311              
312             =item new
313              
314             =item log
315              
316             =back
317              
318             =head1 AUTHOR
319              
320             Trevor Little <bundacia@tjlittle.com>
321              
322             =head1 COPYRIGHT AND LICENSE
323              
324             This software is copyright (c) 2014 by Trevor Little.
325              
326             This is free software; you can redistribute it and/or modify it under
327             the same terms as the Perl 5 programming language system itself.
328              
329             =cut