1##################################################
2package Log::Log4perl::Appender;
3##################################################
4
5use 5.006;
6use strict;
7use warnings;
8
9use Log::Log4perl::Level;
10use Log::Log4perl::Config;
11
12use constant _INTERNAL_DEBUG => 0;
13
14our $unique_counter = 0;
15
16##################################################
17sub reset {
18##################################################
19    $unique_counter = 0;
20}
21
22##################################################
23sub unique_name {
24##################################################
25        # THREADS: Need to lock here to make it thread safe
26    $unique_counter++;
27    my $unique_name = sprintf("app%03d", $unique_counter);
28        # THREADS: Need to unlock here to make it thread safe
29    return $unique_name;
30}
31
32##################################################
33sub new {
34##################################################
35    my($class, $appenderclass, %params) = @_;
36
37        # Pull in the specified Log::Log4perl::Appender object
38    eval {
39
40           # Eval erroneously succeeds on unknown appender classes if
41           # the eval string just consists of valid perl code (e.g. an
42           # appended ';' in $appenderclass variable). Fail if we see
43           # anything in there that can't be class name.
44        die "'$appenderclass' not a valid class name " if $appenderclass =~ /[^:\w]/;
45
46            # Check if the class/package is already in the namespace because
47            # something like Class::Prototyped injected it previously.
48        no strict 'refs';
49        if(!scalar(keys %{"$appenderclass\::"})) {
50            # Not available yet, try to pull it in.
51            # see 'perldoc -f require' for why two evals
52            eval "require $appenderclass";
53                 #unless ${$appenderclass.'::IS_LOADED'};  #for unit tests,
54                                                          #see 004Config
55            die $@ if $@;
56        }
57    };
58
59    $@ and die "ERROR: can't load appenderclass '$appenderclass'\n$@";
60
61    $params{name} = unique_name() unless exists $params{name};
62
63    # If it's a Log::Dispatch::File appender, default to append
64    # mode (Log::Dispatch::File defaults to 'clobber') -- consensus 9/2002
65    # (Log::Log4perl::Appender::File already defaults to 'append')
66    if ($appenderclass eq 'Log::Dispatch::File' &&
67        ! exists $params{mode}) {
68        $params{mode} = 'append';
69    }
70
71    my $appender = $appenderclass->new(
72            # Set min_level to the lowest setting. *we* are
73            # controlling this now, the appender should just
74            # log it with no questions asked.
75        min_level => 'debug',
76            # Set 'name' and other parameters
77        map { $_ => $params{$_} } keys %params,
78    );
79
80    my $self = {
81                 appender  => $appender,
82                 name      => $params{name},
83                 layout    => undef,
84                 level     => $ALL,
85                 composite => 0,
86               };
87
88        #whether to collapse arrays, etc.
89    $self->{warp_message} = $params{warp_message};
90    if($self->{warp_message} and
91       my $cref =
92       Log::Log4perl::Config::compile_if_perl($self->{warp_message})) {
93        $self->{warp_message} = $cref;
94    }
95
96    bless $self, $class;
97
98    return $self;
99}
100
101##################################################
102sub composite { # Set/Get the composite flag
103##################################################
104    my ($self, $flag) = @_;
105
106    $self->{composite} = $flag if defined $flag;
107    return $self->{composite};
108}
109
110##################################################
111sub threshold { # Set/Get the appender threshold
112##################################################
113    my ($self, $level) = @_;
114
115    print "Setting threshold to $level\n" if _INTERNAL_DEBUG;
116
117    if(defined $level) {
118        # Checking for \d makes for a faster regex(p)
119        $self->{level} = ($level =~ /^(\d+)$/) ? $level :
120            # Take advantage of &to_priority's error reporting
121            Log::Log4perl::Level::to_priority($level);
122    }
123
124    return $self->{level};
125}
126
127##################################################
128sub log {
129##################################################
130# Relay this call to Log::Log4perl::Appender:* or
131# Log::Dispatch::*
132##################################################
133    my ($self, $p, $category, $level) = @_;
134
135    # Check if the appender has a last-minute veto in form
136    # of an "appender threshold"
137    if($self->{level} > $
138                        Log::Log4perl::Level::PRIORITY{$level}) {
139        print "$self->{level} > $level, aborting\n" if _INTERNAL_DEBUG;
140        return undef;
141    }
142
143    # Run against the (yes only one) customized filter (which in turn
144    # might call other filters via the Boolean filter) and check if its
145    # ok() method approves the message or blocks it.
146    if($self->{filter}) {
147        if($self->{filter}->ok(%$p,
148                               log4p_category => $category,
149                               log4p_level    => $level )) {
150            print "Filter $self->{filter}->{name} passes\n" if _INTERNAL_DEBUG;
151        } else {
152            print "Filter $self->{filter}->{name} blocks\n" if _INTERNAL_DEBUG;
153            return undef;
154        }
155    }
156
157    unless($self->composite()) {
158
159            #not defined, the normal case
160        if (! defined $self->{warp_message} ){
161                #join any message elements
162            $p->{message} =
163                join($Log::Log4perl::JOIN_MSG_ARRAY_CHAR,
164                     @{$p->{message}}
165                     ) if ref $p->{message} eq "ARRAY";
166
167            #defined but false, e.g. Appender::DBI
168        } elsif (! $self->{warp_message}) {
169            ;  #leave the message alone
170
171        } elsif (ref($self->{warp_message}) eq "CODE") {
172            #defined and a subref
173            $p->{message} =
174                [$self->{warp_message}->(@{$p->{message}})];
175        } else {
176            #defined and a function name?
177            no strict qw(refs);
178            $p->{message} =
179                [$self->{warp_message}->(@{$p->{message}})];
180        }
181
182        $p->{message} = $self->{layout}->render($p->{message},
183            $category,
184            $level,
185            3 + $Log::Log4perl::caller_depth,
186        ) if $self->layout();
187    }
188
189    $self->{appender}->log(%$p,
190                            #these are used by our Appender::DBI
191                            log4p_category => $category,
192                            log4p_level    => $level,
193                          );
194    return 1;
195}
196
197##################################################
198sub name { # Set/Get the name
199##################################################
200    my($self, $name) = @_;
201
202        # Somebody wants to *set* the name?
203    if($name) {
204        $self->{name} = $name;
205    }
206
207    return $self->{name};
208}
209
210###########################################
211sub layout { # Set/Get the layout object
212             # associated with this appender
213###########################################
214    my($self, $layout) = @_;
215
216        # Somebody wants to *set* the layout?
217    if($layout) {
218        $self->{layout} = $layout;
219
220        # somebody wants a layout, but not set yet, so give 'em default
221    }elsif (! $self->{layout}) {
222        $self->{layout} = Log::Log4perl::Layout::SimpleLayout
223                                                ->new($self->{name});
224
225    }
226
227    return $self->{layout};
228}
229
230##################################################
231sub filter { # Set filter
232##################################################
233    my ($self, $filter) = @_;
234
235    if($filter) {
236        print "Setting filter to $filter->{name}\n" if _INTERNAL_DEBUG;
237        $self->{filter} = $filter;
238    }
239
240    return $self->{filter};
241}
242
243##################################################
244sub AUTOLOAD {
245##################################################
246# Relay everything else to the underlying
247# Log::Log4perl::Appender::* or Log::Dispatch::*
248#  object
249##################################################
250    my $self = shift;
251
252    no strict qw(vars);
253
254    $AUTOLOAD =~ s/.*:://;
255
256    return $self->{appender}->$AUTOLOAD(@_);
257}
258
259##################################################
260sub DESTROY {
261##################################################
262    foreach my $key (keys %{$_[0]}) {
263        # print "deleting $key\n";
264        delete $_[0]->{$key};
265    }
266}
267
2681;
269
270__END__
271
272=head1 NAME
273
274Log::Log4perl::Appender - Log appender class
275
276=head1 SYNOPSIS
277
278  use Log::Log4perl;
279
280      # Define a logger
281  my $logger = Log::Log4perl->get_logger("abc.def.ghi");
282
283      # Define a layout
284  my $layout = Log::Log4perl::Layout::PatternLayout->new(
285                   "%d (%F:%L)> %m");
286
287      # Define an appender
288  my $appender = Log::Log4perl::Appender->new(
289                   "Log::Log4perl::Appender::Screen",
290                   name => 'dumpy');
291
292      # Set the appender's layout
293  $appender->layout($layout);
294  $logger->add_appender($appender);
295
296=head1 DESCRIPTION
297
298This class is a wrapper around the C<Log::Log4perl::Appender>
299appender set.
300
301It also supports the <Log::Dispatch::*> collections of appenders. The
302module hides the idiosyncrasies of C<Log::Dispatch> (e.g. every
303dispatcher gotta have a name, but there's no accessor to retrieve it)
304from C<Log::Log4perl> and yet re-uses the extremely useful variety of
305dispatchers already created and tested in C<Log::Dispatch>.
306
307=head1 FUNCTIONS
308
309=head2 Log::Log4perl::Appender->new($dispatcher_class_name, ...);
310
311The constructor C<new()> takes the name of the appender
312class to be created as a I<string> (!) argument, optionally followed by
313a number of appender-specific parameters,
314for example:
315
316      # Define an appender
317  my $appender = Log::Log4perl::Appender->new(
318      "Log::Log4perl::Appender::File"
319      filename => 'out.log');
320
321In case of C<Log::Dispatch> appenders,
322if no C<name> parameter is specified, the appender object will create
323a unique one (format C<appNNN>), which can be retrieved later via
324the C<name()> method:
325
326  print "The appender's name is ", $appender->name(), "\n";
327
328Other parameters are specific to the appender class being used.
329In the case above, the C<filename> parameter specifies the name of
330the C<Log::Log4perl::Appender::File> dispatcher used.
331
332However, if, for instance,
333you're using a C<Log::Dispatch::Email> dispatcher to send you
334email, you'll have to specify C<from> and C<to> email addresses.
335Every dispatcher is different.
336Please check the C<Log::Dispatch::*> documentation for the appender used
337for details on specific requirements.
338
339The C<new()> method will just pass these parameters on to a newly created
340C<Log::Dispatch::*> object of the specified type.
341
342When it comes to logging, the C<Log::Log4perl::Appender> will transparently
343relay all messages to the C<Log::Dispatch::*> object it carries
344in its womb.
345
346=head2 $appender->layout($layout);
347
348The C<layout()> method sets the log layout
349used by the appender to the format specified by the
350C<Log::Log4perl::Layout::*> object which is passed to it as a reference.
351Currently there's two layouts available:
352
353    Log::Log4perl::Layout::SimpleLayout
354    Log::Log4perl::Layout::PatternLayout
355
356Please check the L<Log::Log4perl::Layout::SimpleLayout> and
357L<Log::Log4perl::Layout::PatternLayout> manual pages for details.
358
359=head1 Supported Appenders
360
361Here's the list of appender modules currently available via C<Log::Dispatch>,
362if not noted otherwise, written by Dave Rolsky:
363
364       Log::Dispatch::ApacheLog
365       Log::Dispatch::DBI (by Tatsuhiko Miyagawa)
366       Log::Dispatch::Email,
367       Log::Dispatch::Email::MailSend,
368       Log::Dispatch::Email::MailSendmail,
369       Log::Dispatch::Email::MIMELite
370       Log::Dispatch::File
371       Log::Dispatch::FileRotate (by Mark Pfeiffer)
372       Log::Dispatch::Handle
373       Log::Dispatch::Screen
374       Log::Dispatch::Syslog
375       Log::Dispatch::Tk (by Dominique Dumont)
376
377C<Log4perl> doesn't care which ones you use, they're all handled in
378the same way via the C<Log::Log4perl::Appender> interface.
379Please check the well-written manual pages of the
380C<Log::Dispatch> hierarchy on how to use each one of them.
381
382=head1 Parameters passed on to the appender's log() method
383
384When calling the appender's log()-Funktion, Log::Log4perl will
385submit a list of key/value pairs. Entries to the following keys are
386guaranteed to be present:
387
388=over 4
389
390=item message
391
392Text of the rendered message
393
394=item log4p_category
395
396Name of the category of the logger that triggered the event.
397
398=item log4p_level
399
400Log::Log4perl level of the event
401
402=back
403
404=head1 Pitfalls
405
406Since the C<Log::Dispatch::File> appender truncates log files by default,
407and most of the time this is I<not> what you want, we've instructed
408C<Log::Log4perl> to change this behaviour by slipping it the
409C<mode =E<gt> append> parameter behind the scenes. So, effectively
410with C<Log::Log4perl> 0.23, a configuration like
411
412    log4perl.category = INFO, FileAppndr
413    log4perl.appender.FileAppndr          = Log::Dispatch::File
414    log4perl.appender.FileAppndr.filename = test.log
415    log4perl.appender.FileAppndr.layout   = Log::Log4perl::Layout::SimpleLayout
416
417will always I<append> to an existing logfile C<test.log> while if you
418specifically request clobbering like in
419
420    log4perl.category = INFO, FileAppndr
421    log4perl.appender.FileAppndr          = Log::Dispatch::File
422    log4perl.appender.FileAppndr.filename = test.log
423    log4perl.appender.FileAppndr.mode     = write
424    log4perl.appender.FileAppndr.layout   = Log::Log4perl::Layout::SimpleLayout
425
426it will overwrite an existing log file C<test.log> and start from scratch.
427
428=head1 Appenders Expecting Message Chunks
429
430Instead of simple strings, certain appenders are expecting multiple fields
431as log messages. If a statement like
432
433    $logger->debug($ip, $user, "signed in");
434
435causes an off-the-shelf C<Log::Log4perl::Screen>
436appender to fire, the appender will
437just concatenate the three message chunks passed to it
438in order to form a single string.
439The chunks will be separated by a string defined in
440C<$Log::Log4perl::JOIN_MSG_ARRAY_CHAR> (defaults to the empty string
441"").
442
443However, different appenders might choose to
444interpret the message above differently: An
445appender like C<Log::Log4perl::Appender::DBI> might take the
446three arguments passed to the logger and put them in three separate
447rows into the DB.
448
449The  C<warp_message> appender option is used to specify the desired
450behaviour.
451If no setting for the appender property
452
453    # *** Not defined ***
454    # log4perl.appender.SomeApp.warp_message
455
456is defined in the Log4perl configuration file, the
457appender referenced by C<SomeApp> will fall back to the standard behaviour
458and join all message chunks together, separating them by
459C<$Log::Log4perl::JOIN_MSG_ARRAY_CHAR>.
460
461If, on the other hand, it is set to a false value, like in
462
463    log4perl.appender.SomeApp.layout=NoopLayout
464    log4perl.appender.SomeApp.warp_message = 0
465
466then the message chunks are passed unmodified to the appender as an
467array reference. Please note that you need to set the appender's
468layout to C<Log::Log4perl::Layout::NoopLayout> which just leaves
469the messages chunks alone instead of formatting them or replacing
470conversion specifiers.
471
472B<Please note that the standard appenders in the Log::Dispatch hierarchy
473will choke on a bunch of messages passed to them as an array reference.
474You can't use C<warp_message = 0> (or the function name syntax
475defined below) on them.
476Only special appenders like Log::Log4perl::Appender::DBI can deal with
477this.>
478
479If (and now we're getting fancy)
480an appender expects message chunks, but we would
481like to pre-inspect and probably modify them before they're
482actually passed to the appender's C<log>
483method, an inspection subroutine can be defined with the
484appender's C<warp_message> property:
485
486    log4perl.appender.SomeApp.layout=NoopLayout
487    log4perl.appender.SomeApp.warp_message = sub { \
488                                           $#_ = 2 if @_ > 3; \
489                                           return @_; }
490
491The inspection subroutine defined by the C<warp_message>
492property will receive the list of message chunks, like they were
493passed to the logger and is expected to return a corrected list.
494The example above simply limits the argument list to a maximum of
495three by cutting off excess elements and returning the shortened list.
496
497Also, the warp function can be specified by name like in
498
499    log4perl.appender.SomeApp.layout=NoopLayout
500    log4perl.appender.SomeApp.warp_message = main::filter_my_message
501
502In this example,
503C<filter_my_message> is a function in the C<main> package,
504defined like this:
505
506    my $COUNTER = 0;
507
508    sub filter_my_message {
509        my @chunks = @_;
510        unshift @chunks, ++$COUNTER;
511        return @chunks;
512    }
513
514The subroutine above will add an ever increasing counter
515as an additional first field to
516every message passed to the C<SomeApp> appender -- but not to
517any other appender in the system.
518
519=head1 SEE ALSO
520
521Log::Dispatch
522
523=head1 AUTHOR
524
525Mike Schilli, E<lt>log4perl@perlmeister.comE<gt>
526
527=cut
528