File Coverage

blib/lib/Log/Log4perl/Appender/RabbitMQ.pm
Criterion Covered Total %
statement 61 65 93.8
branch 15 22 68.1
condition 8 13 61.5
subroutine 9 9 100.0
pod 2 2 100.0
total 95 111 85.5


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