% horenso -r reporter.pl -- /path/to/job args...
2>&1 | logger ...
とかやっても、コマンドの成否自体は後続のコマンドからは知るすべはない% /path/to/wrapper /path/to/job ...
ラッパーコマンドが、実行コマンドを受け取り、そのコマンドを実行した後に、事後処理をおこなう
@kazuho さんによるPerlスクリプト
% cronlog -- ping -n 5 my-server 2>&1
ジョブが失敗した時だけ、出力を出す
→ cronに指定すると、失敗した時だけメールが飛ぶとかできる
拙作のPerl製ジョブ通知フレームワーク
% ./wrapper.(sh|pl) /path/to/job ...
https://github.com/Songmu/horenso/releases よりバイナリ取得可能
go get
する手もあります
% go get github.com/Songmu/horenso/cmd/horenso
$ /path/to/job args...
こういうジョブに対して
$ horenso --reporter reporter.pl -- /path/to/job args...
このようにラップする。(reporter.plは任意の通知用スクリプト・後述)
{
"command": "perl -E 'say 1;warn \"$$\\n\";sleep 2'",
"commandArgs": [
"perl",
"-E",
"say 1;warn \"$$\\n\";sleep 2"
],
"output": "57078\n1\n",
"stdout": "1\n",
"stderr": "57078\n",
"exitCode": 0,
"result": "command exited with code: 0",
"hostname": "MatsukiMasayuki-no-MacBook-Pro.local",
"pid": 57078,
"startAt": "2016-02-16T01:27:08.946009881+09:00",
"endAt": "2016-02-16T01:27:11.010627341+09:00",
"systemTime": 0.03176,
"userTime": 0.025954
}
use JSON::PP;
my $report = decode_json <>; // 結果を取り出す
$report->{startAt};
...
import (
"encoding/json"
"os"
"github.com/Songmu/horenso"
)
func main() {
var d = horenso.Report{}
json.NewDecoder(os.Stdin).Decode(&d)
d.StartAt
...
}
% horenso --noticer 'ruby noticer.rb' \
-r reporter.pl -- /path/to/job args...
時間のかかるジョブの実行前に通知させたい場合
% horenso -r reporter.pl -r reporter2.py -- /path/to/job args...
複数のreporterを指定した場合、平行に実行される(Goっぽい)
#!/bin/sh
exec /path/to/horenso \
-n /path/to/noticer.py \
-r /path/to/reporter.pl \
-r 'ruby /path/to/reporter.rb' \
-- "$@"
これを例えば以下のようにcrontabに指定する
3 4 * * * /path/to/wrapper.sh /path/to/job --args... 2>&1 | logger -t myjob
別にcronじゃなくても、バッチジョブ実行の仕組みに対して統一的に導入すると便利。
Goっぽさある。
// コマンドからパイプを取り出す
cmd := exec.Command(args[0], args[1:]...)
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()
// 出力を格納する bytes.Buffer を宣言
var bufStdout bytes.Buffer
var bufStderr bytes.Buffer
var bufMerged bytes.Buffer
// pipeの書き出し先 io.TeeReader で追加
stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(&bufStdout, &bufMerged))
stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(&bufStderr, &bufMerged))
// コマンドの実行と io.Copy
cmd.Start()
go func() {
defer stdoutPipe.Close()
io.Copy(os.Stdout, stdoutPipe2)
}
go func() {
defer stderrPipe.Close()
io.Copy(os.Stderr, stderrPipe2)
}
cmd.Wait()
// 取り出す
out := bufStdout.String()
stderr := bufStderr.String()
merged := bufMerged.String()
cmd := exec.Command(args[0], args[1:]...)
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()
var bufStdout bytes.Buffer
var bufStderr bytes.Buffer
var bufMerged bytes.Buffer
stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(&bufStdout, &bufMerged))
stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(&bufStderr, &bufMerged))
cmd.Start()
go func() {
defer stdoutPipe.Close()
io.Copy(os.Stdout, stdoutPipe2)
}
go func() {
defer stderrPipe.Close()
io.Copy(os.Stderr, stderrPipe2)
}
cmd.Wait()
out := bufStdout.String()
stderr := bufStderr.String()
merged := bufMerged.String()
err := cmd.Run()
exitCode := 0
if e, ok := err.(*exec.ExitError); ok {
if w, ok := e.Sys().(syscall.WaitStatus); ok {
if w.Signaled() {
// 無理やり感
exitCode = int(w) | 0x80
} else {
exitCode = s.ExitStatus()
}
}
exitCode = -1
}
https://github.com/Songmu/wrapcommander に切り出し
io.Writer
interfaceを実装Write()
が呼ばれるたびにメモリアロケートしてるので現状効率悪い