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