1########################################################################### 2# 3# File.pm 4# 5# Copyright (C) 1999 Raphael Manfredi. 6# Copyright (C) 2002-2017 Mark Rogaski, mrogaski@cpan.org; 7# all rights reserved. 8# 9# See the README file included with the 10# distribution for license information. 11# 12########################################################################## 13 14use strict; 15require Log::Agent::Driver; 16 17######################################################################## 18package Log::Agent::Driver::File; 19 20use vars qw(@ISA); 21 22@ISA = qw(Log::Agent::Driver); 23 24# 25# ->make -- defined 26# 27# Creation routine. 28# 29# Attributes (and switches that set them): 30# 31# prefix the application name 32# duperr whether to duplicate "error" channels to "output" 33# stampfmt stamping format ("syslog", "date", "own", "none") or closure 34# showpid whether to show pid after prefix in [] 35# channels where each channel ("error", "output", "debug") goes 36# chanperm what permissions each channel ("error", "output", "debug") has 37# magic_open flag to tell whether ">>file" or "|proc" are allowed filenames 38# rotate default rotating policy for logfiles 39# 40# Additional switches: 41# 42# file sole channel, implies -duperr = 0 and supersedes -channels 43# perm file permissions that supersedes all channel permissions 44# 45# Other attributes: 46# 47# channel_obj opened channel objects 48# 49sub make { 50 my $self = bless {}, shift; 51 my (%args) = @_; 52 my $prefix; 53 my $file; 54 my $perm; 55 56 my %set = ( 57 -prefix => \$prefix, # Handled by parent via _init 58 -duperr => \$self->{'duperr'}, 59 -channels => \$self->{'channels'}, 60 -chanperm => \$self->{'chanperm'}, 61 -stampfmt => \$self->{'stampfmt'}, 62 -showpid => \$self->{'showpid'}, 63 -magic_open => \$self->{'magic_open'}, 64 -file => \$file, 65 -perm => \$perm, 66 -rotate => \$self->{'rotate'}, 67 ); 68 69 while (my ($arg, $val) = each %args) { 70 my $vset = $set{lc($arg)}; 71 unless (ref $vset) { 72 require Carp; 73 Carp::croak("Unknown switch $arg"); 74 } 75 $$vset = $val; 76 } 77 78 # 79 # If -file was used, it supersedes -duperr and -channels 80 # 81 82 if (defined $file && length $file) { 83 $self->{'channels'} = { 84 'debug' => $file, 85 'output' => $file, 86 'error' => $file, 87 }; 88 $self->{'duperr'} = 0; 89 } 90 91 # 92 # and we do something similar for file permissions 93 # 94 95 if (defined $perm && length $perm) { 96 $self->{chanperm} = { 97 debug => $perm, 98 output => $perm, 99 error => $perm 100 }; 101 } 102 103 $self->_init($prefix, 0); # 1 is the skip Carp penalty for confess 104 105 $self->{channels} = {} unless $self->channels; # No defined channels 106 $self->{chanperm} = {} unless $self->chanperm; # No defined perms 107 $self->{channel_obj} = {}; # No opened files 108 109 # 110 # Check for logfile rotation, which can be specified on a global or 111 # file by file basis. Since Log::Agent::Rotate is a separate extension, 112 # it may not be installed. 113 # 114 115 my $use_rotate = defined($self->rotate) ? 1 : 0; 116 unless ($use_rotate) { 117 foreach my $chan (keys %{$self->channels}) { 118 $use_rotate = 1 if ref $self->channels->{$chan} eq 'ARRAY'; 119 last if $use_rotate; 120 } 121 } 122 123 if ($use_rotate) { 124 eval { 125 require Log::Agent::File::Rotate; 126 }; 127 if ($@) { 128 warn $@; 129 require Carp; 130 Carp::croak("Must install Log::Agent::Rotate to use rotation"); 131 } 132 } 133 134 return $self; 135} 136 137# 138# Attribute access 139# 140 141sub duperr { $_[0]->{duperr} } 142sub channels { $_[0]->{channels} } 143sub chanperm { $_[0]->{chanperm} } 144sub channel_obj { $_[0]->{channel_obj} } 145sub stampfmt { $_[0]->{stampfmt} } 146sub showpid { $_[0]->{showpid} } 147sub magic_open { $_[0]->{magic_open} } 148sub rotate { $_[0]->{rotate} } 149 150# 151# ->prefix_msg -- defined 152# 153# NOP: channel handles prefixing for us. 154# 155sub prefix_msg { 156 my $self = shift; 157 return $_[0]; 158} 159 160# 161# ->chanfn 162# 163# Return channel file name. 164# 165sub chanfn { 166 my $self = shift; 167 my ($channel) = @_; 168 my $filename = $self->channels->{$channel}; 169 if (ref $filename eq 'ARRAY') { 170 $filename = $filename->[0]; 171 } 172 # No channel defined, use 'error' 173 $filename = $self->channels->{'error'} unless 174 defined $filename && length $filename; 175 $filename = '<STDERR>' unless defined $filename; 176 177 return $filename; 178} 179 180# 181# ->channel_eq -- defined 182# 183# Compare two channels. 184# 185# It's hard to know for certain that two channels are equivalent, so we 186# compare filenames. This is not correct, of course, but it will do for 187# what we're trying to achieve here, namely avoid duplicates if possible 188# when traces are remapped to Carp::Datum. 189# 190sub channel_eq { 191 my $self = shift; 192 my ($chan1, $chan2) = @_; 193 my $fn1 = $self->chanfn($chan1); 194 my $fn2 = $self->chanfn($chan2); 195 return $fn1 eq $fn2; 196} 197 198# 199# ->write -- defined 200# 201sub write { 202 my $self = shift; 203 my ($channel, $priority, $logstring) = @_; 204 my $chan = $self->channel($channel); 205 return unless $chan; 206 207 $chan->write($priority, $logstring); 208} 209 210# 211# ->channel 212# 213# Return channel object (one of the Log::Agent::Channel::* objects) 214# 215sub channel { 216 my $self = shift; 217 my ($name) = @_; 218 my $obj = $self->channel_obj->{$name}; 219 $obj = $self->open_channel($name) unless $obj; 220 return $obj; 221} 222 223 224# 225# ->open_channel 226# 227# Open given channel according to the configured channel description and 228# return the object file descriptor. 229# 230# If no channel of that name was defined, use 'error' or STDERR. 231# 232sub open_channel { 233 my $self = shift; 234 my ($name) = @_; 235 my $filename = $self->channels->{$name}; 236 237 # 238 # Handle possible logfile rotation, which may be defined globally 239 # or on a file by file basis. 240 # 241 242 my $rotate; # A Log::Agent::Rotate object 243 if (ref $filename eq 'ARRAY') { 244 ($filename, $rotate) = @$filename; 245 } else { 246 $rotate = $self->rotate; 247 } 248 249 my @common_args = ( 250 -prefix => $self->prefix, 251 -stampfmt => $self->stampfmt, 252 -showpid => $self->showpid, 253 ); 254 my @other_args; 255 my $type; 256 257 # 258 # No channel defined, use 'error', or revert to STDERR 259 # 260 261 unless (defined $filename && length $filename) { 262 $filename = $self->channels->{'error'}; 263 ($filename, $rotate) = @$filename if ref $filename eq 'ARRAY'; 264 } 265 266 unless (defined $filename && length $filename) { 267 require Log::Agent::Channel::Handle; 268 select((select(main::STDERR), $| = 1)[0]); 269 $type = "Log::Agent::Channel::Handle"; 270 @other_args = (-handle => \*main::STDERR); 271 } else { 272 require Log::Agent::Channel::File; 273 $type = "Log::Agent::Channel::File"; 274 @other_args = ( 275 -filename => $filename, 276 -magic_open => $self->magic_open, 277 -share => 1, 278 ); 279 push(@other_args, -fileperm => $self->chanperm->{$name}) 280 if $self->chanperm->{$name}; 281 push(@other_args, -rotate => $rotate) if ref $rotate; 282 } 283 284 return $self->channel_obj->{$name} = 285 $type->make(@common_args, @other_args); 286} 287 288# 289# ->emit_output 290# 291# Force error message to the regular 'output' channel with a specified tag. 292# 293sub emit_output { 294 my $self = shift; 295 my ($prio, $tag, $str) = @_; 296 my $cstr = $str->clone; # We're prepending tag on a copy 297 $cstr->prepend("$tag: "); 298 $self->write('output', $prio, $cstr); 299} 300 301### 302### Redefined routines to handle duperr 303### 304 305# 306# ->logconfess 307# 308# When `duperr' is true, emit message on the 'output' channel prefixed 309# with FATAL. 310# 311sub logconfess { 312 my $self = shift; 313 my ($str) = @_; 314 $self->emit_output('critical', "FATAL", $str) if $self->duperr; 315 $self->SUPER::logconfess($str); # Carp strips calls within hierarchy 316} 317 318# 319# ->logxcroak 320# 321# When `duperr' is true, emit message on the 'output' channel prefixed 322# with FATAL. 323# 324sub logxcroak { 325 my $self = shift; 326 my ($offset, $str) = @_; 327 my $msg = Log::Agent::Message->make( 328 $self->carpmess($offset, $str, \&Carp::shortmess) 329 ); 330 $self->emit_output('critical', "FATAL", $msg) if $self->duperr; 331 332 # 333 # Carp strips calls within hierarchy, so that new call should not show, 334 # there's no need to adjust the frame offset. 335 # 336 $self->SUPER::logdie($msg); 337} 338 339# 340# ->logdie 341# 342# When `duperr' is true, emit message on the 'output' channel prefixed 343# with FATAL. 344# 345sub logdie { 346 my $self = shift; 347 my ($str) = @_; 348 $self->emit_output('critical', "FATAL", $str) if $self->duperr; 349 $self->SUPER::logdie($str); 350} 351 352# 353# ->logerr 354# 355# When `duperr' is true, emit message on the 'output' channel prefixed 356# with ERROR. 357# 358sub logerr { 359 my $self = shift; 360 my ($str) = @_; 361 $self->emit_output('error', "ERROR", $str) if $self->duperr; 362 $self->SUPER::logerr($str); 363} 364 365# 366# ->logcluck 367# 368# When `duperr' is true, emit message on the 'output' channel prefixed 369# with WARNING. 370# 371sub logconfess { 372 my $self = shift; 373 my ($str) = @_; 374 $self->emit_output('warning', "WARNING", $str) if $self->duperr; 375 $self->SUPER::logcluck($str); # Carp strips calls within hierarchy 376} 377 378# 379# ->logwarn 380# 381# When `duperr' is true, emit message on the 'output' channel prefixed 382# with WARNING. 383# 384sub logwarn { 385 my $self = shift; 386 my ($str) = @_; 387 $self->emit_output('warning', "WARNING", $str) if $self->duperr; 388 $self->SUPER::logwarn($str); 389} 390 391# 392# ->logxcarp 393# 394# When `duperr' is true, emit message on the 'output' channel prefixed 395# with WARNING. 396# 397sub logxcarp { 398 my $self = shift; 399 my ($offset, $str) = @_; 400 my $msg = Log::Agent::Message->make( 401 $self->carpmess($offset, $str, \&Carp::shortmess) 402 ); 403 $self->emit_output('warning', "WARNING", $msg) if $self->duperr; 404 $self->SUPER::logwarn($msg); 405} 406 407# 408# ->DESTROY 409# 410# Close all opened channels, so they may be removed from the common pool. 411# 412sub DESTROY { 413 my $self = shift; 414 my $channel_obj = $self->channel_obj; 415 return unless defined $channel_obj; 416 foreach my $chan (values %$channel_obj) { 417 $chan->close if defined $chan; 418 } 419} 420 4211; # for require 422__END__ 423 424=head1 NAME 425 426Log::Agent::Driver::File - file logging driver for Log::Agent 427 428=head1 SYNOPSIS 429 430 use Log::Agent; 431 require Log::Agent::Driver::File; 432 433 my $driver = Log::Agent::Driver::File->make( 434 -prefix => "prefix", 435 -duperr => 1, 436 -stampfmt => "own", 437 -showpid => 1, 438 -magic_open => 0, 439 -channels => { 440 error => '/tmp/output.err', 441 output => 'log.out', 442 debug => '../appli.debug', 443 }, 444 -chanperm => { 445 error => 0777, 446 output => 0666, 447 debug => 0644 448 } 449 ); 450 logconfig(-driver => $driver); 451 452=head1 DESCRIPTION 453 454The file logging driver redirects logxxx() operations to specified files, 455one per channel usually (but channels may go to the same file). 456 457The creation routine make() takes the following arguments: 458 459=over 4 460 461=item C<-channels> => I<hash ref> 462 463Specifies where channels go. The supplied hash maps channel names 464(C<error>, C<output> and C<debug>) to filenames. When C<-magic_open> is 465set to true, filenames are allowed magic processing via perl's open(), so 466this allows things like: 467 468 -channels => { 469 'error' => '>&FILE', 470 'output' => '>newlog', # recreate each time, don't append 471 'debug' => '|mailx -s whatever user', 472 } 473 474If a channel (e.g. 'output') is not specified, it will go to the 'error' 475channel, and if that one is not specified either, it will go to STDERR instead. 476 477If you have installed the additional C<Log::Agent::Rotate> module, it is 478also possible to override any default rotating policy setup via the C<-rotate> 479argument: instead of supplying the channel as a single string, use an array 480reference where the first item is the channel file, and the second one is 481the C<Log::Agent::Rotate> configuration: 482 483 my $rotate = Log::Agent::Rotate->make( 484 -backlog => 7, 485 -unzipped => 2, 486 -max_write => 100_000, 487 -is_alone => 1, 488 ); 489 490 my $driver = Log::Agent::Driver::File->make( 491 ... 492 -channels => { 493 'error' => ['errors', $rotate], 494 'output' => ['output, $rotate], 495 'debug' => ['>&FILE, $rotate], # WRONG 496 }, 497 -magic_open => 1, 498 ... 499 ); 500 501In the above example, the rotation policy for the C<debug> channel will 502not be activated, since the channel is opened via a I<magic> method. 503See L<Log::Agent::Rotate> for more details. 504 505=item C<-chanperm> => I<hash ref> 506 507Specifies the file permissions for the channels specified by C<-channels>. 508The arguemtn is a hash ref, indexed by channel name, with numeric values. 509This option is only necessary to override the default permissions used by 510Log::Agent::Channel::File. It is generally better to leave these 511permissive and rely on the user's umask. 512See L<perlfunc(3)/umask> for more details.. 513 514=item C<-duperr> => I<flag> 515 516When true, all messages normally sent to the C<error> channel are also 517copied to the C<output> channel with a prefixing made to clearly mark 518them as such: "FATAL: " for logdie(), logcroak() and logconfess(), 519"ERROR: " for logerr() and "WARNING: " for logwarn(). 520 521Note that the "duplicate" is the original error string for logconfess() 522and logcroak(), and is not strictly identical to the message that will be 523logged to the C<error> channel. This is a an accidental feature. 524 525Default is false. 526 527=item C<-file> => I<file> 528 529This switch supersedes both C<-duperr> and C<-channels> by defining a 530single file for all the channels. 531 532=item C<-perm> => I<perm> 533 534This switch supersedes C<-chanperm> by defining consistent for all 535the channels. 536 537=item C<-magic_open> => I<flag> 538 539When true, channel filenames beginning with '>' or '|' are opened using 540Perl's open(). Otherwise, sysopen() is used, in append mode. 541 542Default is false. 543 544=item C<-prefix> => I<prefix> 545 546The application prefix string to prepend to messages. 547 548=item C<-rotate> => I<object> 549 550This sets a default logfile rotation policy. You need to install the 551additional C<Log::Agent::Rotate> module to use this switch. 552 553I<object> is the C<Log::Agent::Rotate> instance describing the default 554policy for all the channels. Only files which are not opened via a 555so-called I<magic open> can be rotated. 556 557=item C<-showpid> => I<flag> 558 559If set to true, the PID of the process will be appended within square 560brackets after the prefix, to all messages. 561 562Default is false. 563 564=item C<-stampfmt> => (I<name> | I<CODE>) 565 566Specifies the time stamp format to use. By default, my "own" format is used. 567The following formats are available: 568 569 date "[Fri Oct 22 16:23:10 1999]" 570 none 571 own "99/10/22 16:23:10" 572 syslog "Oct 22 16:23:10". 573 574You may also specify a CODE ref: that routine will be called every time 575we need to compute a time stamp. It should not expect any parameter, and 576should return a string. 577 578=back 579 580=head1 CHANNELS 581 582All the channels go to the specified files. If a channel is not configured, 583it is redirected to 'error', or STDERR if no 'error' channel was configured 584either. 585 586Two channels not opened via a I<magic> open and whose logfile name is the 587same are effectively I<shared>, i.e. the same file descriptor is used for 588both of them. If you supply distinct rotation policies (e.g. by having a 589default policy, and supplying another policy to one of the channel only), 590then the final rotation policy will depend on which one was opened first. 591So don't do that. 592 593=head1 CAVEAT 594 595Beware of chdir(). If your program uses chdir(), you should always specify 596logfiles by using absolute paths, otherwise you run the risk of having 597your relative paths become invalid: there is no anchoring done at the time 598you specify them. This is especially true when configured for rotation, 599since the logfiles are recreated as needed and you might end up with many 600logfiles scattered throughout all the directories you chdir()ed to. 601 602Logging channels with the same pathname are shared, i.e. they are only 603opened once by C<Log::Agent::Driver::File>. Therefore, if you specify 604different rotation policy to such channels, the channel opening order will 605determine which of the policies will be used for all such shared channels. 606Such errors are flagged at runtime with the following message: 607 608 Rotation for 'logfile' may be wrong (shared with distinct policies) 609 610emitted in the logs upon subsequent sharing. 611 612=head1 AUTHORS 613 614Originally written by Raphael Manfredi E<lt>Raphael_Manfredi@pobox.comE<gt>, 615currently maintained by Mark Rogaski E<lt>mrogaski@cpan.orgE<gt>. 616 617Thanks to Joseph Pepin for suggesting the file permissions arguments 618to make(). 619 620=head1 LICENSE 621 622Copyright (C) 1999 Raphael Manfredi. 623Copyright (C) 2002 Mark Rogaski; all rights reserved. 624 625See L<Log::Agent(3)> or the README file included with the distribution for 626license information. 627 628=head1 SEE ALSO 629 630Log::Agent::Driver(3), Log::Agent(3), Log::Agent::Rotate(3). 631 632=cut 633