document branches better
[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