Commit | Line | Data |
---|---|---|
2ea8f66b IK |
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 |