diff --git a/commands/service_compile.go b/commands/service_compile.go
index a71195382cf..24cbfd52b2c 100644
--- a/commands/service_compile.go
+++ b/commands/service_compile.go
@@ -28,6 +28,7 @@ import (
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/builder"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesmanager"
 	"github.com/arduino/arduino-cli/internal/arduino/sketch"
 	"github.com/arduino/arduino-cli/internal/arduino/utils"
@@ -244,6 +245,13 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu
 			Message: &rpc.CompileResponse_Progress{Progress: p},
 		})
 	}
+	var verbosity logger.Verbosity = logger.VerbosityNormal
+	if req.GetQuiet() {
+		verbosity = logger.VerbosityQuiet
+	}
+	if req.GetVerbose() {
+		verbosity = logger.VerbosityVerbose
+	}
 	sketchBuilder, err := builder.NewBuilder(
 		ctx,
 		sk,
@@ -265,7 +273,7 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu
 		req.GetSkipLibrariesDiscovery(),
 		libsManager,
 		paths.NewPathList(req.GetLibrary()...),
-		outStream, errStream, req.GetVerbose(), req.GetWarnings(),
+		outStream, errStream, verbosity, req.GetWarnings(),
 		progressCB,
 		pme.GetEnvVarsForSpawnedProcess(),
 	)
diff --git a/internal/arduino/builder/archive_compiled_files.go b/internal/arduino/builder/archive_compiled_files.go
index ed87225e2ff..32208a59092 100644
--- a/internal/arduino/builder/archive_compiled_files.go
+++ b/internal/arduino/builder/archive_compiled_files.go
@@ -16,6 +16,7 @@
 package builder
 
 import (
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	"github.com/arduino/go-paths-helper"
 )
