Logit.pm

From Glabwiki

This is a class to allow logging from a perl script. It requires a filename to log to, and features different warning levels and time-stamps. Use perldoc for more info.

package Logit;
# See documentation at end of module


##############################################################################
# E X T E R N A L   I N C L U D E S
##############################################################################


use 5.6.1;
    # Limit to only recent version of perl not for a specific reason, merely
    # a young program's fear of the old ones.

use version; our $VERSION = qv('0.0.1');
    # On same line on purpose, makes sure that the qv object is understood
    # by instalation programs like ExtUtils::MakeMaker and Module::Build.
    # May be unnecessary in the future if qv objects widely supported.

use warnings;
    # Help me find possible mistakes.

use strict;
    # Don't let me be sloppy.

use Carp;
    # Return error messages relative to the caller. Who cares where in me
    # the error occured. I should be perfect.

use Perl6::Export::Attrs;
    # Use the new subroutine attribute paradigm for deliniating export
    # Of course, only modules that are used export subroutines...
    # :Export
    #      Subroutine is exported upon specific request.
    #      Subroutine is also exported if the ":ALL" tag is reqested.
    # :Export( :SOME_TAG, :ANOTHER_TAG )
    #      Combine above with export by group if any :TAG requested.
    # :Export( :DEFAULT )
    #      Export sub automatically.

use Class::Std::Utils;

use IO::File;

use Readonly;

##############################################################################
# I M P L E M E N T A T I O N
##############################################################################


{
    # Defining log levels as constant functions
    sub FATAL_MAJOR :Export( :LOG_LEVELS ) { return 1; }
    sub FATAL       :Export( :LOG_LEVELS ) { return 2; }
    sub FATAL_MINOR :Export( :LOG_LEVELS ) { return 3; }
    sub WARN_MAJOR  :Export( :LOG_LEVELS ) { return 4; }
    sub WARN        :Export( :LOG_LEVELS ) { return 5; }
    sub WARN_MINOR  :Export( :LOG_LEVELS ) { return 6; }
    sub INFO_MAJOR  :Export( :LOG_LEVELS ) { return 7; }
    sub INFO        :Export( :LOG_LEVELS ) { return 8; }
    sub INFO_MINOR  :Export( :LOG_LEVELS ) { return 9; }

    Readonly my $DEFAULT_FOR_DEFAULT_LOG_LEVEL => WARN();

    # Objects of this class have the following attributes...
    #=========================================================================
    my %filename_of;            # The filename to log to.
    my %default_log_level_of;   # The default level for logging.


    # Objects of this class have the following methods...
    #=========================================================================

    # Constructor takes filename and optional default level.
    #-------------------------------------------------------------------------
    sub new {
        my ($class, $filename, $base_level) = @_;

        # Bless a scalar to instantiate the new (inside-out) object.
        my $this_O = bless \do{my $anon_scalar}, $class;

        # Initialize objects attributes
        $filename_of{ident $this_O} = $filename;
        $default_log_level_of{ident $this_O} =
            defined $base_level ? $base_level : $DEFAULT_FOR_DEFAULT_LOG_LEVEL;

        return $this_O;
    }

    # No Clone method. Only need one logging engine.
    #-------------------------------------------------------------------------

    # Get method for the filename attribute.
    # No set method - create a new logging engine if need this.
    #-------------------------------------------------------------------------
    sub get_filename {
        my ($this_O) = @_;
        return $filename_of{ident $this_O};
    }

    # Get/Set methods for the default_log_level attribute.
    #-------------------------------------------------------------------------
    sub set_default_log_level {
        my ($this_O, $default_log_level) = @_;

        # Check params
        croak( 'Usage: $Obj->set_filename($default_log_level)' )
            if @_ < 2;

        # Store new value
        $default_log_level_of{ident $this_O} = $default_log_level;

        return;
    }

    sub get_default_log_level {
        my ($this_O) = @_;
        return $default_log_level_of{ident $this_O};
    }

    sub _get_timestamp {

        my ($sec, $min, $hour, $day, $mon, $year) = localtime(time());
        my $time_string = $hour . '-' . $min . '-' . $sec;
        my $date_string = $year + 1900 . '-' . $mon . '-' . $day;
        return $date_string . '_' . $time_string;
    }

    # Add message
    #-------------------------------------------------------------------------
    sub logit {
        my ($this_O, $log_level, $message) = @_;

        # Check params
        (@_ > 1 && @_ < 4) or
            croak( 'Usage: $Obj->logit([$log_level,] $message)' );

        if (@_ == 2) {
            $message = $log_level;
            $log_level = $default_log_level_of{ident $this_O};
        }

        if ($log_level !~ /^\d$/) {
            $log_level = $default_log_level_of{ident $this_O};
        }

        my $log_file = $filename_of{ident $this_O};

        my $fh = IO::File->new($log_file, '>>') or
            croak "Can't open file \"$log_file\".";

        chomp $message;
        $message = _get_timestamp() . " [$log_level]\t " . $message . "\n";

        $fh->print( $message ) or
            croak "Can't write \"$message\" to \"$log_file\".";

        $fh->close
    }

    # Destructor
    #-------------------------------------------------------------------------
    sub DESTROY {
        my ($this_O) = @_;

        delete $filename_of{ident $this_O};
        delete $default_log_level_of{ident $this_O};
    }

}

1;     # Magic number to signal end of module.



=head1 NAME

Logit - Provide a logging engine for an application

=head1 VERSION

Logit version 0.0.1

