#!/usr/bin/perl -w ##------------------------------------------------------------------------## ## File: ## $Id: logcmd,v 1.1 2002/10/01 22:49:46 ehood Exp $ ## Author: ## Earl Hood earl@earlhood.com ## Description: ## Program to log the output of a program. ## POD at __END__. ##------------------------------------------------------------------------## ## Copyright (C) 2002 Earl Hood, earl@earlhood.com ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## 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. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ## 02111-1307, USA ##------------------------------------------------------------------------## package Devtools::locmd; use strict; use Getopt::Long; use POSIX; my $our_name = 'logcmd'; my @cmd = ( ); my $cmd_name = ""; my $time_fmt = '%Y-%m-%d %H:%M:%S'; my $first_time = 1; my $log_file = '-'; my $log_fh; my $child_pid; MAIN: { my @mailto = ( ); my ($help, $man); GetOptions( 'log=s' => \$log_file, 'mailto=s@' => \@mailto, # not implemented, yet 'time-fmt=s' => \$time_fmt, 'help' => \$help, 'man' => \$man, ) || die qq/ERROR: Use -help for usage information\n/; usage(1, 0) if ($help); usage(2, 0) if ($man); if (!@ARGV) { die qq/ERROR: No command specified\n/; } ($cmd_name = $ARGV[0]) =~ s/.*\///; @cmd = @ARGV; $child_pid = open(CMD, "-|"); if ($child_pid) { # parent { local @SIG{'PIPE','TERM','INT'} = (sub { log_line($our_name, $$, "Caught signal: SIG$_[0]\n"); }) x 3; while () { log_line($cmd_name, $child_pid, $_); } } if (!close(CMD)) { log_line($our_name, $$, qq/Non-zero exit for [$child_pid]: $?\n/); } log_close(); } else { # child open(STDERR, '>&STDOUT'); exec(@cmd) || die qq/ERROR: Cannot exec "@cmd": $!\n/; } } # End: MAIN ##=========================================================================## sub format_time { POSIX::strftime($time_fmt, localtime); } sub log_line { my $label = shift; my $pid = shift; my $line = shift; my $have_newline = $line =~ /\n\Z/; my $eol = ($have_newline) ? "" : "\n"; if ($first_time) { $first_time = 0; log_open($log_file); print $log_fh join('', '[',format_time(),']',' [',$$,'] ',$our_name,': ', "Command: [$child_pid] @cmd\n"); } print $log_fh join('', '[',format_time(),']',' [',$pid,'] ',$label,': ', $line,$eol); } sub log_open { my $file = shift; if (!defined($file) || $file eq '-') { $log_fh = \*STDOUT; } else { open(LOG, ">>$file") || die qq/ERROR: Unable to open "$file" for appending: $!\n/; $log_fh = \*LOG; } } sub log_close { if (!$first_time) { if ($log_fh != \*STDOUT) { close($log_fh) || warn qq/Warning: Problem closing log file: $!\n/; } } } sub usage { require Pod::Usage; my $verbose = shift || 0; my $exit_code = shift; if ($verbose == 0) { Pod::Usage::pod2usage(-verbose => $verbose); } else { my $pager = $ENV{'PAGER'} || 'more'; local(*PAGER); my $fh = (-t STDOUT && open(PAGER, "|$pager")) ? \*PAGER : \*STDOUT; Pod::Usage::pod2usage(-verbose => $verbose, -output => $fh); close(PAGER) if ($fh == \*PAGER); } defined($exit_code) && exit($exit_code); } ##=========================================================================## __END__ =head1 NAME logcmd - Log the output of a program. =head1 SYNOPSIS logcmd [options] -- [command-args ...] =head1 DESCRIPTION B logs the output of a program. This program is useful for logging program output for programs that do not have a built-in logging facility. Typical usage is to use B in crontabs to log any command output to a file instead of mail automatically being sent to the crontab owner. When invoking B, you B use "C<-->" to separate the B and its options from the command you are invoking. For example, logcmd -log out.log -- some-cmd arg1 arg2 arg3 This tells B to run "C" and have any output goto C. Each line logged will be preceded by a timestamp, the process ID, and the name of the command. Also, before the first log message for a command is printed, B will print out a line giving the full command-line of the command invoked. Example: [2002-07-30 14:52:13] [25392] logcmd: Command: [25393] mhonarc -quiet -out out /var/archiver/mail [2002-07-30 14:52:13] [25393] mhonarc: [2002-07-30 14:52:13] [25393] mhonarc: Warning: Unrecognized character set: utf-8 [2002-07-30 14:52:13] [25393] mhonarc: Message-Id: <1022714308.2028.1.camel@xxx.xxx.xxx> [2002-07-30 14:52:13] [25393] mhonarc: Message Number: 00367 [2002-07-30 14:52:13] [25393] mhonarc: [2002-07-30 14:52:13] [25393] mhonarc: Warning: Unrecognized character set: utf-8 [2002-07-30 14:52:13] [25393] mhonarc: Message-Id: <1022716087.2028.3.camel@xxx.xxx.xxx> [2002-07-30 14:52:13] [25393] mhonarc: Message Number: 00368 [2002-07-30 14:52:14] [25393] mhonarc: [2002-07-30 14:52:14] [25393] mhonarc: Warning: Unrecognized character set: utf-8 [2002-07-30 14:52:14] [25393] mhonarc: Message-Id: <1024365245.9394.0.camel@xxx.xxx.xxx> [2002-07-30 14:52:14] [25393] mhonarc: Message Number: 00398 =head1 OPTIONS =over =item C<-help> Display SYNOPSIS and OPTIONS sections. =item C<-log> I Command output will be logged to I. If this option is not specified, then standard out is used. =item C<-man> Display manpage. =item C<-time-fmt> I Date/time format to use when printing time before each logged line. The format string should be in C(3) format. If this option is not specified, then "C<%Y-%m-%d %H:%M:%S>" is used. =item C<--> Terminate option processing for B. Any arguments after C<--> comprise the command that B should execute. =back =head1 EXAMPLES The following is an example crontab entries of a user that has any output of commands goto a personal log file: 0 2 * * * logcmd -log $HOME/log/cron.log -- $HOME/bin/clean_account 0 3 * * * logcmd -log $HOME/log/cron.log -- $HOME/bin/pack_mail =head1 LIMITATIONS =over =item * B performs no file locking on the log file specified. To help minimize that a single output line to the log does not get broken up due to multiple writers, the entire line is dumped in a single call to Perl's C operator. =back =head1 DEPENDENCIES C, C. =head1 AUTHOR Earl Hood, earl@earlhood.com This program comes with ABSOLUTELY NO WARRANTY and may be copied only under the terms of the GNU General Public License. =cut