Update: code and POD updated based on suggestions as of 2006-Feb-23
I have been a fan of Log::Log4Perl for a long time, as it's one of the most comprehensive, configurable, and just generally nifty loggers out there. Sometimes, though, it's a bit too much, so there are things like Log::Simple to fill the gap.
What I really wanted, though, was a logging facility that wouldn't get in my way, even if it was only suitable for small and/or simple scripts.
What I came up with is a module that I temporarily just named 'Log' (yes, I know, horrible namespace choice), and it's here for some peer review. The goals were:
I gave in to prototypes to make the interface mimic that of warn:
info 'This is an info log item'. debug "the value of this variable is: '$this'"
Custom import function that hooks into $SIG{__DIE__} and $SIG{__WARN__} provides drop-in logging of those events, while propagating them. Any code that use warn or die (unless it has its own custom signal handlers that don't chain) gets logging of those functions just by use Log. (Again with the bad namespace, I know, I know).
Custom import function allows for one-line setup:
# log die, warn, info, and debug events to mylog.log, but don't # show debug events on the console. use Log ( file=>'mylog.log', warn => 2, info => 2, debug => 1 );
No XS, and no dependencies on modules outside the Perl core distribution.
Without any parameters to use Log, the logfile name is the name of the file from which use Log is called with '.log' appended ('myscript.pl' logs to 'myscript.pl.log').
The logging level defaults in such a way that all die, warn, and info events are logged, and die and warn events are propagated; trace and debug are skipped. Die events are always logged and propagated (well, unless someone customizes a signal handler that bypasses this module).
I'm sure there is much more (I am still significantly short of any kind of Perl Master status!), and I'd like to know about the issues, considerations, suggestions for improvement, wheels I might be reinventing (even though I did look through CPAN, I miss things all the time...), etc.
All I ask is: (a)please no name-space discussions, I know 'Log' is a horrible choice; and (b)please be a little verbose explaining issues/suggesting alternatives -- if I suck, that's fine, but I want to understand how I suck and how to stop. ;-)
Thanks in advance!
package Log; #===================================================================== +========== # $Id: Log.pm,v 1.8 2006-02-22 20:05:16 radmat Exp $ # Logging package for info/warn/die/debug/trace #===================================================================== +========== use strict; use warnings; our $VERSION = sprintf "%d.%03d", q$Revision: 1.8 $ =~ /: (\d+)\.(\d+) +/; #CVS #__modules__ use Fcntl; use POSIX qw[_exit strftime]; #use subs qw[warn die]; require Exporter; our @ISA = 'Exporter'; our @EXPORT = qw[info debug trace]; #__globals__ our $logfile = ( caller(0) )[0] . '.log'; # Default to main.log our ( $debug, $trace ) = ( 0, 0 ); # No logging of debug() a +nd trace() our ( $info, $warn ) = ( 1, 2 ); # Log info(), Log and pri +nt warn() our $seq = 0; # Session sequence our $line_format = '%s %d.%04d %s'; # Line format (see POD) our $time_format = '%Y-%m-%dT%H:%M:%S'; # POSIX strftime format our $log_umask = 0111; #__subroutines__ #_____________________________________________________________________ + import()_ sub import { # import - override Exporter->import my $me = shift; if ( @_ == 1 ) { $logfile = shift } elsif ( @_ % 2 == 0 ) { my %parm = @_; $logfile = $parm{file} if defined $parm{file}; $debug = $parm{debug} if defined $parm{debug}; $trace = $parm{trace} if defined $parm{trace}; $info = $parm{info} if defined $parm{info}; $warn = $parm{'warn'} if defined $parm{'warn'}; $line_format = $parm{lineform} if defined $parm{lineform}; $time_format = $parm{timeform} if defined $parm{timeform}; $log_umask = $parm{'umask'} if defined $parm{'umask'}; } else { } # TODO ? strings subs for 0/1/2 # TODO per-package configuration umask $log_umask; sysopen( LOG, $logfile, O_WRONLY | O_APPEND | O_CREAT, 0777 ) or die("Cannot log to $logfile: $!"); select LOG; $| = 1; select STDOUT; $SIG{__WARN__} = sub { Log::warn(@_) }; $SIG{__DIE__} = sub { Log::die(@_) }; $me->export_to_level( 1, $me, @EXPORT ); print LOG "\n"; info('Logging started'); } #_____________________________________________________________________ +__ _msg()_ sub _msg { # _msg ( \@msgarray ) my @a = @{ $_[0] }; foreach (@a) { unless ( defined $_ ) { $_ = ''; next; } s/[\r\n]+/ /g; } return join( ' ', @a ); } #_____________________________________________________________________ +__ _log()_ sub _log { # _log ( ) my $time = strftime( $time_format, localtime ); printf LOG $line_format."\n", $time, $$, $seq++, _msg( \@_ ); } #_____________________________________________________________________ +__ warn()_ sub warn ($) { # warn ( ) _log( 'W:', @_ ) if $warn; CORE::warn(@_) if $warn == 2; } #_____________________________________________________________________ +___ die()_ sub die ($) { # die ( ) _log( 'X:', @_ ); CORE::die(@_); } #_____________________________________________________________________ +__ info()_ sub info ($) { # info ( ) return undef unless $info; _log( 'I:', @_ ); print STDERR _msg( \@_ ), "\n" if $info == 2; } #_____________________________________________________________________ +_ debug()_ sub debug ($) { # debug ( ) return undef unless $debug; _log( 'D:', @_ ); print STDERR '. Debug:', _msg( \@_ ), "\n" if $debug == 2; } #_____________________________________________________________________ +_ trace()_ sub trace ($) { # trace ( ) return undef unless $trace; _log( 'T:', @_ ); print STDERR '* Trace:', _msg( \@_ ), "\n" if $trace == 2; } 1; # modules must return a true value. __END__
Log ($Revision: 1.9 $) - Logging package for info/warn/die/debug/trace
use Log; warn 'hello'; info 'this is an information statement'; trace 'this is a trace log item'; debug 'this is a debug log item'; die 'Game over, man: game over!';
Log is a simple, lightweight logging system designed to promote logging in code by requiring a minimum of configuration, automatic logging of warn and die events, and an interface that doesn't get in the way of functional code.
When loaded, Log exports info, debug, and trace, then binds WARN and DIE events to its internal warn and die methods. These latter two end by calling CORE::warn and CORE::die, respectively, so code that depends on the default warn and die behavior will continue to work. This makes Log ideal for dropping into existing code to provide a minimal warn/die log.
Log will always record and propagate DIE events. By default, it will also record WARN events and calls to info; the WARN events will be propagated, while the info calls will be silently logged. This behavior can be altered during import: see import for more.
The default log file name is the package name of the script or module from which Log is imported, appended with '.log'. For example, a script will use a log named main.log. This can be altered during import: see import for more.
To change default behaviors (see SYNOPSIS), parameters can be passed to import as a named-pair hash.
To specify a filename to log to, overriding the default:
use Log ( file => 'myfile.txt' );
To change the level of verbosity for each logging type:
use Log ( warn => 2, # 2 means log and print to console info => 1, # 1 means log only debug => 1, trace => 0, # 0 means ignore these! );
The verbosity of the die type cannot be altered, it is always effectively '2'. Setting warn's level to 1 will prevent warnings from propagating via CORE::warn, but will still log them; setting its value to 0 will entirely supress warnings.
Default values are ( warn=>2, info=>1, debug=>0, trace=>0 ).
To change the format of the line or time-stamp, use 'lineform' and 'timeform', respectively. See LOG FILE FORMAT for more information.
When the log file is created, a mode of 0777 is passed, mitigated by a umask of 0111, resulting in a default mode of 0666 (rw-rw-rw). This can be altered during import using the 'umask' parameter.
use Log ( umask => 0177 );
Note that this is an octal value, not a string or decimal. See the umask documentation for more information.
Custom handlers attached to $SIG{__WARN__} should take care to call Log::warn(@_), or warnings may not be logged. If using such handlers, be sure that $warn < 2.
Even exceptions caught from inside an eval block will be logged; it is a good practice to do something like:
eval { sub_which_could_die(); }; if ($@) { info "Caught: $@" # handle the exception }
In this way, the log will show the exception being thrown and being caught (assuming $info > 0), making for much more sensible reading.
Custom handlers attached to $SIG{__DIE__} should take care to call Log::die(@_), or exceptions may not be logged.
The level of verbosity can be adjusted at run time via $info:
$Log::info = 2; info 'This will be in the log and on the screen'; $Log::info = 1; info 'This will be logged only';
This is not recommended, as it makes it very difficult to adjust these values globally via import.
Accepts a single scalar.
The level of verbosity can be adjusted at run time via $debug; see info for a related example.
Accepts a single scalar.
When activated it, this should be extremely verbose.
The level of verbosity can be adjusted at run time via $trace; see info for a related example.
Accepts a single scalar.
The log file format for Log is designed to be easy to parse with code, yet reasonable for a human to read as well. There is one message per line in the following format (followed by an example):
YYYY-mm-ddTHH:MM:SS <pid>.<seq> TYPE: Message text 2006-02-17T10:00:32 12345.0000 I: Logging started
Each time Log is imported, the logfile has a blank line appended and is issued an info event of 'Logging started' with a sequence number of 0. Dates are in ISO 8601 format without fractional seconds or timezone information.
The TYPE field is one of T, D, I, W, X; these map to trace, debug, info, warn, and die (exception) events, respectively.
The format of this line can be altered using the 'lineform' import instruction. Its value is a printf style format string for the list of values timestamp, pid, seq, message. The format will have a newline appended before use. Be certain not to use tainted user input here!
The format of the timestamp may be altered using the 'timeform' import instruction, similar to 'lineform' above; the format string will be passed to the POSIX strftime function. The warning about tainted user data above applies here as well.
Look for Log::Parser in the future.
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: RFC: A simple and badly-named logging module
by YuckFoo (Abbot) on Feb 17, 2006 at 21:56 UTC | |
by radiantmatrix (Parson) on Feb 22, 2006 at 17:43 UTC | |
Re: RFC: A simple and badly-named logging module
by xdg (Monsignor) on Feb 18, 2006 at 01:47 UTC | |
by radiantmatrix (Parson) on Feb 22, 2006 at 18:03 UTC | |
Re: RFC: A simple and badly-named logging module
by Anonymous Monk on Feb 17, 2006 at 19:54 UTC | |
by revdiablo (Prior) on Feb 17, 2006 at 20:57 UTC | |
Re: RFC: A simple and badly-named logging module
by dynamo (Chaplain) on Feb 17, 2006 at 21:30 UTC |