Repository: peak/s5cmd (go)

Issue:
log level and stats
Is it possible to prevent `s5cmd cp` from listing all copied files while still keeping `--stat` output? It seems that `--log error` is the only way to prevent s5cmd from listing files. Yet this also prevents `--stat` from working, as it is attached to info logging level. I have to copy thousands of files, and listing everything is too busy, so getting some kind of stats at the end would be nice. I don't think it would be unreasonable for `--stat` to force output independent of log level, since this is what user is asking for.

Pull request description:
flag/stat: fix omitted stat in error log level
Create unit & e2e tests
Fixes #359

Co-Authored-By: Muhammed Can Küçükaslan <32893303+muhammedcankucukaslan@users.noreply.github.com>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3280d55..705ffcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@
 - Updated region detection call to use current session's address resolving method ([#314](https://github.com/peak/s5cmd/issues/314))
 - Fixed a bug where lines with large tokens fail in `run` command. `sync` was failing when it finds multiple files to remove. ([#435](https://github.com/peak/s5cmd/issues/435), [#436](https://github.com/peak/s5cmd/issues/436))
 - Print usage error if given log level(`--log`) is not valid. ([#430](https://github.com/peak/s5cmd/pull/430))
+- Fixed a bug where (`--stat`) is ignored when log level is error. ([#359](https://github.com/peak/s5cmd/issues/359))
 
 ## v1.4.0 - 21 Sep 2021
 
diff --git a/command/app.go b/command/app.go
index 49927ae..67f82a9 100644
--- a/command/app.go
+++ b/command/app.go
@@ -143,7 +143,7 @@ var app = &cli.App{
 	},
 	After: func(c *cli.Context) error {
 		if c.Bool("stat") {
-			log.Info(stat.Statistics())
+			log.Stat(stat.Statistics())
 		}
 
 		parallel.Close()
diff --git a/log/log.go b/log/log.go
index c890e68..25139b9 100644
--- a/log/log.go
+++ b/log/log.go
@@ -37,6 +37,12 @@ func Info(msg Message) {
 	global.printf(levelInfo, msg, os.Stdout)
 }
 
+// Stat prints stat message regardless of the log level with info print formatting.
+// It uses printfHelper instead of printf to ignore the log level condition.
+func Stat(msg Message) {
+	global.printfHelper(levelInfo, msg, os.Stdout)
+}
+
 // Error prints message in error mode.
 func Error(msg Message) {
 	global.printf(levelError, msg, os.Stderr)
@@ -72,7 +78,10 @@ func (l *Logger) printf(level logLevel, message Message, std *os.File) {
 	if level < l.level {
 		return
 	}
+	l.printfHelper(level, message, std)
+}
 
+func (l *Logger) printfHelper(level logLevel, message Message, std *os.File) {
 	if l.json {
 		outputCh <- output{
 			message: message.JSON(),


Test patch:
diff --git a/e2e/app_test.go b/e2e/app_test.go
index 24cf08d..2d43cf4 100644
--- a/e2e/app_test.go
+++ b/e2e/app_test.go
@@ -69,21 +69,64 @@ func TestAppRetryCount(t *testing.T) {
 	}
 }
 
+// Checks if the stats are written at the end of each log level output.
 func TestAppDashStat(t *testing.T) {
 	t.Parallel()
 
-	_, s5cmd, cleanup := setup(t)
-	defer cleanup()
+	const (
+		bucket      = "bucket"
+		fileContent = "this is a file content"
+		dst         = "."
+		src         = "file1.txt"
+	)
 
-	cmd := s5cmd("--stat", "ls")
-	result := icmd.RunCmd(cmd)
+	testcases := []struct {
+		name  string
+		level string
+	}{
+		{
+			name:  "--stat --log trace cp s3://bucket/object .",
+			level: "trace",
+		},
+		{
+			name:  "--stat --log debug cp s3://bucket/object .",
+			level: "debug",
+		},
+		{
+			name:  "--stat --log info cp s3://bucket/object .",
+			level: "info",
+		},
+		{
+			name:  "--stat --log error cp s3://bucket/object .",
+			level: "error",
+		},
+	}
 
-	result.Assert(t, icmd.Success)
+	for _, tc := range testcases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			s3client, s5cmd, cleanup := setup(t)
+			defer cleanup()
+
+			createBucket(t, s3client, bucket)
 
-	out := result.Stdout()
+			putFile(t, s3client, bucket, src, fileContent)
 
-	tsv := fmt.Sprintf("%s\t%s\t%s\t%s\t", "Operation", "Total", "Error", "Success")
-	assert.Assert(t, strings.Contains(out, tsv))
+			srcPath := fmt.Sprintf("s3://%v/%v", bucket, src)
+			cmd := s5cmd("--stat", "--log", tc.level, "cp", srcPath, dst)
+			result := icmd.RunCmd(cmd)
+
+			result.Assert(t, icmd.Success)
+
+			out := result.Stdout()
+			tsv := fmt.Sprintf("%s\t%s\t%s\t%s\t", "Operation", "Total", "Error", "Success")
+
+			assert.Assert(t, strings.Contains(out, tsv))
+
+		})
+	}
 }
 
 func TestAppUnknownCommand(t *testing.T) {