lists.def blocks http mboxes, unused otherwise
[mharc.git] / bin / logcmd
1 #!/usr/bin/perl -w
2 ##------------------------------------------------------------------------##
3 ##  File:
4 ##      $Id: logcmd,v 1.1 2002/10/01 22:49:46 ehood Exp $
5 ##  Author:
6 ##      Earl Hood       earl@earlhood.com
7 ##  Description:
8 ##      Program to log the output of a program.
9 ##      POD at __END__.
10 ##------------------------------------------------------------------------##
11 ##    Copyright (C) 2002   Earl Hood, earl@earlhood.com
12 ##
13 ##    This program is free software; you can redistribute it and/or modify
14 ##    it under the terms of the GNU General Public License as published by
15 ##    the Free Software Foundation; either version 2 of the License, or
16 ##    (at your option) any later version.
17 ##
18 ##    This program is distributed in the hope that it will be useful,
19 ##    but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 ##    GNU General Public License for more details.
22 ##
23 ##    You should have received a copy of the GNU General Public License
24 ##    along with this program; if not, write to the Free Software
25 ##    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
26 ##    02111-1307, USA
27 ##------------------------------------------------------------------------##
28
29 package Devtools::locmd;
30
31 use strict;
32 use Getopt::Long;
33 use POSIX;
34
35 my $our_name = 'logcmd';
36 my @cmd = ( );
37 my $cmd_name = "";
38 my $time_fmt = '%Y-%m-%d %H:%M:%S';
39
40 my $first_time = 1;
41 my $log_file = '-';
42 my $log_fh;
43 my $child_pid;
44
45 MAIN: {
46   my @mailto   = ( );
47   my ($help, $man);
48
49   GetOptions(
50     'log=s'       => \$log_file,
51     'mailto=s@'   => \@mailto,  # not implemented, yet
52     'time-fmt=s'  => \$time_fmt,
53
54     'help'        => \$help,
55     'man'         => \$man,
56   ) || die qq/ERROR: Use -help for usage information\n/;
57   usage(1, 0)  if ($help);
58   usage(2, 0)  if ($man);
59
60   if (!@ARGV) {
61     die qq/ERROR: No command specified\n/;
62   }
63   ($cmd_name = $ARGV[0]) =~ s/.*\///;
64   @cmd = @ARGV;
65
66   $child_pid = open(CMD, "-|");
67   if ($child_pid) {   # parent
68     {
69       local @SIG{'PIPE','TERM','INT'} = (sub {
70         log_line($our_name, $$, "Caught signal: SIG$_[0]\n");
71       }) x 3;
72
73       while (<CMD>) {
74         log_line($cmd_name, $child_pid, $_);
75       }
76     }
77     if (!close(CMD)) {
78       log_line($our_name, $$, qq/Non-zero exit for [$child_pid]: $?\n/);
79     }
80     log_close();
81
82   } else {      # child
83     open(STDERR, '>&STDOUT');
84     exec(@cmd) || die qq/ERROR: Cannot exec "@cmd": $!\n/;
85   }
86
87 } # End: MAIN
88
89 ##=========================================================================##
90
91 sub format_time {
92   POSIX::strftime($time_fmt, localtime);
93 }
94
95 sub log_line  {
96   my $label = shift;
97   my $pid   = shift;
98   my $line  = shift;
99   my $have_newline = $line =~ /\n\Z/;
100   my $eol = ($have_newline) ? "" : "\n";
101   if ($first_time) {
102     $first_time = 0;
103     log_open($log_file);
104     print $log_fh join('', '[',format_time(),']',' [',$$,'] ',$our_name,': ',
105                            "Command: [$child_pid] @cmd\n");
106   }
107   print $log_fh join('', '[',format_time(),']',' [',$pid,'] ',$label,': ',
108                           $line,$eol);
109 }
110
111 sub log_open {
112   my $file = shift;
113   if (!defined($file) || $file eq '-') {
114     $log_fh = \*STDOUT;
115   } else {
116     open(LOG, ">>$file") ||
117         die qq/ERROR: Unable to open "$file" for appending: $!\n/;
118     $log_fh = \*LOG;
119   }
120 }
121
122 sub log_close {
123   if (!$first_time) {
124     if ($log_fh != \*STDOUT) {
125       close($log_fh) ||
126           warn qq/Warning: Problem closing log file: $!\n/;
127     }
128   }
129 }
130
131 sub usage {
132   require Pod::Usage;
133   my $verbose   = shift || 0;
134   my $exit_code = shift;
135
136   if ($verbose == 0) {
137     Pod::Usage::pod2usage(-verbose => $verbose);
138   } else {
139     my $pager = $ENV{'PAGER'} || 'more';
140     local(*PAGER);
141     my $fh = (-t STDOUT && open(PAGER, "|$pager")) ? \*PAGER : \*STDOUT;
142     Pod::Usage::pod2usage(-verbose => $verbose,
143                           -output  => $fh);
144     close(PAGER)  if ($fh == \*PAGER);
145   }
146   defined($exit_code) && exit($exit_code);
147 }
148
149 ##=========================================================================##
150 __END__
151
152 =head1 NAME
153
154 logcmd - Log the output of a program.
155
156 =head1 SYNOPSIS
157
158   logcmd [options] -- <command> [command-args ...]
159
160 =head1 DESCRIPTION
161
162 B<logcmd> logs the output of a program.  This program is useful
163 for logging program output for programs that do not have a built-in
164 logging facility.
165
166 Typical usage is to use B<logcmd> in crontabs to log any command
167 output to a file instead of mail automatically being sent to the
168 crontab owner.
169
170 When invoking B<logcmd>, you B<must> use "C<-->" to separate the
171 B<logcmd> and its options from the command you are invoking.  For
172 example,
173
174   logcmd -log out.log -- some-cmd arg1 arg2 arg3
175
176 This tells B<logcmd> to run "C<some-cmd arg1 arg2 arg3>" and have
177 any output goto C<out.log>.
178
179 Each line logged will be preceded by a timestamp, the process ID, and
180 the name of the command.  Also, before the first log message for a command
181 is printed, B<logcmd> will print out a line giving the full command-line
182 of the command invoked.  Example:
183
184   [2002-07-30 14:52:13] [25392] logcmd: Command: [25393] mhonarc -quiet -out out /var/archiver/mail
185   [2002-07-30 14:52:13] [25393] mhonarc: 
186   [2002-07-30 14:52:13] [25393] mhonarc: Warning: Unrecognized character set: utf-8
187   [2002-07-30 14:52:13] [25393] mhonarc:          Message-Id: <1022714308.2028.1.camel@xxx.xxx.xxx>
188   [2002-07-30 14:52:13] [25393] mhonarc:          Message Number: 00367
189   [2002-07-30 14:52:13] [25393] mhonarc: 
190   [2002-07-30 14:52:13] [25393] mhonarc: Warning: Unrecognized character set: utf-8
191   [2002-07-30 14:52:13] [25393] mhonarc:          Message-Id: <1022716087.2028.3.camel@xxx.xxx.xxx>
192   [2002-07-30 14:52:13] [25393] mhonarc:          Message Number: 00368
193   [2002-07-30 14:52:14] [25393] mhonarc: 
194   [2002-07-30 14:52:14] [25393] mhonarc: Warning: Unrecognized character set: utf-8
195   [2002-07-30 14:52:14] [25393] mhonarc:          Message-Id: <1024365245.9394.0.camel@xxx.xxx.xxx>
196   [2002-07-30 14:52:14] [25393] mhonarc:          Message Number: 00398
197
198
199 =head1 OPTIONS
200
201 =over
202
203 =item C<-help>
204
205 Display SYNOPSIS and OPTIONS sections.
206
207 =item C<-log> I<pathname>
208
209 Command output will be logged to I<pathname>.  If this option is
210 not specified, then standard out is used.
211
212 =item C<-man>
213
214 Display manpage.
215
216 =item C<-time-fmt> I<fmt>
217
218 Date/time format to use when printing time before each logged line.
219 The format string should be in C<strftime>(3) format.  If this
220 option is not specified, then "C<%Y-%m-%d %H:%M:%S>" is used.
221
222 =item C<-->
223
224 Terminate option processing for B<logcmd>.  Any arguments after
225 C<--> comprise the command that B<logcmd> should execute.
226
227 =back
228
229 =head1 EXAMPLES
230
231 The following is an example crontab entries of a user that has
232 any output of commands goto a personal log file:
233
234   0 2 * * * logcmd -log $HOME/log/cron.log -- $HOME/bin/clean_account
235   0 3 * * * logcmd -log $HOME/log/cron.log -- $HOME/bin/pack_mail
236
237 =head1 LIMITATIONS
238
239 =over
240
241 =item *
242
243 B<logcmd> performs no file locking on the log file specified.  To help
244 minimize that a single output line to the log does not get broken up
245 due to multiple writers, the entire line is dumped in a single call
246 to Perl's C<print> operator.
247
248 =back
249
250 =head1 DEPENDENCIES
251
252 C<Getopt::Long>, C<POSIX>.
253
254 =head1 AUTHOR
255
256 Earl Hood, earl@earlhood.com
257
258 This program comes with ABSOLUTELY NO WARRANTY and may be copied only
259 under the terms of the GNU General Public License.
260
261 =cut
262