Wednesday, October 17, 2012

A logger for PHP applications

     This is a simple logger implementation that you can plug into your php applications without installing anything.

     Logger class definition, comments are provided wherever necessary.

safeer@penguinpower:~/DEV/PHP$ cat logger.php
class logger {
### Variable declaration & initialization
private $logFile = NULL;
private $filePtr = NULL;
private $incScriptName = FALSE;
private $incScriptPath = FALSE;
### Constructor, sets log file name and log entry behaviour
function __construct($options) {
### Open logfile
if( isset($options['logfile'])) 
        { 
         $logDate = date('d-m-Y');
### Final logfile name should be <LOGFILE>.<DD-MM-YYYY>.log
         $this->logFile = $options['logfile'].".".$logDate.".log"; 
### Create if log file is not existing, then open for appending
         $this->filePtr = fopen($this->logFile,"a") 
                or exit("Unable to open logfile $file\n"); 
        }
### Abort if the logfile is not set during object creation
else 
        { exit( "Logfile should be defined\n" ) ; }
### Whether logger should add calling script name to the log file
### Include script name in log entry
if( $options['scriptname'] ==  TRUE )
        { $this->incScriptName = TRUE; }
### If set include full path of script, else just script name
elseif( $options['scriptpath'] == TRUE )
        { $this->incScriptPath = TRUE; }

}
### Destructor, close the file handle when object is destroyed
function __destruct() {
if($this->filePtr) { fclose($this->filePtr); }
}
### Logging happens here
### Each line in the log file ( log entry ) will start with a log prefix
### The log prefix will start with [DATE][LOG_TYPE]
### Log type will be one of INFO/DEBUG/WARN/CRIT
private function logme($logType,$logMsg) {
$timeStamp = date('Y-m-d H:i:s');
$logPrefix = "[".$timeStamp."]";
$logPrefix .= "[".$logType."]"; 
$message = NULL;
### Add script name to log prefix based on setting
if( $this->incScriptName == TRUE and $this->incScriptPath == TRUE )
        { $logPrefix .= "[".$_SERVER['PHP_SELF']."]"; }
elseif ( $this->incScriptName == TRUE and $this->incScriptPath == FALSE )
        {  $logPrefix .= "[".basename( $_SERVER['PHP_SELF'] )."]"; }
### Break down multi-line log message and append prefix to each line
foreach ( explode("\n",$logMsg) as $msg) {
        $message .= "$logPrefix $msg\n";
        }
### Write log entries to file
fwrite($this->filePtr, $message) 
or exit("Unable to write to log ".$this->logFile);

}
### Following functions are defined for the convenience of user
### All of them calls logme() funcation  internally

### Used to register informative messages
public function info($logMsg) {
$this->logme("INFO",$logMsg);
}
### Used to register debug messages
public function debug($logMsg) {
$this->logme("DEBUG",$logMsg);
}
### Used to register warning messages
public function warn($logMsg) {
$this->logme("WARN",$logMsg);
} 
### Used to register critical messages
public function crit($logMsg) {
$this->logme("CRIT",$logMsg);
}

}
?>


     Sample use of the logger

safeer@penguinpower:~/DEV/PHP$ cat logcaller.php
### Inlcude logger class definition
include_once("logger.php");
### Define
$opts['logfile']="/tmp/testLogger";
$opts['scriptname'] = TRUE;
$opts['scriptpath'] = FALSE;
$logger = new logger($opts);
### Register all types of log messages
$logger->info("Information Message");
$logger->debug("Debug Message");
$logger->warn("Warning Message");
$logger->crit("Critical Message");
?>



Run php application and see the logging.

safeer@penguinpower:~/DEV/PHP$ php logcaller.php
safeer@
penguinpower:~/DEV/PHP$ cat /tmp/testLogger.17-10-2012.log
[2012-10-10 13:31:54][INFO][logcaller.php] Information Message
[2012-10-10 13:31:54][DEBUG][logcaller.php] Debug Message
[2012-10-10 13:31:54][WARN][logcaller.php] Warning Message
[2012-10-10 13:31:54][CRIT][logcaller.php] Critical Message

Wednesday, July 18, 2012

Bash Recipes Part 4 - Read from multiple files in parallel

     I had to download a set of files for which the download URLs where provided in one file and the corresponding file names in another file.  Now to download and name the downloaded files at the same time, I have to loop through both the files in parallel, picking one URL and its name at a time and feeding it to wget.   I used while loop combined with read command for this as follows:

while read -u5 url;read -u6 name
do
/usr/bin/wget  -c  "${url}" -O "${name}"
done 5 < url.txt 6 < name.txt


    In the last line of the loop, we are redirecting the contents of files "url.txt" and "name.txt" respectively to file descriptors 5 and 6.  The while loop iterates over two read commands, each of which reads from the file descriptors 5 and 6 one line at a time and save the line into variables "url" and "name" respectively.  These variables are used with wget to download and set the name.

     Note that file descriptor numbers 0,1 & 2 are reserved for standard input, standard output and standard error and shouldn't be used.