=head1 SYNOPSIS

    use Logit;

    my $log_filename = "/etc/log/myperlapp.log";
    my $log_level = 3;

    my log = Logit->new("log_filename");
    my log = Logit->new("log_filename", $log_level);

    # Log messages
    log->logit($message);
    log->logit($log_level, $message);

    # To use the defined log levels (implemented as functions)
    use Logit (:LOG_LEVELS);

    # Then can say things like:
    my $log_level = WARN();
    log->logit(FATAL(), $message);

=head1 DESCRIPTION

This module is a simple logging engine for a perl script. It allows
a simple way to write messages out to a log file as an application runs. 
Messages are time-stamped and flagged with a severity code.

=head2 Creating a Logging Engine

Creating a new instance of the Logit engine requires a filename that can be
appended to. This filename can be queried after creation of the logging
engine, but it can not be changed. If a new file is required, a new
logging engine must be instantiated. Note: this must be a complete path
filename.

A default message severity level for the engine can be specified.
If not specified when creating the engine, a level of "WARN" is
set. This base level can be changed or queried at any point.

=head2 Logging a Message

Logging a message is simply a matter of calling the logit() method of the
logging object, passing it the [optional] message severity level and the
message you want to log. Messages are automatically timestampled and are
written sequenctially to the log file. The log file is only opened during
the writting of the message, and is immediately closed. Many rapid calls
to log a message may cause delays. So don't use inside a loop that could
itterate quickly. This is a concious implementation decision to preserve
the integrity of the log file under the widest possible failure conditions.

=head2 Log Levels

Log levels are essentially the numbers 1 through 9, with 1 being the most
severe and 9 being the least. These are given the following names thorugh
use of simple constant functions. These are only exported by request with
the :LOG_LEVELS tag.

    FATAL_MAJOR() = 1
    FATAL()       = 2
    FATAL_MINOR() = 3
    WARN_MAJOR()  = 4
    WARN()        = 5
    WARN_MINOR()  = 6
    INFO_MAJOR()  = 7
    INFO()        = 8
    INFO_MINOR()  = 9

=head1 EXAMPLES

TODO: Show log entry lines here.

=head1 FREQUENTLY ASKED QUESTIONS

=head1 COMMON USAGE MISTAKES

Trying to use a log level tag without explicitly requesting them.

Trying to use the log level functions as variables. They can not be
assigned to, and they are not scalar variables but functions that
return scalars.

Specifying a log level that is not 1..9.

Getting the location of the log level wrong. Its second when setting the
defaut log level for the engine, but first when logging a message (Note:
this is possible going to change...)

=head1 SUBROUTINES or METHODS

An object of this class represents a logging engine, which sits around
waiting to log messages to the file with which it is associated.

=head2 $logit_object->new($filename, [$log_level]);

Create a new logging object that writes to filename, with a default message
level of $log_level. $log_level is optional, "WARN" will be used if nothing
is specified. This severity level will be used when writting messages to the
log file unless (as is usually done) the level is specified with the message.

=head2 $logit_object->get_default_log_level();

Returns the scalar value of the log level.

=head2 $logit_object->set_default_log_level($log_level);

Sets the default value for the log level. $log_level can be a named function
level, or its scalat equivalent.

=head2 $logit_object->get_filename()

Returns the filename of the logfile. This is a complete path filename. There
is no ->set_filename() function. To change the name of the file logging to,
just create a new logging object.

=head2 $logit_object->logit([$log_level], $message);

Prepends a timestamp and the scalar log level to $message and then appends it
to the logfile associated with the logit object. If $log_level is not
specified, then the default log level associated with the logit_object will
be used. Note: Everything is logged. The user will have to filter the log
file to determine what level of detail is needed. This is likely to change
as a reasonable feature is to provide a log_cutoff value for the logging
engine, above which a message will not be recorded.

=head2 $logit_object->validate(); [NOT IMPLEMENTED YET]

Validates that the logit_object is legitamate. just validates the default
log level is between 1 and 9, and that the associated filename is writeable.
If the filename doesn't exist, this will attempt to create it and then
delete it. Failure to delete it is not fatal, but is a warning.

=head2 $logit_object->translate_log_level($level); [NOT IMPLEMENTED YET]

Given the integer log level, it returns the level name string (like "WARN").
Given the level name string, it returns the integer log level.

=head1 DIAGNOSTICS

TODO: Fill this out.

=head1 CONFIGURATION & ENVIRONMENT

Does not assume any file system specific details, so should be portable.
It assumes the filename is specified in a way that works with a file
open command on your system.

=head1 DEPENDENCIES

This module depends on several CPAN modules

  versions
  Perl6::Export:Attrs
  Class:Std::Utils
  Readonly

All of these are convieniences, and the module could be easily re-written
without depending on these (well, maybe not exactly the same, but with
essentially the same functionality).

=head1 INCOMPATIBILITIES

None known.

=head1 BUGS & LIMITATIONS

There are no known bugs in this module.
Please report problems to Stuart R. Jefferys (srj_AT_unc_DOT_edu).

There is a potential resource issue with opening and closing a file if
many messagess are sent in a short period of time. This is not a bug, it
is a feature (see above). It should be robust enough for all but the most
esoteric uses.

=head1 SEE ALSO

TODO: Other logging modules?

=head1 AUTHOR

Stuart R. Jefferys (srj_AT_unc_DOT_edu)

=head1 LICENSE & COPYRIGHT

Copyright (c) 2005 Stuart R. Jefferys (srj_AT_unc_DOT_edu). All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. see L<perlartistic>.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

=cut