From 0b115350838a5f5b32484cfe89aeab83307ae4d5 Mon Sep 17 00:00:00 2001 From: Juan Hernandez Date: Tue, 6 Jun 2017 20:28:37 +0200 Subject: [PATCH] Improve console and log output This patch changes the tools so that they automatically generate a log file that contains all the details of the execution. The console will now only contain the more relevant messages. For example, the 'build' tool will only present the following messages in the console: [INFO] Log file is '/projects/ovirt-containers/build.log' [INFO] Loading project file '/projects/ovirt-containers/project.conf' [INFO] Building image 'ovirt/base:master' [INFO] Building image 'ovirt/engine:master' [INFO] Building image 'ovirt/engine-database:master' [INFO] Building image 'ovirt/engine-spice-proxy:master' [INFO] Building image 'ovirt/vdsc:master' [INFO] Building image 'ovirt/vdsc-syslog:master' [INFO] Tool finished successfully The rest of the details, including the commands executed and their output, will be available in the 'build.log' file. In addition the messages presented in the console will have a touch of color. The [INFO] prefix will be green and the [ERROR] prefix will be red. Change-Id: I85a6887693df9771cc5f3810db62d6371e34b121 Signed-off-by: Juan Hernandez --- Makefile | 10 +- tools/src/ovc/build.go | 5 +- tools/src/ovc/build/commands.go | 12 +-- tools/src/ovc/build/templates.go | 9 +- tools/src/ovc/clean.go | 8 +- tools/src/ovc/log/log.go | 174 +++++++++++++++++++++++++++++++ tools/src/ovc/main.go | 42 +++++--- tools/src/ovc/push.go | 8 +- tools/src/ovc/save.go | 8 +- 9 files changed, 226 insertions(+), 50 deletions(-) create mode 100644 tools/src/ovc/log/log.go diff --git a/Makefile b/Makefile index 4b83a90..9f03a2f 100644 --- a/Makefile +++ b/Makefile @@ -62,22 +62,22 @@ tool: $(TOOL_BINARY) .PHONY: build build: $(TOOL_BINARY) - $< $@ 2>&1 | tee $@.log + $< $@ .PHONY: save save: $(TOOL_BINARY) - $< $@ 2>&1 | tee $@.log + $< $@ .PHONY: push push: $(TOOL_BINARY) - $< $@ 2>&1 | tee $@.log + $< $@ .PHONY: deploy deploy: $(TOOL_BINARY) - $< $@ 2>&1 | tee $@.log + $< $@ .PHONY: deploy clean: $(TOOL_BINARY) - $< $@ 2>&1 | tee $@.log + $< $@ rm -rf tools/{bin,pkg} docker images --filter dangling=true --quiet | xargs -r docker rmi --force diff --git a/tools/src/ovc/build.go b/tools/src/ovc/build.go index f30c029..5e40c72 100644 --- a/tools/src/ovc/build.go +++ b/tools/src/ovc/build.go @@ -22,14 +22,15 @@ import ( "fmt" "ovc/build" + "ovc/log" ) func buildTool(project *build.Project) error { for _, image := range project.Images().List() { - fmt.Printf("Building image '%s'.\n", image) + log.Info("Building image '%s'", image) err := image.Build() if err != nil { - return fmt.Errorf("Failed to build image '%s'.\n", image) + return fmt.Errorf("Failed to build image '%s'", image) } } diff --git a/tools/src/ovc/build/commands.go b/tools/src/ovc/build/commands.go index 34f5a20..b224999 100644 --- a/tools/src/ovc/build/commands.go +++ b/tools/src/ovc/build/commands.go @@ -20,10 +20,10 @@ package build // evaluation of external commands. import ( - "fmt" - "os" "os/exec" "strings" + + "ovc/log" ) // RunCommand executes the given command and waits till it finishes. The @@ -31,10 +31,10 @@ import ( // the standard output and standard error of the calling program. // func RunCommand(name string, args ...string) error { - fmt.Printf("Running command '%s' with arguments '%s'\n", name, strings.Join(args, " ")) + log.Debug("Running command '%s' with arguments '%s'", name, strings.Join(args, " ")) command := exec.Command(name, args...) - command.Stdout = os.Stdout - command.Stderr = os.Stderr + command.Stdout = log.DebugWriter() + command.Stderr = log.ErrorWriter() return command.Run() } @@ -43,7 +43,7 @@ func RunCommand(name string, args ...string) error { // execution of the command fails it returns nil. // func EvalCommand(name string, args ...string) []byte { - fmt.Printf("Evaluating command '%s' with arguments '%s'\n", name, strings.Join(args, " ")) + log.Debug("Evaluating command '%s' with arguments '%s'", name, strings.Join(args, " ")) bytes, err := exec.Command(name, args...).Output() if err != nil { return nil diff --git a/tools/src/ovc/build/templates.go b/tools/src/ovc/build/templates.go index 9b358b3..4b250a7 100644 --- a/tools/src/ovc/build/templates.go +++ b/tools/src/ovc/build/templates.go @@ -23,6 +23,8 @@ import ( "os" "path/filepath" "text/template" + + "ovc/log" ) // Context contains the objects passed to templates when they are @@ -67,11 +69,7 @@ func ProcessTemplate(project *Project, in string, out string) error { }) // Parse the template: - fmt.Printf( - "Processing template '%s' and writing result to '%s'.\n", - in, - out, - ) + log.Debug("Loading template from file '%s'", in) _, err := tmpl.ParseFiles(in) if err != nil { return err @@ -83,6 +81,7 @@ func ProcessTemplate(project *Project, in string, out string) error { if err != nil { return err } + log.Debug("Creating template result file '%s'", out) file, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY, info.Mode()) if err != nil { return err diff --git a/tools/src/ovc/clean.go b/tools/src/ovc/clean.go index 3741812..fcade2c 100644 --- a/tools/src/ovc/clean.go +++ b/tools/src/ovc/clean.go @@ -23,6 +23,7 @@ import ( "fmt" "ovc/build" + "ovc/log" ) func cleanTool(project *build.Project) error { @@ -34,13 +35,10 @@ func cleanTool(project *build.Project) error { images := project.Images().List() for i := len(images) - 1; i >= 0; i-- { image := images[i] - fmt.Printf("Remove image '%s'\n", image) + log.Info("Remove image '%s'", image) err := image.Remove() if err != nil { - return fmt.Errorf( - "Failed to remove image '%s'", - image, - ) + return fmt.Errorf("Failed to remove image '%s'", image) } } diff --git a/tools/src/ovc/log/log.go b/tools/src/ovc/log/log.go new file mode 100644 index 0000000..8f2c106 --- /dev/null +++ b/tools/src/ovc/log/log.go @@ -0,0 +1,174 @@ +/* +Copyright (c) 2017 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file contains functions useful for generating log files. + +package log + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" +) + +// The log file. +// +var path string +var file *os.File + +// The writers for the different levels. +// +var errorWriter io.Writer +var infoWriter io.Writer +var debugWriter io.Writer + +// prefixWriter implements a writer that adds a prefix to each line. +// +type prefixWriter struct { + prefix string + stream io.Writer + start bool +} + +// newPrefixWriter creates a new prefix writer that adds the given +// prefix and writes the modified lines to the given stream. +// +func newPrefixWriter(stream io.Writer, prefix string) io.Writer { + p := new(prefixWriter) + p.prefix = prefix + p.stream = stream + p.start = true + return p +} + +func (p *prefixWriter) Write(data []byte) (count int, err error) { + buffer := new(bytes.Buffer) + buffer.Grow(len(data)) + for _, char := range data { + if p.start { + buffer.WriteString(p.prefix) + p.start = false + } + buffer.WriteByte(char) + if char == 10 { + p.start = true + } + } + count = len(data) + _, err = p.stream.Write(buffer.Bytes()) + return +} + +// newLogWriter creates a log writer that will write to the given file +// and console. Each line will have the prefix added, in the given +// color. The file and the console can be nil. +// +func newLogWriter(file, console io.Writer, prefix, color string) io.Writer { + writers := make([]io.Writer, 0) + if file != nil { + plain := fmt.Sprintf("[%s] ", prefix) + file = newPrefixWriter(file, plain) + writers = append(writers, file) + } + if console != nil { + colored := fmt.Sprintf("\033[%sm[%s]\033[m ", color, prefix) + console = newPrefixWriter(console, colored) + writers = append(writers, console) + } + return io.MultiWriter(writers...) +} + +// Open creates a log file with the given name and the .log extension, +// and configures the log to write to it. +// +func Open(name string) error { + var err error + + path, err = filepath.Abs(name + ".log") + if err != nil { + return err + } + file, err = os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0664) + if err != nil { + return err + } + errorWriter = newLogWriter(file, os.Stderr, "ERROR", "0;31") + infoWriter = newLogWriter(file, os.Stdout, "INFO", "0;32") + debugWriter = newLogWriter(file, nil, "DEBUG", "0;34") + return nil +} + +// Close closes the log file. +// +func Close() error { + return file.Close() +} + +// Path the returns the path of the log file. +// +func Path() string { + return path +} + +// Info sends an informative message to the log file and to the standard +// ouptut of the process. +// +func Info(format string, args ...interface{}) { + write(infoWriter, format, args...) +} + +// Debug sends a debug message to the log file. +// +func Debug(format string, args ...interface{}) { + write(debugWriter, format, args...) +} + +// Error sends an error message to the log file and to the standard +// error stream of the process. +// +func Error(format string, args ...interface{}) { + write(errorWriter, format, args...) +} + +func write(writer io.Writer, format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + writer.Write([]byte(message)) + writer.Write([]byte("\n")) +} + +// InfoWriter returns the writer that writes informative messages to the +// log file and to the standard output of the process. +// +func InfoWriter() io.Writer { + return infoWriter +} + +// DebugWriter returns the writer that writes debug messages to the log +// file. +// +func DebugWriter() io.Writer { + return debugWriter +} + +// ErrorWriter returns the writer that writes error messages to the log +// file and to the standard error stream of the process. +// file. +// +func ErrorWriter() io.Writer { + return errorWriter +} diff --git a/tools/src/ovc/main.go b/tools/src/ovc/main.go index 24df683..3f600f1 100644 --- a/tools/src/ovc/main.go +++ b/tools/src/ovc/main.go @@ -24,6 +24,7 @@ import ( "path/filepath" "ovc/build" + "ovc/log" ) // ToolFunc is the type of functions that implement tools. @@ -43,11 +44,7 @@ var toolsIndex = map[string]ToolFunc{ func main() { // Get the name of the tool: if len(os.Args) < 2 { - fmt.Fprintf( - os.Stderr, - "Usage: %s TOOL [ARGS...]\n", - filepath.Base(os.Args[0]), - ) + fmt.Fprintf(os.Stderr, "Usage: %s TOOL [ARGS...]\n", filepath.Base(os.Args[0])) os.Exit(1) } toolName := os.Args[1] @@ -55,31 +52,42 @@ func main() { // Find the function that corresponds to the tool name: toolFunc := toolsIndex[toolName] if toolFunc == nil { - fmt.Fprintf( - os.Stderr, - "Can't find tool named '%s'.\n", - toolName, - ) + fmt.Fprintf(os.Stderr, "Can't find tool named '%s'.\n", toolName) os.Exit(1) } + // Open the log: + log.Open(toolName) + log.Info("Log file is '%s'", log.Path()) + // Load the project: - project, err := build.LoadProject("") + path, _ := filepath.Abs("project.conf") + log.Info("Loading project file '%s'", path) + project, err := build.LoadProject(path) if err != nil { - fmt.Fprintf(os.Stderr, "Can't load project: %s\n", err) + log.Error("%s", err) os.Exit(1) } // Call the tool function, and close the project regardless of // the result: + log.Debug("Running tool '%s'", toolName) err = toolFunc(project) + project.Close() - // Check the result of the tool: + // Report the result: + var code int if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) + log.Error("%s", err) + log.Error("Tool failed, check log file '%s' for details", log.Path()) + code = 1 + } else { + log.Info("Tool finished successfully") + code = 0 } + log.Debug("Exit code is %d", code) - // Bye: - os.Exit(0) + // Close the log and exit: + log.Close() + os.Exit(code) } diff --git a/tools/src/ovc/push.go b/tools/src/ovc/push.go index 9313c60..c7ff30e 100644 --- a/tools/src/ovc/push.go +++ b/tools/src/ovc/push.go @@ -22,17 +22,15 @@ import ( "fmt" "ovc/build" + "ovc/log" ) func pushTool(project *build.Project) error { for _, image := range project.Images().List() { - fmt.Printf("Pushing image '%s'\n", image) + log.Info("Pushing image '%s'", image) err := image.Push() if err != nil { - return fmt.Errorf( - "Failed to push image '%s': %s", - image, err, - ) + return fmt.Errorf("Failed to push image '%s': %s", image, err) } } diff --git a/tools/src/ovc/save.go b/tools/src/ovc/save.go index 9dc36b4..f93a67c 100644 --- a/tools/src/ovc/save.go +++ b/tools/src/ovc/save.go @@ -22,17 +22,15 @@ import ( "fmt" "ovc/build" + "ovc/log" ) func saveTool(project *build.Project) error { for _, image := range project.Images().List() { - fmt.Printf("Saving image '%s'\n", image) + log.Info("Saving image '%s'", image) err := image.Save() if err != nil { - return fmt.Errorf( - "Failed to save image '%s': %s", - image, err, - ) + return fmt.Errorf("Failed to save image '%s': %s", image, err) } }