Tuesday, July 17, 2012

A simple logger for Perl

     Below is a simple logger for your custom perl applications.  The logger is created as a module and can be included in any script.  Code is explained within the program as comments

Module :


safeer@penguinpower:~$ cat DEV/Perl/MyLogger.pm
#!/usr/bin/perl -w
### Module Name is MyLogger
package MyLogger;
### Include Modules
use IO::File;
use DateTime;
use File::Basename;
use File::Spec;

### Constructor

sub new
{

my $class = shift;
### Object creation parameters
my(%params) = @_;
my $self = { };
### Identify the parent scripting using the class
(undef,$self{'caller'}) = caller;
### Whether to append current date to the log file name or not
### Off by default
$self->{'datedLogFile'} = $params{'datedLogFile'}  || 0;
### Whether to include scriptname in the log entries
### Off by default
$self->{'incScriptName'} = $params{'incScriptName'}  || 0;
### Whether to include scriptname - with full path - in the log entries
### Off by default
$self->{'incScriptWithPath'} = $params{'incScriptWithPath'}  || 0;
### Whether to include current date and time in log entries
## On by default
$self->{'incDateTime'} = $params{'incDateTime'} || 1;
### logFileBase - basename from which the logfile name should be constructed
if( defined $params{'logFileBase'} ) { 
$self->{'logFile'} = $params{'logFileBase'}; 
if( $self->{'datedLogFile'} == 1 ) { 
my $currentDate = DateTime->now->strftime('-%d-%m-%Y');
$self->{'logFile'} .= $currentDate;
}
### Append ".log" to the end of log file name
$self->{'logFile'} .= ".log";
}
else { print "No logfile defined!!" ; exit 1 ; }
### Open the file and store the file object pointer in the class
$self->{'log'} = IO::File->new;
$self->{'log'}->open("$self->{'logFile'}",">>");

bless $self,$class;
return $self;
}

### This function will do the actual logging
### It is preferred not to use this function directly
### Rather call the functions derived from this one
### Like debug,info,warn,crit
sub logme()
{
my $self = shift;
### Accept two parameters, log level ( DEBUG/INFO/WARN/CRIT )
### and log message ( can be multi line message ) 
my ($logLevel,$message) = @_ ;
### Breakup multiline messages and process line by line
@logentries = split("\n" , $message  );
foreach my $logentry ( @logentries ) {
my $logprefix = "";
### Include date and time
if( $self->{'incDateTime'} == 1 ) { 
  $timestamp = DateTime->now(time_zone=>"local")->strftime("[%d-%m-%Y %H:%M:%S]");
  $logprefix .= $timestamp ;
  }
### Add log level
$logprefix .=   "[" . $logLevel ."]" ;
### Include scriptname with path
if( $self->{'incScriptWithPath'} == 1 ) {
  my $scriptpath = File::Spec->rel2abs($self{'caller'}) ;
  $logprefix .= "[" . $scriptpath . "]"; 
  }
### Include scriptname wihtout path
elsif( $self->{'incScriptName'} == 1 ) { 
  my $scriptname = basename($self{'caller'}) ;
  $logprefix .= "[" . $scriptname . "]" ;
  }

$self->{'log'}->print( "$logprefix $logentry\n" );

}

}

### Following functions are defined for the convenience of user
### All of them calls logme() funcation  internally

### Used to register informative messages
sub info()
{
my $self = shift;
$self->logme("INFO",$_[0]);
}

### Used to register warning messages
sub warn()
{
my $self = shift;
$self->logme("WARN",$_[0]);
}

### Used to register critical messages
sub crit()
{
my $self = shift;
$self->logme("CRIT",$_[0]);
}

### Used to register debug messages
sub debug()
{
my $self = shift;
$self->logme("DEBUG",$_[0]);
}

### Destroyer
sub DESTROY
{
my $self = shift;
$self->{'log'}->close;
}


1;


Now use this module in a script


safeer@penguinpower:~/DEV/Perl$ cat loguse.pl
#!/usr/bin/perl
### Define custom library path for adding modules
use lib '/home/safeer/DEV/Perl/CustomModules';
### Include the cutom logger module
use MyLogger;
### Create an instance of custom logger
my $logger = MyLogger->new('logFileBase' => "/tmp/logPerl" , 'datedLogFile' => 1 , 'incScriptName' => 1 , 'incScriptWithPath' => 1 );
### Log all types of log messages
$logger->info("Information Message");
$logger->warn("Warning Message");
$logger->crit("Critical Message");
$logger->debug("Debug Message");

Execute the script

safeer@penguinpower:~/DEV/Perl$ ./loguse.pl

Look at the log file created

safeer@penguinpower:~$ cat /tmp/logPerl-17-07-2012.log
[17-07-2012 23:26:15][INFO][/home/safeer/DEV/Perl/loguse.pl] Information Message
[17-07-2012 23:26:15][WARN][/home/safeer/DEV/Perl/loguse.pl] Warning Message
[17-07-2012 23:26:15][CRIT][/home/safeer/DEV/Perl/loguse.pl] Critical Message
[17-07-2012 23:26:15][DEBUG][/home/safeer/DEV/Perl/loguse.pl] Debug Message



