From 0de280546c0a83c1a5adf22bffb668ae68513835 Mon Sep 17 00:00:00 2001 From: Rafael Quintela Date: Thu, 2 Jul 2020 16:00:06 -0300 Subject: [PATCH] ref(log) fixes and improvments 1. fix mixed output when running commands async 3. now you can format time 2. now you can format commands output --- README.md | 7 +- pkg/wtc/config.go | 7 +- pkg/wtc/wtc.go | 176 +++++++++++++++++++++++++++++++++------------- 3 files changed, 136 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index be160e2..074357c 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,11 @@ no_trace: false debounce: 300 # if rule has no debounce, this will be used instead ignore: \.git/ format: - ok: "{{.Time}} \u001b[38;5;2m[{{.Name}}]\u001b[0m - {{.Command}}\n" - fail: "{{.Time}} \u001b[38;5;1m[{{.Name}}]\u001b[0m - {{.Error}}\n" + time: "15:04:05" # golang format + ok: "\u001b[38;5;244m[{{.Time}}] \u001b[38;5;2m[{{.Title}}]\u001b[0m \u001b[38;5;238m{{.Message}}\u001b[0m\n" + fail: "\u001b[38;5;244m[{{.Time}}] \u001b[38;5;1m[{{.Title}}] \u001b[38;5;238m{{.Message}}\u001b[0m\n" + command_ok: "\u001b[38;5;240m[{{.Time}}] [{{.Title}}] \u001b[0m{{.Message}}\n" + command_err: "\u001b[38;5;240m[{{.Time}}] [{{.Title}}] \u001b[38;5;1m{{.Message}}\u001b[0m\n" trig: [start, buildNRun] # will run on start trig_async: true env: diff --git a/pkg/wtc/config.go b/pkg/wtc/config.go index 8bddf93..bee5935 100644 --- a/pkg/wtc/config.go +++ b/pkg/wtc/config.go @@ -11,8 +11,11 @@ type Config struct { ExitOnTrig bool `yaml:"-"` Env []*Env `yaml:"env"` Format struct { - OK string `yaml:"ok"` - Fail string `yaml:"fail"` + OK string `yaml:"ok"` + Fail string `yaml:"fail"` + CommandOK string `yaml:"command_ok"` + CommandErr string `yaml:"command_err"` + Time string `yaml:"time"` } `yaml:"format"` } diff --git a/pkg/wtc/wtc.go b/pkg/wtc/wtc.go index 8fed6f7..7b44f27 100644 --- a/pkg/wtc/wtc.go +++ b/pkg/wtc/wtc.go @@ -1,10 +1,11 @@ package wtc import ( + "bufio" "context" "flag" "fmt" - "html/template" + "io" "io/ioutil" "log" "os" @@ -26,16 +27,40 @@ var ( contextsLock map[string]chan struct{} ctxmutex sync.Mutex contextsLockMutext sync.Mutex +) + +var ( + logger chan []byte + templateRegex = regexp.MustCompile(`\{\{\.([^}]+)\}\}`) - okFormat *template.Template - failFormat *template.Template + TimeFormat = "15:04:05" + + TypeOK = "\u001b[38;5;244m[{{.Time}}] \u001b[38;5;2m[{{.Title}}]\u001b[0m \u001b[38;5;238m{{.Message}}\u001b[0m\n" + TypeFail = "\u001b[38;5;244m[{{.Time}}] \u001b[38;5;1m[{{.Title}}] \u001b[38;5;238m{{.Message}}\u001b[0m\n" + TypeCommandOK = "\u001b[38;5;240m[{{.Time}}] [{{.Title}}] \u001b[0m{{.Message}}\n" + TypeCommandErr = "\u001b[38;5;240m[{{.Time}}] [{{.Title}}] \u001b[38;5;1m{{.Message}}\u001b[0m\n" ) -type formatPayload struct { - Name string +type Line struct { + Type string Time string - Command string - Error string + Title string + Message string +} + +func (l *Line) Log() { + logger <- []byte(templateRegex.ReplaceAllStringFunc(l.Type, func(k string) string { + switch k[3:][:len(k)-5] { + case "Time": + return time.Now().Format(TimeFormat) + case "Title": + return l.Title + case "Message": + return l.Message + default: + return "" + } + })) } func getContext(label string) (context.Context, context.CancelFunc) { @@ -103,25 +128,23 @@ func ParseArgs() *Config { config.ExitOnTrig = true } - if config.Format.OK == "" { - config.Format.OK = "\u001b[38;5;244m[{{.Time}}] \u001b[1m\u001b[38;5;2m[{{.Name}}]\033[0m " + - "\u001b[38;5;238m{{.Command}}\u001b[0m\n" + if config.Format.OK != "" { + TypeOK = config.Format.OK } - if config.Format.Fail == "" { - config.Format.Fail = "\u001b[38;5;244m[{{.Time}}] \u001b[1m\u001b[38;5;1m[{{.Name}} failed]\u001b[0m " + - "\u001b[38;5;238m{{.Error}}\u001b[0m\n" + if config.Format.Fail != "" { + TypeFail = config.Format.Fail } - var err error - okFormat, err = template.New("okFormat").Parse(config.Format.OK) - if err != nil { - log.Fatal("Invalid Ok Format") - return nil + + if config.Format.CommandOK != "" { + TypeCommandOK = config.Format.CommandOK } - failFormat, err = template.New("failFormat").Parse(config.Format.Fail) - if err != nil { - log.Fatal("Invalid Fail Format") - return nil + + if config.Format.CommandErr != "" { + TypeCommandErr = config.Format.CommandErr + } + if config.Format.Time != "" { + TimeFormat = config.Format.Time } return config @@ -140,6 +163,16 @@ func Start(cfg *Config) { log.Fatal(err) } + logger = make(chan []byte, 256) + go func() { + for { + select { + case l := <-logger: + os.Stdout.Write(l) + } + } + }() + go func() { findAndTrig(config.TrigAsync, config.Trig, "./", "./") if config.ExitOnTrig { @@ -194,13 +227,11 @@ func Start(cfg *Config) { if rule.Match != "" && retrieveRegexp(rule.Match).MatchString(path) { go func() { if err := trig(rule, pkg, path); err != nil { - _ = failFormat.Execute(os.Stderr, formatPayload{ - Name: rule.Name, - Time: time.Now().Format("15:04:05"), - Error: err.Error(), - Command: strings.Replace(strings.Replace(rule.Command, "{PKG}", pkg, -1), - "{FILE}", path, -1), - }) + (&Line{ + Type: TypeFail, + Title: rule.Name, + Message: err.Error(), + }).Log() } }() } @@ -274,13 +305,11 @@ func findAndTrig(async bool, key []string, pkg, path string) { r := r fn := func() { if err := trig(r, pkg, path); err != nil { - _ = failFormat.Execute(os.Stderr, formatPayload{ - Name: r.Name, - Time: time.Now().Format("15:04:05"), - Error: err.Error(), - Command: strings.Replace(strings.Replace(r.Command, "{PKG}", pkg, -1), - "{FILE}", path, -1), - }) + (&Line{ + Type: TypeFail, + Title: r.Name, + Message: err.Error(), + }).Log() } } @@ -300,11 +329,11 @@ func findAndTrig(async bool, key []string, pkg, path string) { } if !found { - _ = failFormat.Execute(os.Stderr, formatPayload{ - Name: s, - Time: time.Now().Format("15:04:05"), - Error: "rule not found", - }) + (&Line{ + Type: TypeFail, + Title: s, + Message: "rule not found", + }).Log() } } @@ -381,14 +410,14 @@ func trig(rule *Rule, pkg, path string) error { } if !config.NoTrace { - _ = okFormat.Execute(os.Stdout, formatPayload{ - Name: rule.Name, - Time: time.Now().Format("15:04:05"), - Command: cmd, - }) + (&Line{ + Type: TypeOK, + Title: rule.Name, + Message: cmd, + }).Log() } - err := run(ctx, cmd, envs) + err := run(ctx, rule.Name, cmd, envs) if err == context.Canceled { return nil } @@ -403,10 +432,57 @@ func trig(rule *Rule, pkg, path string) error { return nil } -func run(ctx context.Context, command string, env []string) error { +func run(ctx context.Context, name, command string, env []string) error { cmd := exec.Command("sh", "-c", command) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr + + { + rr, ww := io.Pipe() + defer ww.Close() + + reader := bufio.NewReader(rr) + go func() { + defer rr.Close() + for { + l, _, err := reader.ReadLine() + if err == io.EOF { + return + } + + (&Line{ + Type: TypeCommandOK, + Title: name, + Message: string(l), + }).Log() + } + }() + + cmd.Stdout = ww + } + + { + rr, ww := io.Pipe() + defer ww.Close() + + reader := bufio.NewReader(rr) + go func() { + defer rr.Close() + for { + l, _, err := reader.ReadLine() + if err == io.EOF { + return + } + + (&Line{ + Type: TypeCommandErr, + Title: name, + Message: string(l), + }).Log() + } + }() + + cmd.Stderr = ww + } + cmd.Env = env // ask Go to create a new Process Group for this process