Last active
December 16, 2015 03:39
-
-
Save mpasternacki/5371061 to your computer and use it in GitHub Desktop.
A runner script to run any command and save its stdout and stderr in a timestamped log file, ready to be harvested by Logstash. Adds JSON metadata, and optionally locks the command, ensuring it doesn't run in multiple copies at the same time.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl -w | |
# | |
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
# Version 2, December 2004 | |
# | |
# Copyright (C) 2013 Maciej Pasternacki <[email protected]> | |
# | |
# Everyone is permitted to copy and distribute verbatim or modified | |
# copies of this license document, and changing it is allowed as long | |
# as the name is changed. | |
# | |
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
# | |
# 0. You just DO WHAT THE FUCK YOU WANT TO. | |
use strict; | |
use Cwd; | |
use Fcntl qw(:flock); | |
use File::Basename; | |
use File::Spec; | |
use Getopt::Long; | |
use IO::Select; | |
use IPC::Open3; | |
use Time::HiRes; | |
use Time::Piece; | |
use JSON; | |
our $pid = 0; | |
our $exited = 0; | |
our $lock = 0; | |
our $timeout; | |
our $name; | |
our $basedir; | |
our $command; | |
our $chdir; | |
sub say { | |
my ( $tag, $message, %meta ) = @_; | |
my ( $sec, $usec ) = Time::HiRes::gettimeofday; | |
my $line = sprintf "%s.%.06dZ %s[%d] %s %s", | |
Time::Piece->gmtime($sec)->datetime, $usec, | |
$name, $pid, uc($tag), | |
$message; | |
$line .= " ".encode_json(\%meta) if %meta; | |
print LOGFILE "$line\n"; | |
} | |
sub handle { | |
foreach my $fh ( @_ ) { | |
my $tag = ( $fh == \*CHILD_OUT ? 'stdout' : | |
$fh == \*CHILD_ERR ? 'stderr' : | |
'wtf' ); | |
while ( <$fh> ) { | |
chomp; | |
/^$/ and next; | |
say $tag, $_; | |
} | |
} | |
} | |
my ( $_help, $_debug ); | |
if ( !GetOptions( 'timeout=i' => \$timeout, | |
'name=s' => \$name, | |
'dir=s' => \$basedir, | |
'chdir=s' => \$chdir, | |
'lock' => \$lock, | |
'debug' => \$_debug, | |
'help' => \$_help ) | |
|| $_help | |
|| $#ARGV < 0) { | |
print <<END; | |
USAGE: $0 [OPTIONS] -- command to run ... | |
OPTIONS: | |
--timeout,-t SECONDS | |
--name,-n NAME Name of process (default: basename of given command) | |
--dir,-d DIR Base directory to write files to (default: current dir) | |
--chdir,-c DIR Change directory before running command | |
--lock,-l Don't allow running multiple instances of process at the same time | |
--debug Print log to stderr instead of file | |
END | |
exit !$_help; | |
} | |
chdir $chdir if defined $chdir; | |
$name ||= basename($ARGV[0]); | |
$basedir ||= getcwd; | |
$command = join(' ', @ARGV); | |
open LOGFILE, $_debug ? ">&STDERR" : ">>".File::Spec->catfile($basedir, $name).".log"; | |
unless ( flock(LOGFILE, ($lock ? LOCK_EX : LOCK_SH)|LOCK_NB) ) { | |
say 'locked', "Can't obtain lock", reason => $!; | |
exit -1; | |
} | |
$SIG{CHLD} = sub { | |
say 'sigchld', $command if $_debug; | |
$exited = 1; | |
}; | |
$pid = open3('<&0', \*CHILD_OUT, \*CHILD_ERR, @ARGV); | |
say 'start', $command, lock => $lock, timeout => $timeout, cwd => getcwd; | |
if ( defined $timeout ) { | |
$SIG{ALRM} = sub { | |
say 'timeout', $command; | |
# TODO: graceful kill? | |
kill 'TERM', $pid; | |
}; | |
alarm $timeout; | |
} | |
our $sel = IO::Select->new(\*CHILD_OUT, \*CHILD_ERR); | |
while ( my @ready = $sel->can_read(0.1) ) { | |
handle @ready; | |
last if $exited; | |
} | |
handle(\*CHILD_OUT, \*CHILD_ERR); | |
waitpid $pid, 0; | |
my ($rv, $sig) = ($?>>8, $?&127); | |
my %meta; | |
$meta{status} = $rv; | |
$meta{signal} = $sig if $sig; | |
say 'finished', $command, %meta; | |
exit ($rv || ($sig?-1:0)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment