package Log::StdLog; use version; $VERSION = qv('0.0.3'); use warnings; use strict; use Carp; use base 'IO::File'; my @levels = qw( all trace debug user info warn error fatal none ); my %severity; @severity{@levels} = 1..@levels; # Aliases... $severity{warning} = $severity{warn}; sub _make_formatter { my ($format) = @_; return sub { my ($time, $source, $type, @msg) = @_; my $msg = join q{}, @msg; return sprintf $format, $time, $source, $type, $msg; } } sub import { my ($package, $opt_ref) = @_; my ($caller, $file) = caller; croak "Usage: use $package { file=>\$filename, level=>\$level, format=>sub{...} }\n " if $opt_ref && not ref $opt_ref eq 'HASH'; if (not exists $opt_ref->{file}) { $opt_ref->{file} = "$file.log"; } if (not exists $opt_ref->{format}) { $opt_ref->{format} = _make_formatter("[%s] [%s] [%s] %s"); } elsif (not ref $opt_ref->{format}) { $opt_ref->{format} = _make_formatter($opt_ref->{format}); } if (not exists $opt_ref->{level}) { $opt_ref->{level} = 'user'; } no strict 'refs'; tie *{$caller.'::STDLOG'}, $package, $opt_ref; } sub TIEHANDLE { my ($package, $opt_ref) = @_; return bless { file => $opt_ref->{file}, handle => $opt_ref->{handle}, formatter => $opt_ref->{format}, min_severity_name => $opt_ref->{level} || 'user', min_severity => $severity{$opt_ref->{level}} || $severity{user}, }; } use Fcntl ':flock'; sub PRINT { my ($self, $level, @msg) = @_ == 1 ? ( $_[0], $_[0]->{min_severity_name}, $_ ) : @_ == 2 ? ( $_[0], $_[0]->{min_severity_name}, $_[1] ) : ( @_ ) ; # No-op if message isn't important enough... my $severity = $severity{$level} || $severity{user}; return 0 if $self->{min_severity} > $severity; # Format message early to get accurate time-stamp... my ($sec,$min,$hour,$day,$mon,$year) = localtime; $year+=1900; $mon++; my $time = sprintf("%04d%02d%02d.%02d%02d%02d", $year, $mon, $day, $hour, $min, $sec); $msg[-1] =~ s/\n\z// if @msg; my $log_msg = $self->{formatter}->($time, $$, $level, @msg); # Create connection to log file, if necessary... if (not $self->{handle}) { open $self->{handle}, '>>', $self->{file} or croak "Unable to open log file '$self->{file}'"; } # Synchronize writing to file via advisory locking... flock($self->{handle}, LOCK_EX); my $result = $self->{handle}->print($log_msg."\n"); flock($self->{handle}, LOCK_UN); return $result; } sub CLOSE { my ($self) = @_; $self->{handle}->close(); } 1; # Magic true value required at end of module __END__ =head1 NAME Log::StdLog - A simple log file via a special filehandle =head1 VERSION This document describes Log::StdLog version 0.0.3 =head1 SYNOPSIS use Log::StdLog { level => 'warn', file => "$0.log" }; # Messages at the same or a higher level are logged... print {*STDLOG} error => "This error message will be logged\n"; print {*STDLOG} warn => "This warning message will be logged\n"; # Messages at a lower level are ignored... print {*STDLOG} info => "This info message will NOT be logged\n"; # The default message level is the one that was specified # when the module was loaded... print {*STDLOG} "This is a warning message. It will be logged\n"; =head1 DESCRIPTION This module provides a very simple kind of log file, with a very simple interface. Messages are logged simply by printing to *STDLOG, which the module exports to any namespace into which it's loaded. =head1 INTERFACE The entire interface consists of the options that can be passed when the module is loaded, plus normal C calls via the C<*STDLOG> filehandle. =head2 Load-time configuration When loading Log::StdLog, you can pass up to four arguments to the module to configure it. Those arguments are passed in a single hash: use Log::StdLog { file => $filename, level=>$min_level }; Each is described below. =over =item use Log::StdLog { file => $filename }; Normally, Log::StdLog logs messages to a file named C<"$0.log"> (that is, the current filename with C<.log> appended. But if you pass a C<'file'> option, it uses that file as its logfile instead. =item use Log::StdLog { handle => $filehandle }; Instead of specifying the name of the log file using the C<'file'> option, you can specify a filehandle to which log messages are written, using the C<'handle'> option. If you specify both C<'file'> and C<'handle'>, the C<'handle'> option takes precedence. =item use Log::StdLog { level => $level_name }; The C<'level'> option specifies the minimum level of message to log, as well as the default level of message, when a level isn't specified (see below). The available levels (in decreasing order of severity) are: 'none' 'fatal' 'error' 'warn' 'info' 'user' 'debug' 'trace' 'all' If the C<'level'> option is used, only those messages whose level is at or above the specified level will be printed. All other messages will be silently discarded. If the C<'level'> option is not specified, the level defaults to C<'user'>. =item use Log::StdLog { format => $log_formatter }; Normally, log entries are autoformatted like so: [YYYYMMDD.HHMMSS] [PID] [LEVEL] Message but you can change that default by specifying either a subroutine or a string using the C<'format'> option. If you specify a subroutine reference as the option's value, that subroutine is called every time a message is logged, and is passed four arguments: the date (in YYYYMMDD.HHMMSS format), the current process ID (C<$$>), the level at which the message was logged, and a list of the components of the message itself. The subroutine is expected to return the log text to be printed (I a trailing newline in the text). So, for example, to implement a new logging format: LEVEL (YYYYMMDD.HHMMSS/PID): Message you could specify: sub new_format { my ($date, $pid, $level, @message) = @_ return "$level ($date/$pid): " . join(q{}, @message); } use Log::StdLog { format => \&new_format }; Alternatively, you can specify the new logging format as a simple string. That string will be passed to C and can use C<'%s'> or other escapes to interpolate the date, pid, level, and message text (which are passed to the C call in that order). So, for example, to implement a new logging format: /YYYYMMDD.HHMMSS/PID/LEVEL/ << Message >> you could specify: use Log::StdLog { format => '/%s/%s/%s/ << %s >>' }; =back =head2 Logging via C To write to the log file provided by Log::StdLog, you simple print to the special filehandle C<*STDLOG>: print {*STDLOG} warn => 'Danger, Will Robinson!!!'; The first argument after the filehandle is treated as the severity level of the log message, and used to determine whether a log entry is actually generated (depending on the minimum severity level you set when loading the module). If the level is appropriate, then the message is printed (with a newline added if necessary) to the logfile, in either the format you specified when loading the module, or the default format (see earlier). If only a single argument is specified after the filehandle: print {*STDLOG} 'Danger, Will Robinson!!!'; then it is treated as a message without an explicit level, and the current minimum level (default or user-specified) is used. This means that the message is always printed. If no argument is specified: print {*STDLOG} (); then the current value of C<$_> is used as the message. In all cases, the log file is updated atomically, by exclusively locking the file before the C statement executes. Note that this means a C will block until the file becomes available for writing. Future versions of this module may employ other synchronization strategies, including allowing the logging attempt to fail after a timeout. =head1 DIAGNOSTICS =over =item C<< Usage: use Log::StdLog { file=>$filename, level=>$level, format=>sub{...} } >> You loaded the module, but passed it an argument it didn't understand. The only argument the module can be passed when it's loaded is a hash reference, with the keys shown. =item C<< Unable to open log file '%s' >> The logfile specified when the module was loaded cannot be opened for appending. This is usually either a permissions problem or a misspecified filepath. =back =head1 CONFIGURATION AND ENVIRONMENT Log::StdLog requires no configuration files or environment variables. =head1 DEPENDENCIES This module requires the following other modules: =over =item * IO::File =item * Fcntl =item * version =back =head1 INCOMPATIBILITIES None reported. =head1 BUGS AND LIMITATIONS No bugs have been reported. Please report any bugs or feature requests to C, or through the web interface at L. =head1 AUTHOR Damian Conway C<< >> =head1 LICENCE AND COPYRIGHT Copyright (c) 2005, Damian Conway C<< >>. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 DISCLAIMER OF WARRANTY BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.