Thursday, June 28, 2012

Perl One Liner for dumping xml files

     Whenever we work with an xml file, the first thing we like to find out is how the xml is organized.  Once we get the structure of the file, it will be easier to write code to process the xml data.  Perl developers popularly use the Dumper function from the Data::Dumper module to achieve this inside their code.  But if you have the xml file readily available and you want to take a peak into how the data is organized within the xml, you don’t have to start writing a program, instead run the following one liner with the xml file name as the last argument.

perl -MData::Dumper -MXML::Simple -e 'print Dumper(new XML::Simple->XMLin($ARGV[0]));' XML_FILE_NAME

-M - Followed by a perl module name make the module available for the perl command.  So here we are loading modules Data::Dumper and XML::Simple
-e  - Any quoted string following this will be executed as perl code.

Now examining the executable code itself,

$ARGV[0] is the first command line argument passed to the perl command after all the command "options"

XML::Simple module provide the XMLin function, which can take an xml file as argument and return a reference to a data structure containing the xml data in a more structured and accessible format.

Dumper function take any perl variable or variable reference as an argument and returns it as a structured printable string.

So the executable code,

 * dynamically calls the XMLin function made available by the XML::Simple module and
 *  passes the xml file name as its argument ( which was provided as the command line argument to perl)
 * XMLin return the xml in a data structure reference, which is
 * passed as an argument to Dumper function made available by the Dta::Dumper module, which
 * returns a printable string representation of the above reference, and
 * the print command just prints it out.

A small example

safeer@penguinpower:~$ perl -MData::Dumper -MXML::Simple -e 'print Dumper(new XML::Simple->XMLin($ARGV[0]));'  dummy.xml  
$VAR1 = { 
         'language' => { 
               'perl' => { 
                   'version' => '5.8', 
                   'content' => 'Practical Extraction and Report Language'          
                            },
          'name' => 'perl'  
                         } 
        }; 

To make life easier I have wrapped it in a bash  function and added to my bashrc

safeer@penguinpower:~$ type xmlDump 
xmlDump is a function 
xmlDump () {     
XML=${1?You should provide an xml file};   
perl -MData::Dumper -MXML::Simple -e 'print Dumper(new XML::Simple->XMLin($ARGV[0]));' $XML 
}


Tuesday, May 29, 2012

Authenticated website access using Perl

   This code will allow you to access a website that requires authentication via a username password pair.

Raw materials needed for this script are:

1. The URL of the sign-in page.  Shoot up your browser, go to the website you want access, go to the login screen and copy the URL address.
2. The name/number of the html login form:
   To obtain this, launch the sign in URL in a browser window, view source of the page.
   Look for the block that starts with <form, the value of the "name" property is the form name ( if exists )
   If there is no form name:
   If only one form is found in the source code, the form number will be 1
   If there are multiple forms and the login form is N-th one in the list, N is the form number.
3. The value of the "name" property corresponding to the input elements for username and password in the form.

   With these details, the below mentioned code can log you into the page.  Comments are added above the code lines, wherever necessary.


#!/usr/bin/perl -w

use strict;
## WWW::Mechanize is a wrapper on LWP:UserAgent object,
## which extents the browser capabilities of LWP.
use WWW::Mechanize;
## Needed for initializing and storing cookies for the website
use HTTP::Cookies;
## Needed if your code has to support SSL
use Crypt::SSLeay;
## Use only if you want to trouble shoot!!
use LWP::Debug qw(+);

## I want to login to Ubuntu forum, so this is my URL,
my $URL = "http://ubuntuforums.org";

## My Ubuntu forum username and password
 my $username = "your_username";
 my $password = "your_password";

## Create a browser and a cookie object
 my $browzer = WWW::Mechanize->new();
 $browzer->cookie_jar(HTTP::Cookies->new());

## Open ( HTTP GET ) the login URL
 $browzer->get($URL);

## Ubuntu forum has no form name, so using the number "2" 
## as it is the second form on the login page,
## first being a forum search box.
$browzer->form_number(2);
## vb_login_username/vb_login_password  are the input names
## used in this form
$browzer->field(vb_login_username => $username);
$browzer->field(vb_login_password => $password);
##Submit the form
$browzer->submit();

## If you are not automatically redirected to the
## forum home page after loging in,
## emulate clicking the link that says
## "Click here if your browser does not automatically
## redirect you."
 $browzer->follow_link( text =>
"Click here if your browser does not automatically redirect you."
    );

## Now that you have logged in, you can use different methods of 
## WWW::Mechanize to process the web pages.
## Eg: Save the content of the page you see after loging in 
## my $output_page = $browzer->content();
## my $output_file = "/tmp/mylogin.html"
## open(OUTFILE, ">$output_file");
## print OUTFILE "$output_page";
## close(OUTFILE);