Repository: redhat-certification/chart-verifier
Pull request #240 (2022-01-27 20:56:00)

 report to stdout and error log
New behavior:

Default:

- report goes to stdout
- all other messages go to stderr
- all other messages and log are written to chartverifier/verifier-```<timestamp>```.log
   -  Once 10 files have been created, the oldest will be deleted before a new one is created.

Options

- write report to a file. e.g.: chartverifier/report.yaml
   - if file exists is is replaced.
- suppress log file output
- errors are still output to stderr.

Notes:

 - log-ouput - flag is removed

Have also added a new unit test and updated other test for report output to stdout. 

Will update README separately to time with a release.


Patch:
diff --git a/cmd/report.go b/cmd/report.go
index 7250e54..7f90343 100644
--- a/cmd/report.go
+++ b/cmd/report.go
@@ -9,6 +9,7 @@ import (
 	"gopkg.in/yaml.v3"
 
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/report"
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
 )
 
 func init() {
@@ -36,6 +37,16 @@ func NewReportCmd(config *viper.Viper) *cobra.Command {
 		Short: "Provides information from a report",
 		RunE: func(cmd *cobra.Command, args []string) error {
 
+			reportName := ""
+			if reportToFile {
+				if outputFormatFlag == "json" {
+					reportName = "report-info.json"
+				} else {
+					reportName = "report-info.yaml"
+				}
+			}
+			utils.InitLog(cmd, reportName, true)
+
 			commandArg := args[0]
 			reportArg := args[1]
 
@@ -56,20 +67,24 @@ func NewReportCmd(config *viper.Viper) *cobra.Command {
 				return errors.New(fmt.Sprintf("Error executing command: %v", err))
 			}
 
+			output := ""
 			if outputFormatFlag == "yaml" {
 				b, err := yaml.Marshal(result)
 				if err != nil {
+					utils.LogError(err.Error())
 					return err
 				}
-
-				cmd.Println(string(b))
+				output = string(b)
 			} else {
 				b, err := json.Marshal(result)
 				if err != nil {
+					utils.LogError(err.Error())
 					return err
 				}
-				cmd.Println(string(b))
+				output = string(b)
 			}
+			utils.WriteStdOut(output)
+
 			return nil
 		},
 	}
@@ -80,5 +95,7 @@ func NewReportCmd(config *viper.Viper) *cobra.Command {
 
 	cmd.Flags().StringSliceVarP(&reportOpts.ValueFiles, "set-values", "f", nil, "specify report configuration values in a YAML file or a URL (can specify multiple)")
 
+	cmd.Flags().BoolVarP(&reportToFile, "write-to-file", "w", false, "write report to report-info.json (default: stdout)")
+
 	return cmd
 }
diff --git a/cmd/verify.go b/cmd/verify.go
index 1c4d9e8..dbf7069 100644
--- a/cmd/verify.go
+++ b/cmd/verify.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/checks"
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/profiles"
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
 
 	"helm.sh/helm/v3/pkg/cli"
 	"helm.sh/helm/v3/pkg/cli/values"
@@ -33,7 +34,6 @@ import (
 	"gopkg.in/yaml.v3"
 
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier"
-	"github.com/redhat-certification/chart-verifier/pkg/tool"
 )
 
 func init() {
@@ -54,8 +54,10 @@ var (
 	setOverridesFlag []string
 	// openshiftVersionFlag set the value of `certifiedOpenShiftVersions` in the report
 	openshiftVersionFlag string
-	// output logs flag
-	outputLogs bool
+	// write report to file
+	reportToFile bool
+	// write an error log file
+	suppressErrorLog bool
 )
 
 func filterChecks(set profiles.FilteredRegistry, subset []string, setEnabled bool, subsetEnabled bool) (chartverifier.FilteredRegistry, error) {
@@ -116,9 +118,23 @@ func NewVerifyCmd(config *viper.Viper) *cobra.Command {
 		Short: "Verifies a Helm chart by checking some of its characteristics",
 		RunE: func(cmd *cobra.Command, args []string) error {
 
+			reportName := ""
+			if reportToFile {
+				if outputFormatFlag == "json" {
+					reportName = "report.json"
+				} else {
+					reportName = "report.yaml"
+				}
+			}
+			utils.InitLog(cmd, reportName, suppressErrorLog)
+
+			utils.LogInfo(fmt.Sprintf("Chart Verifer %s.", Version))
+			utils.LogInfo(fmt.Sprintf("Verify : %s", args[0]))
+
 			// vals is a resulting map considering all the options the user has given.
 			vals, err := opts.MergeValues(getter.All(settings))
 			if err != nil {
+				utils.LogError(err.Error())
 				return err
 			}
 
@@ -129,6 +145,7 @@ func NewVerifyCmd(config *viper.Viper) *cobra.Command {
 
 			checks, err := buildChecks(allChecks, verifierBuilder.GetConfig(), enabledChecksFlag, disabledChecksFlag)
 			if err != nil {
+				utils.LogError(err.Error())
 				return err
 			}
 
@@ -144,33 +161,29 @@ func NewVerifyCmd(config *viper.Viper) *cobra.Command {
 
 			result, err := verifier.Verify(args[0])
 			if err != nil {
+				utils.LogError(err.Error())
 				return err
 			}
 
+			outputContent := ""
 			if outputFormatFlag == "json" {
 				b, err := json.Marshal(result)
 				if err != nil {
+					utils.LogError(err.Error())
 					return err
 				}
-
-				cmd.Println(string(b))
-
+				outputContent = string(b)
 			} else {
 				b, err := yaml.Marshal(result)
 				if err != nil {
 					return err
 				}
-				cmd.Println(string(b))
-
-				if outputLogs {
-					logs, err := tool.GetLogsOutput(outputFormatFlag)
-					if err != nil {
-						cmd.Println(fmt.Sprintf("LoggingError: %v", err))
-					} else if len(logs) > 0 {
-						cmd.Println(logs)
-					}
-				}
+				outputContent = string(b)
 			}
+			utils.WriteStdOut(outputContent)
+
+			utils.WriteLogs(outputFormatFlag)
+
 			return nil
 		},
 	}
@@ -195,8 +208,8 @@ func NewVerifyCmd(config *viper.Viper) *cobra.Command {
 
 	cmd.Flags().StringSliceVarP(&verifyOpts.ValueFiles, "set-values", "f", nil, "specify application and check configuration values in a YAML file or a URL (can specify multiple)")
 	cmd.Flags().StringVarP(&openshiftVersionFlag, "openshift-version", "V", "", "version of OpenShift used in the cluster")
-	cmd.Flags().BoolVarP(&outputLogs, "log-output", "l", false, "output logs after report (default: false) ")
-
+	cmd.Flags().BoolVarP(&reportToFile, "write-to-file", "w", false, "write report to report.yaml (default: stdout)")
+	cmd.Flags().BoolVarP(&suppressErrorLog, "suppress-error-log", "E", false, "suppress the error log (default: written to ./chart-verifier.log)")
 	return cmd
 }
 
diff --git a/pkg/chartverifier/profiles/profile.go b/pkg/chartverifier/profiles/profile.go
index df9f0b8..3ea875d 100644
--- a/pkg/chartverifier/profiles/profile.go
+++ b/pkg/chartverifier/profiles/profile.go
@@ -3,6 +3,7 @@ package profiles
 import (
 	"fmt"
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/checks"
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
 	"github.com/redhat-certification/chart-verifier/pkg/profileconfig"
 	"github.com/spf13/viper"
 	"golang.org/x/mod/semver"
@@ -109,7 +110,7 @@ func New(config *viper.Viper) *Profile {
 			}
 		}
 	}
-
+	utils.LogInfo(fmt.Sprintf("Profile in use: %s %s", profileInUse.Vendor, profileInUse.Version))
 	return profileInUse
 }
 
diff --git a/pkg/chartverifier/reportBuilder.go b/pkg/chartverifier/reportBuilder.go
index 70cda5e..d130945 100644
--- a/pkg/chartverifier/reportBuilder.go
+++ b/pkg/chartverifier/reportBuilder.go
@@ -23,6 +23,7 @@ import (
 	"fmt"
 	"github.com/pkg/errors"
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/profiles"
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
 	"io"
 	"net/http"
 	"net/url"
@@ -99,6 +100,10 @@ func (r *reportBuilder) SetChart(chart *helmchart.Chart) ReportBuilder {
 func (r *reportBuilder) AddCheck(check checks.Check, result checks.Result) ReportBuilder {
 	checkReport := r.Report.AddCheck(check)
 	checkReport.SetResult(result.Ok, result.Reason)
+	utils.LogInfo(fmt.Sprintf("Check: %s:%s result : %t", check.CheckId.Name, check.CheckId.Version, result.Ok))
+	if !result.Ok {
+		utils.LogInfo(fmt.Sprintf("Check: %s:%s reason : %s", check.CheckId.Name, check.CheckId.Version, result.Reason))
+	}
 	return r
 }
 
diff --git a/pkg/chartverifier/utils/logger.go b/pkg/chartverifier/utils/logger.go
new file mode 100644
index 0000000..fe2d11b
--- /dev/null
+++ b/pkg/chartverifier/utils/logger.go
@@ -0,0 +1,222 @@
+package utils
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/spf13/cobra"
+	"gopkg.in/yaml.v3"
+	"io"
+	"io/fs"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"sort"
+	"strings"
+	"time"
+)
+
+type VerifierLog struct {
+	Name    string      `json:"name" yaml:"name"`
+	Time    string      `json:"time" yaml: time"`
+	Entries []*LogEntry `json:"log" yaml:"log"`
+}
+
+type LogEntry struct {
+	Entry string `json:"Entry" yaml:"Entry"`
+}
+
+var CmdStdout io.Writer = os.Stdout
+
+var verifierlog VerifierLog
+var cmd *cobra.Command
+var stdoutFileName string
+var stderrFileName string
+
+const outputDirectory string = "chartverifier"
+
+func InitLog(cobraCmd *cobra.Command, stdFilename string, suppressErrorLog bool) {
+	cmd = cobraCmd
+	stdoutFileName = stdFilename
+	now := time.Now()
+	verifierlog = VerifierLog{Name: "Chart Verifier Log", Time: now.Format("01-02-2006-15-04-05")}
+	if suppressErrorLog {
+		stderrFileName = ""
+	} else {
+		stderrFileName = fmt.Sprintf("verifier-%s.log", verifierlog.Time)
+	}
+}
+
+func LogWarning(message string) {
+	if cmd != nil {
+		cmd.PrintErrln(message)
+	}
+	warning_log_entry := LogEntry{Entry: fmt.Sprintf("[WARNING] %s", message)}
+	verifierlog.Entries = append(verifierlog.Entries, &warning_log_entry)
+}
+
+func LogInfo(message string) {
+	info_log_entry := LogEntry{Entry: fmt.Sprintf("[INFO] %s", message)}
+	verifierlog.Entries = append(verifierlog.Entries, &info_log_entry)
+}
+
+func LogError(message string) {
+	if cmd != nil {
+		cmd.PrintErrln(message)
+	}
+	error_log_entry := LogEntry{Entry: fmt.Sprintf("[ERROR] %s", message)}
+	verifierlog.Entries = append(verifierlog.Entries, &error_log_entry)
+}
+
+func WriteLogs(log_format string) {
+
+	pruneLogFiles()
+
+	if len(verifierlog.Entries) > 0 && len(stderrFileName) > 0 {
+		logOut := ""
+		if log_format == "json" {
+			b, err := json.Marshal(&verifierlog)
+			if err != nil {
+				LogError(err.Error())
+				return
+			}
+			logOut = string(b)
+		} else {
+			b, err := yaml.Marshal(&verifierlog)
+			if err != nil {
+				LogError(err.Error())
+				return
+			}
+			logOut = string(b)
+		}
+		writeToFile(logOut, stderrFileName)
+	}
+	return
+
+}
+
+func WriteStdOut(output string) {
+	fileWriteSuccess := false
+	if len(stdoutFileName) > 0 {
+		fileWriteSuccess = writeToFile(output, stdoutFileName)
+	}
+	if !fileWriteSuccess {
+		writeToStdOut(output)
+	}
+}
+
+func writeToStdOut(output string) {
+	savedOut := cmd.OutOrStdout()
+	cmd.SetOut(CmdStdout)
+	cmd.Println(output)
+	cmd.SetOut(savedOut)
+}
+
+func writeToFile(output string, fileName string) bool {
+
+	currentDir, err := os.Getwd()
+	if err != nil {
+		LogError(fmt.Sprintf("error getting current working directory : %s", err))
+		return false
+	}
+	outputDir := path.Join(currentDir, outputDirectory)
+	outputFile := path.Join(outputDir, fileName)
+	if _, err := os.Stat(outputDir); err != nil {
+		if err = os.MkdirAll(outputDir, 0777); err != nil {
+			LogError(fmt.Sprintf("error creating directory : %s : %s", outputDir, err))
+			return false
+		}
+	}
+
+	if _, err := os.Stat(outputFile); err == nil {
+		err = os.Remove(outputFile)
+	} else if errors.Is(err, os.ErrNotExist) {
+		err = nil
+	} else {
+		LogError(fmt.Sprintf("Error removing existing file %s: %s", fileName, err))
+	}
+
+	if outfile, open_err := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600); open_err == nil {
+		outfile.WriteString(output)
+		outfile.Close()
+		return true
+	} else {
+		LogError(fmt.Sprintf("Error creating/opening file %s: %s", fileName, open_err))
+	}
+	return false
+}
+
+type By func(p1, p2 fs.FileInfo) bool
+
+type fileSorter struct {
+	files []fs.FileInfo
+	by    func(p1, p2 fs.FileInfo) bool // Closure used in the Less method.
+}
+
+func (by By) sort(files []fs.FileInfo) {
+	fs := &fileSorter{
+		files: files,
+		by:    by, // The Sort method's receiver is the function (closure) that defines the sort order.
+	}
+	sort.Sort(fs)
+}
+
+// Len is part of sort.Interface.
+func (fs *fileSorter) Len() int {
+	return len(fs.files)
+}
+
+// Swap is part of sort.Interface.
+func (fs *fileSorter) Swap(i, j int) {
+	fs.files[i], fs.files[j] = fs.files[j], fs.files[i]
+}
+
+// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
+func (fs *fileSorter) Less(i, j int) bool {
+	return fs.by(fs.files[i], fs.files[j])
+}
+
+func pruneLogFiles() {
+
+	currentDir, err := os.Getwd()
+	if err != nil {
+		LogError(fmt.Sprintf("error getting current working directory : %s", err))
+		return
+	}
+	logFilesPath := path.Join(currentDir, outputDirectory)
+
+	if _, err := os.Stat(logFilesPath); err != nil {
+		return
+	}
+
+	files, err := ioutil.ReadDir(logFilesPath)
+	if err != nil {
+		LogError(fmt.Sprintf("error reading log directory : %s : %s", logFilesPath, err))
+		return
+	}
+
+	logFiles := make([]fs.FileInfo, 0)
+	for _, file := range files {
+		if strings.HasPrefix(file.Name(), "verifier") && strings.HasSuffix(file.Name(), ".log") {
+			logFiles = append(logFiles, file)
+		}
+	}
+
+	if len(logFiles) >= 10 {
+		sortByName := func(f1, f2 fs.FileInfo) bool {
+			return f1.Name() < f2.Name()
+		}
+		By(sortByName).sort(logFiles)
+
+		for i := 0; i <= len(logFiles)-10; i++ {
+			LogInfo(fmt.Sprintf("Deleting old log file : %s", logFiles[i].Name()))
+			err = os.Remove(filepath.Join(logFilesPath, logFiles[i].Name()))
+			if err != nil {
+				LogError(fmt.Sprintf("error deleting logfile : %s : %s", logFiles[i].Name(), err))
+			}
+		}
+
+	}
+
+}
diff --git a/pkg/tool/helm.go b/pkg/tool/helm.go
index 918594e..e68df3c 100644
--- a/pkg/tool/helm.go
+++ b/pkg/tool/helm.go
@@ -5,6 +5,8 @@ import (
 	"os"
 	"time"
 
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
+
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/cli"
@@ -23,7 +25,7 @@ func NewHelm(envSettings *cli.EnvSettings, args map[string]interface{}) (*Helm,
 	helm := &Helm{envSettings: envSettings, args: args}
 	config := new(action.Configuration)
 	if err := config.Init(envSettings.RESTClientGetter(), envSettings.Namespace(), os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {
-		LogInfo(fmt.Sprintf(format, v))
+		utils.LogInfo(fmt.Sprintf(format, v))
 	}); err != nil {
 		return nil, err
 	}
@@ -32,7 +34,7 @@ func NewHelm(envSettings *cli.EnvSettings, args map[string]interface{}) (*Helm,
 }
 
 func (h Helm) Install(namespace, chart, release, valuesFile string) error {
-	LogInfo(fmt.Sprintf("Execute helm install. namespace: %s, release: %s chart: %s", namespace, release, chart))
+	utils.LogInfo(fmt.Sprintf("Execute helm install. namespace: %s, release: %s chart: %s", namespace, release, chart))
 	client := action.NewInstall(h.config)
 	client.Namespace = namespace
 	client.ReleaseName = release
@@ -43,7 +45,7 @@ func (h Helm) Install(namespace, chart, release, valuesFile string) error {
 
 	cp, err := client.ChartPathOptions.LocateChart(chart, h.envSettings)
 	if err != nil {
-		LogError(fmt.Sprintf("Error LocateChart: %v", err))
+		utils.LogError(fmt.Sprintf("Error LocateChart: %v", err))
 		return err
 	}
 
@@ -54,81 +56,81 @@ func (h Helm) Install(namespace, chart, release, valuesFile string) error {
 	}
 	vals, err := valueOpts.MergeValues(p)
 	if err != nil {
-		LogError(fmt.Sprintf("Error MergeValues: %v", err))
+		utils.LogError(fmt.Sprintf("Error MergeValues: %v", err))
 		return err
 	}
 
 	if val, ok := h.args["set"]; ok {
 		if err := strvals.ParseInto(fmt.Sprintf("%v", val), vals); err != nil {
-			LogError(fmt.Sprintf("Error parsing --set values: %v", err))
+			utils.LogError(fmt.Sprintf("Error parsing --set values: %v", err))
 			return err
 		}
 	}
 
 	if val, ok := h.args["set-file"]; ok {
 		if err := strvals.ParseInto(fmt.Sprintf("%v", val), vals); err != nil {
-			LogError(fmt.Sprintf("Error parsing --set-file values: %v", err))
+			utils.LogError(fmt.Sprintf("Error parsing --set-file values: %v", err))
 			return err
 		}
 	}
 
 	if val, ok := h.args["set-string"]; ok {
 		if err := strvals.ParseInto(fmt.Sprintf("%v", val), vals); err != nil {
-			LogError(fmt.Sprintf("Error parsing --set-string values: %v", err))
+			utils.LogError(fmt.Sprintf("Error parsing --set-string values: %v", err))
 			return err
 		}
 	}
 
 	c, err := loader.Load(cp)
 	if err != nil {
-		LogError(fmt.Sprintf("Error loading chart path: %v", err))
+		utils.LogError(fmt.Sprintf("Error loading chart path: %v", err))
 		return err
 	}
 
 	// TODO: support other options if required
 	_, err = client.Run(c, vals)
 	if err != nil {
-		LogError(fmt.Sprintf("Error running chart install: %v", err))
+		utils.LogError(fmt.Sprintf("Error running chart install: %v", err))
 		return err
 	}
 
-	LogInfo("Helm install complete")
+	utils.LogInfo("Helm install complete")
 	return nil
 }
 
 func (h Helm) Test(namespace, release string) error {
-	LogInfo(fmt.Sprintf("Execute helm test. namespace: %s, release: %s, args: %+v", namespace, release, h.args))
+	utils.LogInfo(fmt.Sprintf("Execute helm test. namespace: %s, release: %s, args: %+v", namespace, release, h.args))
 	client := action.NewReleaseTesting(h.config)
 	client.Namespace = namespace
 
 	// TODO: support filter and timeout options if required
 	_, err := client.Run(release)
 	if err != nil {
-		LogError(fmt.Sprintf("Execute helm test. error %v", err))
+		utils.LogError(fmt.Sprintf("Execute helm test. error %v", err))
 		return err
 	}
 
-	LogInfo("Helm test complete")
+	utils.LogInfo("Helm test complete")
 	return nil
 }
 
 func (h Helm) Uninstall(namespace, release string) error {
-	LogInfo(fmt.Sprintf("Execute helm uninstall. namespace: %s, release: %s", namespace, release))
+	utils.LogInfo(fmt.Sprintf("Execute helm uninstall. namespace: %s, release: %s", namespace, release))
 	client := action.NewUninstall(h.config)
 	// TODO: support other options if required
 	_, err := client.Run(release)
 
 	if err != nil {
-		LogError(fmt.Sprintf("Error from helm uninstall : %v", err))
+		utils.LogError(fmt.Sprintf("Error from helm uninstall : %v", err))
 		return err
 	}
 
-	LogInfo("Delete release complete")
+	utils.LogInfo("Delete release complete")
 	return nil
 }
 
 func (h Helm) Upgrade(namespace, chart, release string) error {
-	LogInfo(fmt.Sprintf("Execute helm upgrade. namespace: %s, release: %s chart: %s", namespace, release, chart))
+	utils.LogInfo(fmt.Sprintf("Execute helm upgrade. namespace: %s, release: %s chart: %s", namespace, release, chart))
 	client := action.NewUpgrade(h.config)
 	client.Namespace = namespace
 	client.ReuseValues = true
@@ -136,7 +138,7 @@ func (h Helm) Upgrade(namespace, chart, release string) error {
 
 	cp, err := client.ChartPathOptions.LocateChart(chart, h.envSettings)
 	if err != nil {
-		LogError(fmt.Sprintf("Error LocateChart: %v", err))
+		utils.LogError(fmt.Sprintf("Error LocateChart: %v", err))
 		return err
 	}
 
@@ -144,23 +146,23 @@ func (h Helm) Upgrade(namespace, chart, release string) error {
 	valueOpts := &values.Options{}
 	vals, err := valueOpts.MergeValues(p)
 	if err != nil {
-		LogError(fmt.Sprintf("Error MergeValues: %v", err))
+		utils.LogError(fmt.Sprintf("Error MergeValues: %v", err))
 		return err
 	}
 
 	c, err := loader.Load(cp)
 	if err != nil {
-		LogError(fmt.Sprintf("Error loading chart path: %v", err))
+		utils.LogError(fmt.Sprintf("Error loading chart path: %v", err))
 		return err
 	}
 
 	// TODO: support other options if required
 	_, err = client.Run(release, c, vals)
 	if err != nil {
-		LogError(fmt.Sprintf("Error running chart upgrade: %v", err))
+		utils.LogError(fmt.Sprintf("Error running chart upgrade: %v", err))
 		return err
 	}
 
-	LogInfo("Helm upgrade complete")
+	utils.LogInfo("Helm upgrade complete")
 	return nil
 }


Test patch:
diff --git a/cmd/report_test.go b/cmd/report_test.go
index 8a8fde4..2496d7e 100644
--- a/cmd/report_test.go
+++ b/cmd/report_test.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/profiles"
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/report"
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/require"
 	helmchart "helm.sh/helm/v3/pkg/chart"
@@ -79,7 +80,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for subcommand annotations", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -98,7 +99,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for subcommand results", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -116,7 +117,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for subcommand metadata", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -134,7 +135,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for subcommand digests", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -152,7 +153,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for subcommand all", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -173,7 +174,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for annotation prefix", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -200,7 +201,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for community profile", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -226,7 +227,7 @@ func TestReport(t *testing.T) {
 	t.Run("Should pass for invalid profile version", func(t *testing.T) {
 		cmd := NewReportCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
diff --git a/cmd/verify_test.go b/cmd/verify_test.go
index 4448ae3..358d1a1 100644
--- a/cmd/verify_test.go
+++ b/cmd/verify_test.go
@@ -28,6 +28,7 @@ import (
 
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier"
 	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/checks"
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
 )
 
 func TestCertify(t *testing.T) {
@@ -49,7 +50,7 @@ func TestCertify(t *testing.T) {
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
-		cmd.SetArgs([]string{"../pkg/chartverifier/checks/chart-0.1.0-v3.non-existing.tgz"})
+		cmd.SetArgs([]string{"-E", "../pkg/chartverifier/checks/chart-0.1.0-v3.non-existing.tgz"})
 
 		err := cmd.Execute()
 		require.Error(t, err)
@@ -63,7 +64,7 @@ func TestCertify(t *testing.T) {
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
-		cmd.SetArgs([]string{"../pkg/chartverifier/checks/chart-0.1.0-v3.non-existing.tgz", "-o"})
+		cmd.SetArgs([]string{"-E", "../pkg/chartverifier/checks/chart-0.1.0-v3.non-existing.tgz", "-o"})
 		err := cmd.Execute()
 		require.Error(t, err)
 		require.False(t, checks.IsChartNotFound(err))
@@ -78,6 +79,7 @@ func TestCertify(t *testing.T) {
 
 		cmd.SetArgs([]string{
 			"-e", "is-helm-vv3",
+			"-E",
 			"../pkg/chartverifier/checks/chart-0.1.0-v3.non-existing.tgz",
 		})
 		err := cmd.Execute()
@@ -94,6 +96,7 @@ func TestCertify(t *testing.T) {
 
 		cmd.SetArgs([]string{
 			"-e", "is-helm-vv3",
+			"-E",
 			"../pkg/chartverifier/checks/chart-0.1.0-v3.valid.tgz",
 		})
 		err := cmd.Execute()
@@ -103,16 +106,19 @@ func TestCertify(t *testing.T) {
 
 	t.Run("Should succeed when the chart exists and is valid for a single check", func(t *testing.T) {
 		cmd := NewVerifyCmd(viper.New())
-		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
+		outBuf := bytes.NewBufferString("")
+		utils.CmdStdout = outBuf
 
 		cmd.SetArgs([]string{
 			"-e", "is-helm-v3",
 			"-V", "4.9",
+			"-E",
 			"../pkg/chartverifier/checks/chart-0.1.0-v3.valid.tgz",
 		})
+
 		require.NoError(t, cmd.Execute())
 		require.NotEmpty(t, outBuf.String())
 
@@ -126,17 +132,19 @@ func TestCertify(t *testing.T) {
 
 	t.Run("Should display JSON certificate when option --output and argument values are given", func(t *testing.T) {
 		cmd := NewVerifyCmd(viper.New())
-		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
+		outBuf := bytes.NewBufferString("")
+		utils.CmdStdout = outBuf
 
 		cmd.SetArgs([]string{
 			"-e", "is-helm-v3", // only consider a single check, perhaps more checks in the future
 			"-V", "4.9",
 			"-o", "json",
+			"-E",
 			"../pkg/chartverifier/checks/chart-0.1.0-v3.valid.tgz",
 		})
+
 		require.NoError(t, cmd.Execute())
 		require.NotEmpty(t, outBuf.String())
 
@@ -155,7 +163,7 @@ func TestCertify(t *testing.T) {
 	t.Run("Should display YAML certificate when option --output and argument values are given", func(t *testing.T) {
 		cmd := NewVerifyCmd(viper.New())
 		outBuf := bytes.NewBufferString("")
-		cmd.SetOut(outBuf)
+		utils.CmdStdout = outBuf
 		errBuf := bytes.NewBufferString("")
 		cmd.SetErr(errBuf)
 
@@ -164,6 +172,7 @@ func TestCertify(t *testing.T) {
 			"-V", "4.9",
 			"-o", "yaml",
 			"../pkg/chartverifier/checks/chart-0.1.0-v3.valid.tgz",
+			"-E",
 		})
 		require.NoError(t, cmd.Execute())
 		require.NotEmpty(t, outBuf.String())
diff --git a/pkg/chartverifier/checks/charttesting.go b/pkg/chartverifier/checks/charttesting.go
index 06f6b80..04c5a34 100644
--- a/pkg/chartverifier/checks/charttesting.go
+++ b/pkg/chartverifier/checks/charttesting.go
@@ -12,6 +12,7 @@ import (
 	"github.com/helm/chart-testing/v3/pkg/config"
 	"github.com/helm/chart-testing/v3/pkg/util"
 	"github.com/imdario/mergo"
+	"github.com/redhat-certification/chart-verifier/pkg/chartverifier/utils"
 	"github.com/redhat-certification/chart-verifier/pkg/tool"
 	"gopkg.in/yaml.v3"
 	"helm.sh/helm/v3/pkg/cli"
@@ -102,43 +103,43 @@ func buildChartTestingConfiguration(opts *CheckOptions) config.Configuration {
 // chart-verifier.
 func ChartTesting(opts *CheckOptions) (Result, error) {
 
-	tool.LogInfo("Start chart install and test check")
+	utils.LogInfo("Start chart install and test check")
 
 	cfg := buildChartTestingConfiguration(opts)
 	helm, err := tool.NewHelm(opts.HelmEnvSettings, opts.Values)
 	if err != nil {
-		tool.LogError("End chart install and test check with NewHelm error")
+		utils.LogError("End chart install and test check with NewHelm error")
 		return NewResult(false, err.Error()), nil
 	}
 
 	kubeConfig := tool.GetClientConfig(opts.HelmEnvSettings)
 	kubectl, err := tool.NewKubectl(kubeConfig)
 	if err != nil {
-		tool.LogError("End chart install and test check with NewKubectl error")
+		utils.LogError("End chart install and test check with NewKubectl error")
 		return NewResult(false, err.Error()), nil
 	}
 
 	_, path, err := LoadChartFromURI(opts.URI)
 	if err != nil {
-		tool.LogError("End chart install and test check with LoadChartFromURI error")
+		utils.LogError("End chart install and test check with LoadChartFromURI error")
 		return NewResult(false, err.Error()), nil
 	}
 
 	chrt, err := chart.NewChart(path)
 	if err != nil {
-		tool.LogError("End chart install and test check with NewChart error")
+		utils.LogError("End chart install and test check with NewChart error")
 		return NewResult(false, err.Error()), nil
 	}
 
 	configRelease := opts.ViperConfig.GetString(ReleaseConfigString)
 	if len(configRelease) > 0 {
-		tool.LogInfo(fmt.Sprintf("User specifed release: %s", configRelease))
+		utils.LogInfo(fmt.Sprintf("User specifed release: %s", configRelease))
 	}
 
 	if cfg.Upgrade {
 		oldChrt, err := getChartPreviousVersion(chrt)
 		if err != nil {
-			tool.LogError("End chart install and test check with getChartPreviousVersion error")
+			utils.LogError("End chart install and test check with getChartPreviousVersion error")
 			return NewResult(
 					false,
 					fmt.Sprintf("skipping upgrade test of '%s' because no previous chart is available", chrt.Yaml().Name)),
@@ -146,37 +147,37 @@ func ChartTesting(opts *CheckOptions) (Result, error) {
 		}
 		breakingChangeAllowed, err := util.BreakingChangeAllowed(oldChrt.Yaml().Version, chrt.Yaml().Version)
 		if !breakingChangeAllowed {
-			tool.LogError("End chart install and test check with BreakingChangeAllowed not allowed")
+			utils.LogError("End chart install and test check with BreakingChangeAllowed not allowed")
 			return NewResult(
 					false,
 					fmt.Sprintf("Skipping upgrade test of '%s' because breaking changes are not allowed for chart", chrt)),
 				nil
 		} else if err != nil {
-			tool.LogError(fmt.Sprintf("End chart install and test check with BreakingChangeAllowed error: %v", err))
+			utils.LogError(fmt.Sprintf("End chart install and test check with BreakingChangeAllowed error: %v", err))
 			return NewResult(false, err.Error()), nil
 		}
 		result := upgradeAndTestChart(cfg, oldChrt, chrt, helm, kubectl, configRelease)
 
 		if result.Error != nil {
-			tool.LogError(fmt.Sprintf("End chart install and test check with upgradeAndTestChart error: %v", result.Error))
+			utils.LogError(fmt.Sprintf("End chart install and test check with upgradeAndTestChart error: %v", result.Error))
 			return NewResult(false, result.Error.Error()), nil
 		}
 	} else {
 		result := installAndTestChartRelease(cfg, chrt, helm, kubectl, opts.Values, configRelease)
 		if result.Error != nil {
-			tool.LogError(fmt.Sprintf("End chart install and test check with installAndTestChartRelease error: %v", result.Error))
+			utils.LogError(fmt.Sprintf("End chart install and test check with installAndTestChartRelease error: %v", result.Error))
 			return NewResult(false, result.Error.Error()), nil
 		}
 	}
 
 	if versionError := setOCVersion(opts.AnnotationHolder, opts.HelmEnvSettings, getVersion); versionError != nil {
 		if versionError != nil {
-			tool.LogWarning(fmt.Sprintf("End chart install and test check with version error: %v", versionError))
+			utils.LogWarning(fmt.Sprintf("End chart install and test check with version error: %v", versionError))
 		}
 		return NewResult(false, versionError.Error()), nil
 	}
 
-	tool.LogInfo("End chart install and test check")
+	utils.LogInfo("End chart install and test check")
 	return NewResult(true, ChartTestingSuccess), nil
 }
 
@@ -262,7 +263,7 @@ func upgradeAndTestChart(
 			if cfg.SkipMissingValues && !chrt.HasCIValuesFile(valuesFile) {
 				// TODO: do not assume STDOUT here; instead a writer
 				//       should be given to be written to.
-				fmt.Printf("Upgrade testing for values file '%s' skipped because a corresponding values file was not found in %s/ci", valuesFile, chrt.Path())
+				utils.LogWarning(fmt.Sprintf("Upgrade testing for values file '%s' skipped because a corresponding values file was not found in %s/ci", valuesFile, chrt.Path()))
 				continue
 			}
 		}
diff --git a/pkg/chartverifier/utils/logger_test.go b/pkg/chartverifier/utils/logger_test.go
new file mode 100644
index 0000000..77ad7ae
--- /dev/null
+++ b/pkg/chartverifier/utils/logger_test.go
@@ -0,0 +1,247 @@
+package utils
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/require"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+	"testing"
+	"time"
+)
+
+func NewTestCmd(config *viper.Viper) *cobra.Command {
+	return &cobra.Command{
+		Use:   "test",
+		Short: "A cobra command for testing logging",
+		Long:  "A cobra command for testing logging",
+		Run: func(cmd *cobra.Command, args []string) {
+			switch args[0] {
+			case "LogInfoOnly":
+				InitLog(cmd, "", true)
+				LogInfo(args[1])
+			case "StdOutOnly":
+				InitLog(cmd, "", true)
+				WriteStdOut(args[1])
+			case "LogInfoFile":
+				InitLog(cmd, "", false)
+				LogInfo(args[1])
+			case "ErrorAndLog":
+				InitLog(cmd, "", false)
+				LogError(args[1])
+			case "WarningAndLog":
+				InitLog(cmd, "", false)
+				LogWarning(args[1])
+			case "StdOutFile":
+				InitLog(cmd, "report.yaml", false)
+				WriteStdOut(args[1])
+			case "testPrune":
+				InitLog(cmd, "", false)
+				LogError("just for pruning")
+			default:
+				fmt.Println("No test", args[0])
+			}
+			WriteLogs("yaml")
+		},
+	}
+}
+
+func TestLogging(t *testing.T) {
+
+	t.Run("LogInfo, no logfile, should be no output", func(t *testing.T) {
+		tCmd := NewTestCmd(viper.New())
+		outBuf := bytes.NewBufferString("")
+		CmdStdout = outBuf
+		errBuf := bytes.NewBufferString("")
+		tCmd.SetErr(errBuf)
+		tCmd.SetArgs([]string{"LogInfoOnly", "This should not be output"})
+		tCmd.Execute()
+
+		require.Empty(t, outBuf.String())
+		require.Empty(t, errBuf.String())
+
+	})
+
+	t.Run("LogInfo, set logfile, should be a logfile created", func(t *testing.T) {
+		tCmd := NewTestCmd(viper.New())
+		outBuf := bytes.NewBufferString("")
+		CmdStdout = outBuf
+		errBuf := bytes.NewBufferString("")
+		tCmd.SetErr(errBuf)
+
+		output := "This should be output in a log file"
+		tCmd.SetArgs([]string{"LogInfoFile", output})
+		tCmd.Execute()
+
+		require.Empty(t, outBuf.String())
+		require.Empty(t, errBuf.String())
+
+		require.True(t, checkAndOrDeleteFiles("log", output), fmt.Sprintf("Expected string not found in logs : %s", output))
+
+		t.Cleanup(func() {
+			checkAndOrDeleteFiles("delete", "don't care just be sure log is deleted")
+		})
+
+	})
+
+	t.Run("Write to stdOut to a file", func(t *testing.T) {
+		tCmd := NewTestCmd(viper.New())
+		outBuf := bytes.NewBufferString("")
+		CmdStdout = outBuf
+		errBuf := bytes.NewBufferString("")
+		tCmd.SetErr(errBuf)
+		output := "This should be output to file only"
+		tCmd.SetArgs([]string{"StdOutFile", output})
+		tCmd.Execute()
+
+		require.Empty(t, outBuf.String())
+		require.Empty(t, errBuf.String())
+		require.True(t, checkAndOrDeleteFiles("report", output), fmt.Sprintf("Expected string not found in report : %s", output))
+
+		t.Cleanup(func() {
+			checkAndOrDeleteFiles("delete", "don't care just be sure report is deleted")
+		})
+
+	})
+
+	t.Run("Write Error to stdError and file", func(t *testing.T) {
+		tCmd := NewTestCmd(viper.New())
+		outBuf := bytes.NewBufferString("")
+		CmdStdout = outBuf
+		errBuf := bytes.NewBufferString("")
+		tCmd.SetErr(errBuf)
+		output := "This error should be output to stderr and log file"
+		tCmd.SetArgs([]string{"ErrorAndLog", output})
+		tCmd.Execute()
+
+		require.Empty(t, outBuf.String())
+		require.True(t, strings.Contains(errBuf.String(), output))
+		require.True(t, checkAndOrDeleteFiles("log", output), fmt.Sprintf("Expected string not found in logs : %s", output))
+
+		t.Cleanup(func() {
+			checkAndOrDeleteFiles("delete", "don't care just be sure log is deleted")
+		})
+
+	})
+
+	t.Run("Write Warning to stdError and file", func(t *testing.T) {
+		tCmd := NewTestCmd(viper.New())
+		outBuf := bytes.NewBufferString("")
+		CmdStdout = outBuf
+		errBuf := bytes.NewBufferString("")
+		tCmd.SetErr(errBuf)
+		output := "This warning should be output to stderr and log file."
+		tCmd.SetArgs([]string{"WarningAndLog", output})
+		tCmd.Execute()
+
+		require.Empty(t, outBuf.String())
+		require.True(t, strings.Contains(errBuf.String(), output))
+		require.True(t, checkAndOrDeleteFiles("log", output), fmt.Sprintf("Expected string not found in logs : %s", output))
+
+		t.Cleanup(func() {
+			checkAndOrDeleteFiles("delete", "don't care just be sure log is deleted")
+		})
+
+	})
+
+	t.Run("Test log file pruning", func(t *testing.T) {
+		tCmd := NewTestCmd(viper.New())
+		outBuf := bytes.NewBufferString("")
+		CmdStdout = outBuf
+		errBuf := bytes.NewBufferString("")
+		tCmd.SetErr(errBuf)
+
+		tCmd.SetArgs([]string{"testPrune"})
+		for i := 1; i <= 15; i++ {
+			tCmd.Execute()
+			numlogFiles := howManyLogFiles()
+			if i < 10 {
+				require.True(t, numlogFiles == i, fmt.Sprintf("expected %d logfile but found %d", i, numlogFiles))
+			} else {
+				require.True(t, numlogFiles == 10, fmt.Sprintf("expected 10 logfile but found %d", numlogFiles))
+			}
+			time.Sleep(2 * time.Second)
+		}
+
+		t.Cleanup(func() {
+			checkAndOrDeleteFiles("delete", "don't care just be sure log file are deleted")
+		})
+
+	})
+}
+
+func checkAndOrDeleteFiles(fileType string, expectedContent string) bool {
+	result := false
+	if len(expectedContent) > 0 {
+		currentDir, err := os.Getwd()
+		if err != nil {
+			fmt.Println(fmt.Sprintf("er"+
+				"ror getting current working directory : %s", err))
+			return false
+		}
+		logFilesPath := path.Join(currentDir, outputDirectory)
+
+		files, err := ioutil.ReadDir(logFilesPath)
+		if err != nil {
+			if fileType != "delete" {
+				fmt.Println(fmt.Sprintf("error reading log directory : %s : %s", logFilesPath, err))
+				return false
+			}
+			return true
+		}
+
+		for _, file := range files {
+			foundFile := false
+			if fileType == "log" && strings.HasPrefix(file.Name(), "verifier") && strings.HasSuffix(file.Name(), ".log") {
+				foundFile = true
+			} else if fileType == "report" && file.Name() == "report.yaml" {
+				foundFile = true
+			} else if fileType == "delete" {
+				foundFile = true
+			}
+			if foundFile {
+				filePath := path.Join(logFilesPath, file.Name())
+				if fileType != "delete" {
+					logfileContent, err := ioutil.ReadFile(filePath)
+					if err != nil {
+						fmt.Println(fmt.Sprintf("error reading file %s", err))
+						return false
+					}
+					if strings.Contains(string(logfileContent), expectedContent) {
+						result = true
+					}
+				}
+				os.Remove(filePath)
+			}
+		}
+		os.Remove(logFilesPath)
+	}
+	return result
+}
+
+func howManyLogFiles() int {
+	currentDir, err := os.Getwd()
+	if err != nil {
+		fmt.Println(fmt.Sprintf("error getting current working directory : %s", err))
+		return 0
+	}
+	logFilesPath := path.Join(currentDir, outputDirectory)
+
+	files, err := ioutil.ReadDir(logFilesPath)
+	if err != nil {
+		fmt.Println(fmt.Sprintf("error reading log directory : %s : %s", logFilesPath, err))
+		return 0
+	}
+	numLogFiles := 0
+
+	for _, file := range files {
+		if strings.HasPrefix(file.Name(), "verifier") && strings.HasSuffix(file.Name(), ".log") {
+			numLogFiles++
+		}
+	}
+	return numLogFiles
+}
diff --git a/pkg/tool/test_log.go b/pkg/tool/test_log.go
deleted file mode 100644
index 78ef012..0000000
--- a/pkg/tool/test_log.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package tool
-
-import (
-	"encoding/json"
-	"fmt"
-	"gopkg.in/yaml.v3"
-)
-
-type TestLog struct {
-	Name    string      `json:"name" yaml:"name"`
-	Entries []*LogEntry `json:"log" yaml:"log"`
-}
-
-type LogEntry struct {
-	Entry string `json:"Entry" yaml:"Entry"`
-}
-
-var testlog TestLog
-
-func init() {
-	testlog = TestLog{Name: "Chart Verifier Log"}
-}
-
-func LogWarning(message string) {
-	warning_log_entry := LogEntry{Entry: fmt.Sprintf("[WARNING] %s", message)}
-	testlog.Entries = append(testlog.Entries, &warning_log_entry)
-}
-
-func LogInfo(message string) {
-	info_log_entry := LogEntry{Entry: fmt.Sprintf("[INFO] %s", message)}
-	testlog.Entries = append(testlog.Entries, &info_log_entry)
-}
-
-func LogError(message string) {
-	error_log_entry := LogEntry{Entry: fmt.Sprintf("[ERROR} %s", message)}
-	testlog.Entries = append(testlog.Entries, &error_log_entry)
-}
-func GetLogsOutput(log_format string) (string, error) {
-
-	if len(testlog.Entries) > 0 {
-		if log_format == "json" {
-			b, err := json.Marshal(&testlog)
-			if err != nil {
-				return "", err
-			}
-			return string(b), nil
-		} else {
-			b, err := yaml.Marshal(&testlog)
-			if err != nil {
-				return "", err
-			}
-			return string(b), nil
-		}
-	}
-	return "", nil
-
-}
diff --git a/tests/tests/functional/chart_test.py b/tests/tests/functional/chart_test.py
index 1f987da..c52c750 100644
--- a/tests/tests/functional/chart_test.py
+++ b/tests/tests/functional/chart_test.py
@@ -98,7 +98,7 @@ def run_docker_image(verifier_image_name,verifier_image_tag,profile_type, chart_
             chart_directory = os.path.dirname(os.path.abspath(chart_location))
             docker_volumes[chart_directory] = {'bind': '/charts/', 'mode': 'rw'}
 
-        output = client.containers.run(verifier_image,docker_command,stdin_open=True,tty=True,stderr=True,volumes=docker_volumes,environment=docker_environment)
+        output = client.containers.run(verifier_image,docker_command,stdin_open=True,tty=True,stdout=True,volumes=docker_volumes,environment=docker_environment)
 
     except docker.errors.ContainerError as exc:
         return f"FAIL: docker.errors.ContainerError: {exc.args}"
@@ -121,7 +121,7 @@ def run_tarball_image(tarball_name,profile_type, chart_location):
 
     out = subprocess.run(["./test_verifier/chart-verifier","verify","--set",f"profile.vendorType={profile_type}",chart_location],capture_output=True)
 
-    return out.stderr.decode("utf-8")
+    return out.stdout.decode("utf-8")
 
 def run_podman_image(verifier_image_name,verifier_image_tag,profile_type, chart_location):
 
@@ -139,7 +139,7 @@ def run_podman_image(verifier_image_name,verifier_image_tag,profile_type, chart_
         out = subprocess.run(["podman", "run", "-v", f"{chart_directory}:/charts:z", "-v", f"{kubeconfig}:/kubeconfig", "-e", "KUBECONFIG=/kubeconfig", "--rm",
                               f"{verifier_image_name}:{verifier_image_tag}", "verify", "--set", f"profile.vendortype={profile_type}", f"/charts/{chart_name}"], capture_output=True)
 
-    return out.stderr.decode("utf-8")
+    return out.stdout.decode("utf-8")
 
 @then("I should see the report-info from the generated report matching the expected report-info")
 def check_report(run_verifier, profile_type, report_info_location):