Skip to content

Commit

Permalink
Improve console and log output
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
jhernand committed Jun 26, 2017
1 parent 737a816 commit 0b11535
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 50 deletions.
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 3 additions & 2 deletions tools/src/ovc/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
12 changes: 6 additions & 6 deletions tools/src/ovc/build/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ 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
// standard output and standard error of the command are redirected to
// 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()
}

Expand All @@ -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
Expand Down
9 changes: 4 additions & 5 deletions tools/src/ovc/build/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"os"
"path/filepath"
"text/template"

"ovc/log"
)

// Context contains the objects passed to templates when they are
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 3 additions & 5 deletions tools/src/ovc/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"fmt"

"ovc/build"
"ovc/log"
)

func cleanTool(project *build.Project) error {
Expand All @@ -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)
}
}

Expand Down
174 changes: 174 additions & 0 deletions tools/src/ovc/log/log.go
Original file line number Diff line number Diff line change
@@ -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
}
42 changes: 25 additions & 17 deletions tools/src/ovc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"path/filepath"

"ovc/build"
"ovc/log"
)

// ToolFunc is the type of functions that implement tools.
Expand All @@ -43,43 +44,50 @@ 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]

// 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)
}
Loading

0 comments on commit 0b11535

Please sign in to comment.