Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Production level script template

by roswell1329 (Acolyte)
on Sep 07, 2005 at 15:38 UTC ( [id://489898]=perlquestion: print w/replies, xml ) Need Help??

roswell1329 has asked for the wisdom of the Perl Monks concerning the following question:

I recently read a great article on production-level scripting in the August 2005 issue of Sys Admin Magazine. In it, the author advocated the use of a template to make production-script rollout much faster. In an example template, the author included multiple levels of verbosity, options to email execution reports and error reports, and a test mode that would perform a test run of the code without actually committing to any changes. The example the author provided in the article was a bash shell script, but said the same techniques could be applied to perl or other administrative languages.

I have attempted to do the same thing in perl, but I have encountered 2 problems:

  1. I'm not happy with the method of process monitoring I've devised, but I cannot seem to come up with another. My method basically just prints out debugging messages (using a subroutine I'm calling 'debugger') at critical points during the script operation. I was hoping there might be some way that was less clunky and perhaps a bit more deliberate than that.
  2. My method to test whether the script ends cleanly or as the result of a die seems flawed. I'm checking the OS_ERROR and CHILD_ERROR variables for contents, but I'm finding that the OS_ERROR variable seems to always have some string in it whether the script ends cleanly or not. My objective is to only kick off the error email when the script encounters a die or a defined signal. Is there another way of seeing whether a script hits the END block because of a die or smoothly?

Any suggestions you have would be greatly appreciated! My template script in it's entirety is below:

#!/usr/bin/perl ############################################################## # Script : template # Author : roswell1329 # Date : 08/31/2005 # Last Edited: 09/07/2005, roswell1329 # Description: working template for fast production rollout ############################################################## # Purpose: # - create a working template with logging, and emailed # reports to make production development of scripts # faster # Requirements: # - error code checking # - different debugging/logging levels # - option for email execution report delivery # - option for email error delivery # - option for TEST (go through motions and logging with no # actual changes made # Method: # - make a script 'shell' with some of the standard options # already implemented # Syntax: template [-h] # template [-m email] [-e email] [-t] [-d] # template [-m email] [-e email] [-t] [-v] # Notes: # - not happy with execution logging or failure checking ############################################################## ###################################### #### Opening Initializations #### ###################################### use strict; use Getopt::Std; my $ex_status = 0; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; $ENV{PATH} = '/bin:/usr/bin'; $SIG{INT} = sub { $ex_status = 1; die }; $SIG{PIPE} = sub { $ex_status = 2; die }; my $cmdname = (split /\//,$0)[-1]; my $basedir = (split /\/$cmdname/,$0)[0]; my $cfgfile = "$basedir/$cmdname.cfg"; my $logfile = "$basedir/$cmdname.log"; my $maillog = ""; my $thread = $$; my $user = (getpwuid $<)[0]; my %opts = (); my %configs = (); getopts('de:hm:tv', \%opts); &usage if ($opts{h}); &usage if (exists($opts{m}) && (!$opts{m})); &usage if (exists($opts{e}) && (!$opts{e})); &usage if (exists($opts{d}) && exists($opts{v})); if(-e $cfgfile) { open(CONFIG,"$cfgfile") or die "Cannot open config file $cfgfile\n"; foreach my $attrib (<CONFIG>) { next if ($attrib =~ /^$/); next if ($attrib =~ /^#/); my ($attrib_name,$attrib_val) = split /\s*=\s*/,$attrib; chomp($attrib_name,$attrib_val); $configs{$attrib_name} = $attrib_val; } &debugger("Config file found. Values loaded"); while(my($k,$v) = each %configs) { &debugger("|- $k attribute loaded with '$v'"); } } else { &debugger("No config file found. Proceeding with internal values"); } ###################################### #### Main Loop #### ###################################### &debugger("Initializations complete"); &debugger("Starting main routine"); ###################################### #### Support Functions #### ###################################### sub debugger($) { if(!exists($opts{v}) && !exists($opts{d})) { return; } my $dbstring = shift @_; my ($dsec,$dmin,$dhour,$dday,$dmon,$dyear,$dwday)=(localtime)[0..6]; my $monname = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug', 'Sep','Oct','Nov','Dec')[$dmon]; my $dayname = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$dwday]; open(LOG,">>$logfile") or die "Cannot open logfile $logfile\n"; if(exists($opts{d})) { warn "[DEBUG]: $dbstring\n"; $maillog .= $dbstring . "\n" if ($opts{m}); printf LOG "%s %s %02d %02d:%02d:%02d thread[%5d]: %s\n", + ($dayname,$monname,$dday,$dhour,$dmin,$dsec,$thread,$dbstring); } elsif(exists($opts{v})) { next if($dbstring =~ /\|-/); $maillog .= $dbstring . "\n" if ($opts{m}); printf LOG "%s %s %02d %02d:%02d:%02d thread[%5d]: %s\n", ($dayname,$monname,$dday,$dhour,$dmin,$dsec,$thread,$dbstring); } close(LOG); } sub mail_report($$) { my ($mesg,$mailsubj) = @_; my $mailfrom = $user; open(MAIL, "| sendmail -oi -t") or die "Cannot start sendmail\n"; print MAIL "To:$opts{m}\nFrom:$mailfrom\nSubject:$mailsubj\n\n"; print MAIL $mesg; close(MAIL); } sub usage { print <<EOF; Usage: $cmdname [-h] $cmdname [-m email] [-e email] [-t] [-v] $cmdname [-m email] [-e email] [-t] [-d] Options: -d debugging mode. This mode will verbosely describe the current execution of $cmdname. -e [EMAIL] error reporting. This option will send any errors detected by $cmdname to email address EMAIL. -h help. This option will display this help message. -m [EMAIL] execution reporting. This option will send a $cmdname execution report to the supplied email address. -t test mode. This allows you to run the script without making any critical changes. -v verbose mode. This option will enable logging for this execution of $cmdname EOF } END { $ex_status > 0 ? &debugger("Program killed!") : &debugger("Exiting normally"); &debugger("Mailing reports"); &mail_report($maillog,"$cmdname Execution Report") if ($opts{m}); if($opts{e}) { if(($!) || ($?)) { my $mesg = "Internal Failures:\n------------------\n$!\n\n" . "External Failures:\n-----------------\n$?\n"; &mail_report($mesg,"$cmdname Error Report"); } } exit; }

Replies are listed 'Best First'.
Re: Production level script template
by tomhukins (Curate) on Sep 07, 2005 at 15:58 UTC
    I would deal with quite this differently to you. For example:
    • Write your documentation in POD, not as comments. That way you can run perldoc scriptname to see the purpose, requirements, etc.
    • Put your global variables such as $cmdname and $thread in a singleton object. That means if someone happens to use a local variable called $thread your code won't break.
    • Put all the code in a separate module, or at least a file that you can require. This makes your scripts more readable because you don't have to wade through the common code in each separate script. If you find a bug, you only have to change it once.
    • Consider using modules such as Log::Log4perl and Email::Send to replace your home-grown equivalents. Less code means less bugs.
Re: Production level script template
by Fletch (Bishop) on Sep 07, 2005 at 17:27 UTC
    my ($dsec,$dmin,$dhour,$dday,$dmon,$dyear,$dwday)=(localtime)[0..6]; my $monname = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug', 'Sep','Oct','Nov','Dec')[$dmon]; my $dayname = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$dwday];

    Consult POSIX::strftime and your system's strftime manual page.

    --
    We're looking for people in ATL

      I saw the idea for a time hash somewhere long ago. I keep this snippet on our work wiki site so I don't forget. It could be cleaned, but is useful (one variable to hold the time data.)
      sub h_localtime() { my %time; my @time = localtime(time); my $iter = 0; my @iter = qw/sec min hour mday mon year wday yday isdst/; foreach (@iter) { $time{$_} = $time[$iter]; $iter++; } $time{mon}++; # make month 1..12 instead of 0..11 $time{year}+=1900; # make year absolute instead of (year-1900) $time{wday}++; # make week day 1..7 instead of 0..6 return %time; }
      The programmer can then do this:
      my %t = h_localtime;
      printf "%d %d %d\n", @t{qw/mday mon year/};
      
      I don't know that it's a significant benefit in the parent example, but I like it.

        This just looks like a quick-n-dirty version of Time::localtime, except that it makes mon and year a useful value, and modifies wday (which is of value only if you think that wday numbers have a meaning outside of an array index).

        The only other change I'd suggest is to check your context:

        return wantarray ? %time : \%time;
        I find being able to get the data in the format I want a very useful feature of perl. Especially since the speed difference is usually (but probably not always) in the ref's favour, while the readability of your example usage is definitely a point in favour of having the list return.

Re: Production level script template
by roswell1329 (Acolyte) on Sep 07, 2005 at 17:57 UTC
    Many thanks for your comments! I will implement what I can from your suggestions. Unfortunately, I'm operating in an extremely rigid environment that I do not have much control over. I cannot add any additional modules to the environment, I can only use the standard modules loaded with Perl 5.8.0. This script is meant to be a template that every agent on my team can use on any system we support, but it has to be able to work on any one of 900 systems by simply dropping it into place. We don't have the authority to add software to the environment. With that in mind, do you have any more recommendations?
      Coming across this rather late, all I can say is that my script template would use Pod::Usage and Getopt::Long and do all the documentation in pod rather than comments.

      Hope this is still of some use :)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://489898]
Approved by saberworks
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (2)
As of 2024-04-20 03:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found