@@ -23,7 +24,7 @@ import (
 // ArchiveCompiledFiles fixdoc
 func (b *Builder) archiveCompiledFiles(archiveFilePath *paths.Path, objectFilesToArchive paths.PathList) (*paths.Path, error) {
 	if b.onlyUpdateCompilationDatabase {
-		if b.logger.Verbose() {
+		if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			b.logger.Info(i18n.Tr("Skipping archive creation of: %[1]s", archiveFilePath))
 		}
 		return archiveFilePath, nil
@@ -46,7 +47,7 @@ func (b *Builder) archiveCompiledFiles(archiveFilePath *paths.Path, objectFilesT
 				return nil, err
 			}
 		} else {
-			if b.logger.Verbose() {
+			if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 				b.logger.Info(i18n.Tr("Using previously compiled file: %[1]s", archiveFilePath))
 			}
 			return archiveFilePath, nil
diff --git a/internal/arduino/builder/builder.go b/internal/arduino/builder/builder.go
index 58d607c827a..0c711273a5d 100644
--- a/internal/arduino/builder/builder.go
+++ b/internal/arduino/builder/builder.go
@@ -27,9 +27,9 @@ import (
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/compilation"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/detector"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/diagnostics"
-	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/logger"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/progress"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/utils"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesmanager"
@@ -136,7 +136,7 @@ func NewBuilder(
 	useCachedLibrariesResolution bool,
 	librariesManager *librariesmanager.LibrariesManager,
 	libraryDirs paths.PathList,
-	stdout, stderr io.Writer, verbose bool, warningsLevel string,
+	stdout, stderr io.Writer, verbosity logger.Verbosity, warningsLevel string,
 	progresCB rpc.TaskProgressCB,
 	toolEnv []string,
 ) (*Builder, error) {
@@ -189,7 +189,7 @@ func NewBuilder(
 		return nil, ErrSketchCannotBeLocatedInBuildPath
 	}
 
-	logger := logger.New(stdout, stderr, verbose, warningsLevel)
+	log := logger.New(stdout, stderr, verbosity, warningsLevel)
 	libsManager, libsResolver, verboseOut, err := detector.LibrariesLoader(
 		useCachedLibrariesResolution, librariesManager,
 		builtInLibrariesDirs, libraryDirs, otherLibrariesDirs,
@@ -198,8 +198,8 @@ func NewBuilder(
 	if err != nil {
 		return nil, err
 	}
-	if logger.Verbose() {
-		logger.Warn(string(verboseOut))
+	if log.VerbosityLevel() == logger.VerbosityVerbose {
+		log.Warn(string(verboseOut))
 	}
 
 	diagnosticStore := diagnostics.NewStore()
@@ -215,7 +215,7 @@ func NewBuilder(
 		customBuildProperties:         customBuildPropertiesArgs,
 		coreBuildCachePath:            coreBuildCachePath,
 		extraCoreBuildCachePaths:      extraCoreBuildCachePaths,
-		logger:                        logger,
+		logger:                        log,
 		clean:                         clean,
 		sourceOverrides:               sourceOverrides,
 		onlyUpdateCompilationDatabase: onlyUpdateCompilationDatabase,
@@ -242,7 +242,7 @@ func NewBuilder(
 			libsManager, libsResolver,
 			useCachedLibrariesResolution,
 			onlyUpdateCompilationDatabase,
-			logger,
+			log,
 			diagnosticStore,
 		),
 	}
@@ -341,7 +341,7 @@ func (b *Builder) preprocess() error {
 }
 
 func (b *Builder) logIfVerbose(warn bool, msg string) {
-	if !b.logger.Verbose() {
+	if b.logger.VerbosityLevel() != logger.VerbosityVerbose {
 		return
 	}
 	if warn {
@@ -526,7 +526,7 @@ func (b *Builder) prepareCommandForRecipe(buildProperties *properties.Map, recip
 }
 
 func (b *Builder) execCommand(command *paths.Process) error {
-	if b.logger.Verbose() {
+	if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 		b.logger.Info(utils.PrintableCommand(command.GetArgs()))
 		command.RedirectStdoutTo(b.logger.Stdout())
 	}
diff --git a/internal/arduino/builder/compilation.go b/internal/arduino/builder/compilation.go
index d3a1459da25..c739a2e37de 100644
--- a/internal/arduino/builder/compilation.go
+++ b/internal/arduino/builder/compilation.go
@@ -23,6 +23,7 @@ import (
 	"sync"
 
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/utils"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/arduino/globals"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	"github.com/arduino/go-paths-helper"
@@ -152,7 +153,7 @@ func (b *Builder) compileFileWithRecipe(
 		command.RedirectStdoutTo(commandStdout)
 		command.RedirectStderrTo(commandStderr)
 
-		if b.logger.Verbose() {
+		if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			b.logger.Info(utils.PrintableCommand(command.GetArgs()))
 		}
 		// Since this compile could be multithreaded, we first capture the command output
@@ -161,7 +162,7 @@ func (b *Builder) compileFileWithRecipe(
 		}
 		err := command.Wait()
 		// and transfer all at once at the end...
-		if b.logger.Verbose() {
+		if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			b.logger.WriteStdout(commandStdout.Bytes())
 		}
 		b.logger.WriteStderr(commandStderr.Bytes())
@@ -176,7 +177,7 @@ func (b *Builder) compileFileWithRecipe(
 		if err != nil {
 			return nil, err
 		}
-	} else if b.logger.Verbose() {
+	} else if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 		if objIsUpToDate {
 			b.logger.Info(i18n.Tr("Using previously compiled file: %[1]s", objectFile))
 		} else {
diff --git a/internal/arduino/builder/core.go b/internal/arduino/builder/core.go
index f541eaeda41..c6d87ec7d66 100644
--- a/internal/arduino/builder/core.go
+++ b/internal/arduino/builder/core.go
@@ -24,6 +24,7 @@ import (
 
 	"github.com/arduino/arduino-cli/internal/arduino/builder/cpp"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/utils"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/buildcache"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	"github.com/arduino/go-paths-helper"
@@ -116,7 +117,7 @@ func (b *Builder) compileCore() (*paths.Path, paths.PathList, error) {
 				return nil, nil, errors.New(i18n.Tr("creating core cache folder: %s", err))
 			}
 			// use archived core
-			if b.logger.Verbose() {
+			if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 				b.logger.Info(i18n.Tr("Using precompiled core: %[1]s", targetArchivedCore))
 			}
 			return targetArchivedCore, variantObjectFiles, nil
@@ -128,7 +129,7 @@ func (b *Builder) compileCore() (*paths.Path, paths.PathList, error) {
 			extraTargetArchivedCore := extraCoreBuildCachePath.Join(archivedCoreName, "core.a")
 			if canUseArchivedCore(extraTargetArchivedCore) {
 				// use archived core
-				if b.logger.Verbose() {
+				if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 					b.logger.Info(i18n.Tr("Using precompiled core: %[1]s", extraTargetArchivedCore))
 				}
 				return extraTargetArchivedCore, variantObjectFiles, nil
@@ -158,7 +159,7 @@ func (b *Builder) compileCore() (*paths.Path, paths.PathList, error) {
 	// archive core.a
 	if targetArchivedCore != nil && !b.onlyUpdateCompilationDatabase {
 		err := archiveFile.CopyTo(targetArchivedCore)
-		if b.logger.Verbose() {
+		if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			if err == nil {
 				b.logger.Info(i18n.Tr("Archiving built core (caching) in: %[1]s", targetArchivedCore))
 			} else if os.IsNotExist(err) {
diff --git a/internal/arduino/builder/internal/detector/detector.go b/internal/arduino/builder/internal/detector/detector.go
index eb3d887d3fe..a983370b075 100644
--- a/internal/arduino/builder/internal/detector/detector.go
+++ b/internal/arduino/builder/internal/detector/detector.go
@@ -28,9 +28,9 @@ import (
 	"time"
 
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/diagnostics"
-	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/logger"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/preprocessor"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/utils"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	"github.com/arduino/arduino-cli/internal/arduino/globals"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries"
@@ -87,7 +87,7 @@ func (l *SketchLibrariesDetector) resolveLibrary(header, platformArch string) *l
 	importedLibraries := l.importedLibraries
 	candidates := l.librariesResolver.AlternativesFor(header)
 
-	if l.logger.Verbose() {
+	if l.logger.VerbosityLevel() == logger.VerbosityVerbose {
 		l.logger.Info(i18n.Tr("Alternatives for %[1]s: %[2]s", header, candidates))
 		l.logger.Info(fmt.Sprintf("ResolveLibrary(%s)", header))
 		l.logger.Info(fmt.Sprintf("  -> %s: %s", i18n.Tr("candidates"), candidates))
@@ -144,7 +144,7 @@ func (l *SketchLibrariesDetector) PrintUsedAndNotUsedLibraries(sketchError bool)
 	// - as warning, when the sketch didn't compile
 	// - as info, when verbose is on
 	// - otherwise, output nothing
-	if !sketchError && !l.logger.Verbose() {
+	if !sketchError && l.logger.VerbosityLevel() != logger.VerbosityVerbose {
 		return
 	}
 
@@ -239,7 +239,7 @@ func (l *SketchLibrariesDetector) findIncludes(
 		if err := json.Unmarshal(d, &l.includeFolders); err != nil {
 			return err
 		}
-		if l.logger.Verbose() {
+		if l.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			l.logger.Info("Using cached library discovery: " + librariesResolutionCache.String())
 		}
 		return nil
@@ -347,12 +347,12 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
 		var missingIncludeH string
 		if unchanged && cache.valid {
 			missingIncludeH = cache.Next().Include
-			if first && l.logger.Verbose() {
+			if first && l.logger.VerbosityLevel() == logger.VerbosityVerbose {
 				l.logger.Info(i18n.Tr("Using cached library dependencies for file: %[1]s", sourcePath))
 			}
 		} else {
 			preprocFirstResult, preprocErr = preprocessor.GCC(ctx, sourcePath, targetFilePath, includeFolders, buildProperties)
-			if l.logger.Verbose() {
+			if l.logger.VerbosityLevel() == logger.VerbosityVerbose {
 				l.logger.WriteStdout(preprocFirstResult.Stdout())
 			}
 			// Unwrap error and see if it is an ExitError.
@@ -365,7 +365,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
 				return preprocErr
 			} else {
 				missingIncludeH = IncludesFinderWithRegExp(string(preprocFirstResult.Stderr()))
-				if missingIncludeH == "" && l.logger.Verbose() {
+				if missingIncludeH == "" && l.logger.VerbosityLevel() == logger.VerbosityVerbose {
 					l.logger.Info(i18n.Tr("Error while detecting libraries included by %[1]s", sourcePath))
 				}
 			}
@@ -383,7 +383,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
 			if preprocErr == nil || preprocFirstResult.Stderr() == nil {
 				// Filename came from cache, so run preprocessor to obtain error to show
 				result, err := preprocessor.GCC(ctx, sourcePath, targetFilePath, includeFolders, buildProperties)
-				if l.logger.Verbose() {
+				if l.logger.VerbosityLevel() == logger.VerbosityVerbose {
 					l.logger.WriteStdout(result.Stdout())
 				}
 				if err == nil {
@@ -410,7 +410,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
 
 		if library.Precompiled && library.PrecompiledWithSources {
 			// Fully precompiled libraries should have no dependencies to avoid ABI breakage
-			if l.logger.Verbose() {
+			if l.logger.VerbosityLevel() == logger.VerbosityVerbose {
 				l.logger.Info(i18n.Tr("Skipping dependencies detection for precompiled library %[1]s", library.Name))
 			}
 		} else {
diff --git a/internal/arduino/builder/internal/preprocessor/ctags.go b/internal/arduino/builder/internal/preprocessor/ctags.go
index fe36cfc89e5..c77d22e783b 100644
--- a/internal/arduino/builder/internal/preprocessor/ctags.go
+++ b/internal/arduino/builder/internal/preprocessor/ctags.go
@@ -83,8 +83,9 @@ func PreprocessSketchWithCtags(
 	}
 
 	// Run CTags on gcc-preprocessed source
-	ctagsOutput, ctagsStdErr, err := RunCTags(ctx, ctagsTarget, buildProperties)
+	ctagsOutput, ctagsStdErr, ctagsCmdLine, err := RunCTags(ctx, ctagsTarget, buildProperties)
 	if verbose {
+		stdout.Write([]byte(ctagsCmdLine + "\n"))
 		stderr.Write(ctagsStdErr)
 	}
 	if err != nil {
@@ -178,7 +179,7 @@ func isFirstFunctionOutsideOfSource(firstFunctionLine int, sourceRows []string)
 }
 
 // RunCTags performs a run of ctags on the given source file. Returns the ctags output and the stderr contents.
-func RunCTags(ctx context.Context, sourceFile *paths.Path, buildProperties *properties.Map) ([]byte, []byte, error) {
+func RunCTags(ctx context.Context, sourceFile *paths.Path, buildProperties *properties.Map) ([]byte, []byte, string, error) {
 	ctagsBuildProperties := properties.NewMap()
 	ctagsBuildProperties.Set("tools.ctags.path", "{runtime.tools.ctags.path}")
 	ctagsBuildProperties.Set("tools.ctags.cmd.path", "{path}/ctags")
@@ -189,24 +190,22 @@ func RunCTags(ctx context.Context, sourceFile *paths.Path, buildProperties *prop
 
 	pattern := ctagsBuildProperties.Get("pattern")
 	if pattern == "" {
-		return nil, nil, errors.New(i18n.Tr("%s pattern is missing", "ctags"))
+		return nil, nil, "", errors.New(i18n.Tr("%s pattern is missing", "ctags"))
 	}
 
 	commandLine := ctagsBuildProperties.ExpandPropsInString(pattern)
 	parts, err := properties.SplitQuotedString(commandLine, `"'`, false)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, "", err
 	}
 	proc, err := paths.NewProcess(nil, parts...)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, "", err
 	}
 	stdout, stderr, err := proc.RunAndCaptureOutput(ctx)
 
-	// Append ctags arguments to stderr
 	args := fmt.Sprintln(strings.Join(parts, " "))
-	stderr = append([]byte(args), stderr...)
-	return stdout, stderr, err
+	return stdout, stderr, args, err
 }
 
 func filterSketchSource(sketch *sketch.Sketch, source io.Reader, removeLineMarkers bool) string {
diff --git a/internal/arduino/builder/libraries.go b/internal/arduino/builder/libraries.go
index 6bb28f96ed2..d2558e4954f 100644
--- a/internal/arduino/builder/libraries.go
+++ b/internal/arduino/builder/libraries.go
@@ -21,6 +21,7 @@ import (
 	"time"
 
 	"github.com/arduino/arduino-cli/internal/arduino/builder/cpp"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	"github.com/arduino/go-paths-helper"
@@ -129,7 +130,7 @@ func (b *Builder) compileLibraries(libraries libraries.List, includes []string)
 }
 
 func (b *Builder) compileLibrary(library *libraries.Library, includes []string) (paths.PathList, error) {
-	if b.logger.Verbose() {
+	if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 		b.logger.Info(i18n.Tr(`Compiling library "%[1]s"`, library.Name))
 	}
 	libraryBuildPath := b.librariesBuildPath.Join(library.DirName)
@@ -292,7 +293,7 @@ func (b *Builder) warnAboutArchIncompatibleLibraries(importedLibraries libraries
 // TODO here we can completly remove this part as it's duplicated in what we can
 // read in the gRPC response
 func (b *Builder) printUsedLibraries(importedLibraries libraries.List) {
-	if !b.logger.Verbose() || len(importedLibraries) == 0 {
+	if b.logger.VerbosityLevel() != logger.VerbosityVerbose || len(importedLibraries) == 0 {
 		return
 	}
 
diff --git a/internal/arduino/builder/linker.go b/internal/arduino/builder/linker.go
index 20032608db5..77f450a9182 100644
--- a/internal/arduino/builder/linker.go
+++ b/internal/arduino/builder/linker.go
@@ -18,6 +18,7 @@ package builder
 import (
 	"strings"
 
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	"github.com/arduino/go-paths-helper"
 	"go.bug.st/f"
@@ -26,7 +27,7 @@ import (
 // link fixdoc
 func (b *Builder) link() error {
 	if b.onlyUpdateCompilationDatabase {
-		if b.logger.Verbose() {
+		if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			b.logger.Info(i18n.Tr("Skip linking of final executable."))
 		}
 		return nil
diff --git a/internal/arduino/builder/internal/logger/logger.go b/internal/arduino/builder/logger/logger.go
similarity index 76%
rename from internal/arduino/builder/internal/logger/logger.go
rename to internal/arduino/builder/logger/logger.go
index 992c15a25b2..347c98ce9f8 100644
--- a/internal/arduino/builder/internal/logger/logger.go
+++ b/internal/arduino/builder/logger/logger.go
@@ -22,18 +22,27 @@ import (
 	"sync"
 )
 
+// Verbosity is the verbosity level of the Logger
+type Verbosity int
+
+const (
+	VerbosityQuiet   Verbosity = -1
+	VerbosityNormal  Verbosity = 0
+	VerbosityVerbose Verbosity = 1
+)
+
 // BuilderLogger fixdoc
 type BuilderLogger struct {
 	stdLock sync.Mutex
 	stdout  io.Writer
 	stderr  io.Writer
 
-	verbose       bool
+	verbosity     Verbosity
 	warningsLevel string
 }
 
-// New fixdoc
-func New(stdout, stderr io.Writer, verbose bool, warningsLevel string) *BuilderLogger {
+// New creates a new Logger to the given output streams with the specified verbosity and warnings level
+func New(stdout, stderr io.Writer, verbosity Verbosity, warningsLevel string) *BuilderLogger {
 	if stdout == nil {
 		stdout = os.Stdout
 	}
@@ -46,13 +55,16 @@ func New(stdout, stderr io.Writer, verbose bool, warningsLevel string) *BuilderL
 	return &BuilderLogger{
 		stdout:        stdout,
 		stderr:        stderr,
-		verbose:       verbose,
+		verbosity:     verbosity,
 		warningsLevel: warningsLevel,
 	}
 }
 
 // Info fixdoc
 func (l *BuilderLogger) Info(msg string) {
+	if msg == "" {
+		return
+	}
 	l.stdLock.Lock()
 	defer l.stdLock.Unlock()
 	fmt.Fprintln(l.stdout, msg)
@@ -60,6 +72,9 @@ func (l *BuilderLogger) Info(msg string) {
 
 // Warn fixdoc
 func (l *BuilderLogger) Warn(msg string) {
+	if msg == "" {
+		return
+	}
 	l.stdLock.Lock()
 	defer l.stdLock.Unlock()
 	fmt.Fprintln(l.stderr, msg)
@@ -79,9 +94,9 @@ func (l *BuilderLogger) WriteStderr(data []byte) (int, error) {
 	return l.stderr.Write(data)
 }
 
-// Verbose fixdoc
-func (l *BuilderLogger) Verbose() bool {
-	return l.verbose
+// VerbosityLevel returns the verbosity level of the logger
+func (l *BuilderLogger) VerbosityLevel() Verbosity {
+	return l.verbosity
 }
 
 // WarningsLevel fixdoc
diff --git a/internal/arduino/builder/preprocess_sketch.go b/internal/arduino/builder/preprocess_sketch.go
index b7fe178db02..290b2963664 100644
--- a/internal/arduino/builder/preprocess_sketch.go
+++ b/internal/arduino/builder/preprocess_sketch.go
@@ -17,6 +17,7 @@ package builder
 
 import (
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/preprocessor"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/go-paths-helper"
 )
 
@@ -26,10 +27,11 @@ func (b *Builder) preprocessSketch(includes paths.PathList) error {
 	result, err := preprocessor.PreprocessSketchWithCtags(
 		b.ctx,
 		b.sketch, b.buildPath, includes, b.lineOffset,
-		b.buildProperties, b.onlyUpdateCompilationDatabase, b.logger.Verbose(),
+		b.buildProperties, b.onlyUpdateCompilationDatabase,
+		b.logger.VerbosityLevel() == logger.VerbosityVerbose,
 	)
 	if result != nil {
-		if b.logger.Verbose() {
+		if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			b.logger.WriteStdout(result.Stdout())
 		}
 		b.logger.WriteStderr(result.Stderr())
diff --git a/internal/arduino/builder/recipe.go b/internal/arduino/builder/recipe.go
index c8fa8962850..b4c456e360f 100644
--- a/internal/arduino/builder/recipe.go
+++ b/internal/arduino/builder/recipe.go
@@ -19,6 +19,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/sirupsen/logrus"
@@ -43,7 +44,7 @@ func (b *Builder) RunRecipe(prefix, suffix string, skipIfOnlyUpdatingCompilation
 		}
 
 		if b.onlyUpdateCompilationDatabase && skipIfOnlyUpdatingCompilationDatabase {
-			if b.logger.Verbose() {
+			if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 				b.logger.Info(i18n.Tr("Skipping: %[1]s", strings.Join(command.GetArgs(), " ")))
 			}
 			return nil
diff --git a/internal/arduino/builder/sizer.go b/internal/arduino/builder/sizer.go
index 84cf8012a32..ae188b48f2b 100644
--- a/internal/arduino/builder/sizer.go
+++ b/internal/arduino/builder/sizer.go
@@ -24,6 +24,7 @@ import (
 	"strconv"
 
 	"github.com/arduino/arduino-cli/internal/arduino/builder/internal/utils"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-properties-orderedmap"
@@ -78,7 +79,7 @@ func (b *Builder) checkSizeAdvanced() (ExecutablesFileSections, error) {
 	if err != nil {
 		return nil, errors.New(i18n.Tr("Error while determining sketch size: %s", err))
 	}
-	if b.logger.Verbose() {
+	if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 		b.logger.Info(utils.PrintableCommand(command.GetArgs()))
 	}
 	out := &bytes.Buffer{}
@@ -155,19 +156,21 @@ func (b *Builder) checkSize() (ExecutablesFileSections, error) {
 		return nil, nil
 	}
 
-	b.logger.Info(i18n.Tr("Sketch uses %[1]s bytes (%[3]s%%) of program storage space. Maximum is %[2]s bytes.",
-		strconv.Itoa(textSize),
-		strconv.Itoa(maxTextSize),
-		strconv.Itoa(textSize*100/maxTextSize)))
-	if dataSize >= 0 {
-		if maxDataSize > 0 {
-			b.logger.Info(i18n.Tr("Global variables use %[1]s bytes (%[3]s%%) of dynamic memory, leaving %[4]s bytes for local variables. Maximum is %[2]s bytes.",
-				strconv.Itoa(dataSize),
-				strconv.Itoa(maxDataSize),
-				strconv.Itoa(dataSize*100/maxDataSize),
-				strconv.Itoa(maxDataSize-dataSize)))
-		} else {
-			b.logger.Info(i18n.Tr("Global variables use %[1]s bytes of dynamic memory.", strconv.Itoa(dataSize)))
+	if b.logger.VerbosityLevel() > logger.VerbosityQuiet {
+		b.logger.Info(i18n.Tr("Sketch uses %[1]s bytes (%[3]s%%) of program storage space. Maximum is %[2]s bytes.",
+			strconv.Itoa(textSize),
+			strconv.Itoa(maxTextSize),
+			strconv.Itoa(textSize*100/maxTextSize)))
+		if dataSize >= 0 {
+			if maxDataSize > 0 {
+				b.logger.Info(i18n.Tr("Global variables use %[1]s bytes (%[3]s%%) of dynamic memory, leaving %[4]s bytes for local variables. Maximum is %[2]s bytes.",
+					strconv.Itoa(dataSize),
+					strconv.Itoa(maxDataSize),
+					strconv.Itoa(dataSize*100/maxDataSize),
+					strconv.Itoa(maxDataSize-dataSize)))
+			} else {
+				b.logger.Info(i18n.Tr("Global variables use %[1]s bytes of dynamic memory.", strconv.Itoa(dataSize)))
+			}
 		}
 	}
 
@@ -215,7 +218,7 @@ func (b *Builder) execSizeRecipe(properties *properties.Map) (textSize int, data
 		resErr = errors.New(i18n.Tr("Error while determining sketch size: %s", err))
 		return
 	}
-	if b.logger.Verbose() {
+	if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 		b.logger.Info(utils.PrintableCommand(command.GetArgs()))
 	}
 	commandStdout := &bytes.Buffer{}
diff --git a/internal/arduino/builder/sketch.go b/internal/arduino/builder/sketch.go
index 1f64d207c81..76e77f4c8e8 100644
--- a/internal/arduino/builder/sketch.go
+++ b/internal/arduino/builder/sketch.go
@@ -26,6 +26,7 @@ import (
 
 	"fortio.org/safecast"
 	"github.com/arduino/arduino-cli/internal/arduino/builder/cpp"
+	"github.com/arduino/arduino-cli/internal/arduino/builder/logger"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	"github.com/arduino/go-paths-helper"
 	"github.com/marcinbor85/gohex"
@@ -240,7 +241,7 @@ func (b *Builder) mergeSketchWithBootloader() error {
 
 	bootloaderPath := b.buildProperties.GetPath("runtime.platform.path").Join("bootloaders", bootloader)
 	if bootloaderPath.NotExist() {
-		if b.logger.Verbose() {
+		if b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 			b.logger.Warn(i18n.Tr("Bootloader file specified but missing: %[1]s", bootloaderPath))
 		}
 		return nil
@@ -255,7 +256,7 @@ func (b *Builder) mergeSketchWithBootloader() error {
 		maximumBinSize *= 2
 	}
 	err := merge(builtSketchPath, bootloaderPath, mergedSketchPath, maximumBinSize)
-	if err != nil && b.logger.Verbose() {
+	if err != nil && b.logger.VerbosityLevel() == logger.VerbosityVerbose {
 		b.logger.Info(err.Error())
 	}
 
diff --git a/internal/cli/compile/compile.go b/internal/cli/compile/compile.go
index 5cccf62ad46..69ec3d52966 100644
--- a/internal/cli/compile/compile.go
+++ b/internal/cli/compile/compile.go
@@ -85,6 +85,9 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer, settings *rpc.Configuration) *
 			"  " + os.Args[0] + ` compile -b arduino:avr:uno --build-property "build.extra_flags=-DPIN=2 \"-DMY_DEFINE=\"hello world\"\"" /home/user/Arduino/MySketch` + "\n" +
 			"  " + os.Args[0] + ` compile -b arduino:avr:uno --build-property build.extra_flags=-DPIN=2 --build-property "compiler.cpp.extra_flags=\"-DSSID=\"hello world\"\"" /home/user/Arduino/MySketch` + "\n",
 		Args: cobra.MaximumNArgs(1),
+		PreRun: func(cmd *cobra.Command, args []string) {
+			arguments.CheckFlagsConflicts(cmd, "quiet", "verbose")
+		},
 		Run: func(cmd *cobra.Command, args []string) {
 			if cmd.Flag("build-cache-path").Changed {
 				feedback.Warning(i18n.Tr("The flag --build-cache-path has been deprecated. Please use just --build-path alone or configure the build cache path in the Arduino CLI settings."))
@@ -116,7 +119,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer, settings *rpc.Configuration) *
 	compileCommand.Flags().StringVar(&warnings, "warnings", "none",
 		i18n.Tr(`Optional, can be: %s. Used to tell gcc which warning level to use (-W flag).`, "none, default, more, all"))
 	compileCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, i18n.Tr("Optional, turns on verbose mode."))
-	compileCommand.Flags().BoolVar(&quiet, "quiet", false, i18n.Tr("Optional, suppresses almost every output."))
+	compileCommand.Flags().BoolVarP(&quiet, "quiet", "q", false, i18n.Tr("Optional, suppresses almost every output."))
 	compileCommand.Flags().BoolVarP(&uploadAfterCompile, "upload", "u", false, i18n.Tr("Upload the binary after the compilation."))
 	portArgs.AddToCommand(compileCommand, srv)
 	compileCommand.Flags().BoolVarP(&verify, "verify", "t", false, i18n.Tr("Verify uploaded binary after the upload."))
@@ -362,6 +365,7 @@ func runCompileCommand(cmd *cobra.Command, args []string, srv rpc.ArduinoCoreSer
 	}
 
 	stdIO := stdIORes()
+	successful := (compileError == nil)
 	res := &compileResult{
 		CompilerOut:   stdIO.Stdout,
 		CompilerErr:   stdIO.Stderr,
@@ -370,9 +374,9 @@ func runCompileCommand(cmd *cobra.Command, args []string, srv rpc.ArduinoCoreSer
 			UpdatedUploadPort: result.NewPort(uploadRes.GetUpdatedUploadPort()),
 		},
 		ProfileOut:         profileOut,
-		Success:            compileError == nil,
+		Success:            successful,
 		showPropertiesMode: showProperties,
-		hideStats:          preprocess,
+		hideStats:          preprocess || quiet || (!verbose && successful),
 	}
 
 	if compileError != nil {
diff --git a/internal/integrationtest/compile_3/compile_verbosity_test.go b/internal/integrationtest/compile_3/compile_verbosity_test.go
new file mode 100644
index 00000000000..db87afa7562
--- /dev/null
+++ b/internal/integrationtest/compile_3/compile_verbosity_test.go
@@ -0,0 +1,103 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2022 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package compile_test
+
+import (
+	"testing"
+
+	"github.com/arduino/arduino-cli/internal/integrationtest"
+	"github.com/arduino/go-paths-helper"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCompileVerbosity(t *testing.T) {
+	env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
+	defer env.CleanUp()
+
+	_, _, err := cli.Run("core", "update-index")
+	require.NoError(t, err)
+	_, _, err = cli.Run("core", "install", "arduino:avr")
+	require.NoError(t, err)
+
+	goodSketch, err := paths.New("testdata", "bare_minimum").Abs()
+	require.NoError(t, err)
+	badSketch, err := paths.New("testdata", "blink_with_error_directive").Abs()
+	require.NoError(t, err)
+
+	hasSketchSize := func(t *testing.T, out []byte) {
+		require.Contains(t, string(out), "Sketch uses")
+	}
+	noSketchSize := func(t *testing.T, out []byte) {
+		require.NotContains(t, string(out), "Sketch uses")
+	}
+	hasRecapTable := func(t *testing.T, out []byte) {
+		require.Contains(t, string(out), "Used platform")
+	}
+	noRecapTable := func(t *testing.T, out []byte) {
+		require.NotContains(t, string(out), "Used platform")
+	}
+
+	t.Run("DefaultVerbosity/SuccessfulBuild", func(t *testing.T) {
+		stdout, stderr, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", goodSketch.String())
+		require.NoError(t, err)
+		hasSketchSize(t, stdout)
+		noRecapTable(t, stdout)
+		require.Empty(t, stderr)
+	})
+
+	t.Run("DefaultVerbosity/BuildWithErrors", func(t *testing.T) {
+		stdout, stderr, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", badSketch.String())
+		require.Error(t, err)
+		hasRecapTable(t, stdout)
+		require.NotEmpty(t, stderr)
+	})
+
+	t.Run("VerboseVerbosity/SuccessfulBuild", func(t *testing.T) {
+		stdout, stderr, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", goodSketch.String())
+		require.NoError(t, err)
+		hasSketchSize(t, stdout)
+		hasRecapTable(t, stdout)
+		require.Empty(t, stderr)
+	})
+
+	t.Run("VerboseVerbosity/BuildWithErrors", func(t *testing.T) {
+		stdout, stderr, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", badSketch.String())
+		require.Error(t, err)
+		hasRecapTable(t, stdout)
+		require.NotEmpty(t, stderr)
+	})
+
+	t.Run("QuietVerbosity/SuccessfulBuild", func(t *testing.T) {
+		stdout, stderr, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", "-q", goodSketch.String())
+		require.NoError(t, err)
+		noSketchSize(t, stdout)
+		noRecapTable(t, stdout)
+		require.Empty(t, stdout) // Empty output
+		require.Empty(t, stderr)
+	})
+
+	t.Run("QuietVerbosity/BuildWithErrors", func(t *testing.T) {
+		stdout, stderr, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", "-q", badSketch.String())
+		require.Error(t, err)
+		noRecapTable(t, stdout)
+		require.NotEmpty(t, stderr)
+	})
+
+	t.Run("ConflictingVerbosityOptions", func(t *testing.T) {
+		_, _, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", "-q", goodSketch.String())
+		require.Error(t, err)
+	})
+}