]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Write all logging (INFO, WARN, ERROR) to stderr
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 13 Dec 2024 08:23:09 +0000 (09:23 +0100)
committerGitHub <noreply@github.com>
Fri, 13 Dec 2024 08:23:09 +0000 (09:23 +0100)
The old setup tried to log >= warning to stderr, the rest to stdout.

However, that logic was flawed, so warnings ended up in stdout, which makes `hugo list all` etc. hard to reason about from scripts.

This commit fixes this by making all logging (info, warn, error) log to stderr and let stdout be reserved for program output.

Fixes #13074

15 files changed:
commands/commandeer.go
commands/list.go
common/loggers/handlerterminal.go
common/loggers/logger.go
common/loggers/logger_test.go
deps/deps.go
hugolib/integrationtest_builder.go
hugolib/site.go
modules/client.go
testscripts/commands/deprecate.txt
testscripts/commands/hugo__configdir.txt
testscripts/commands/hugo__path-warnings.txt
testscripts/commands/hugo_printpathwarnings.txt
testscripts/commands/hugo_printunusedtemplates.txt
testscripts/commands/warnf_stderr.txt [new file with mode: 0644]

index bb82ec654323a95db5fdf18065083f6403c3dfda..3f9f02d2319cd63c7065083bf05cd324a5148504 100644 (file)
@@ -103,7 +103,8 @@ type configKey struct {
 type rootCommand struct {
        Printf  func(format string, v ...interface{})
        Println func(a ...interface{})
-       Out     io.Writer
+       StdOut  io.Writer
+       StdErr  io.Writer
 
        logger loggers.Logger
 
@@ -356,7 +357,7 @@ func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotEx
 }
 
 func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
-       return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
+       return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, StdOut: r.logger.StdOut(), StdErr: r.logger.StdErr(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
 }
 
 func (r *rootCommand) Name() string {
@@ -421,21 +422,23 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
 }
 
 func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
-       r.Out = os.Stdout
+       r.StdOut = os.Stdout
+       r.StdErr = os.Stderr
        if r.quiet {
-               r.Out = io.Discard
+               r.StdOut = io.Discard
+               r.StdErr = io.Discard
        }
        // Used by mkcert (server).
-       log.SetOutput(r.Out)
+       log.SetOutput(r.StdOut)
 
        r.Printf = func(format string, v ...interface{}) {
                if !r.quiet {
-                       fmt.Fprintf(r.Out, format, v...)
+                       fmt.Fprintf(r.StdOut, format, v...)
                }
        }
        r.Println = func(a ...interface{}) {
                if !r.quiet {
-                       fmt.Fprintln(r.Out, a...)
+                       fmt.Fprintln(r.StdOut, a...)
                }
        }
        _, running := runner.Command.(*serverCommand)
@@ -485,8 +488,8 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
        optsLogger := loggers.Options{
                DistinctLevel: logg.LevelWarn,
                Level:         level,
-               Stdout:        r.Out,
-               Stderr:        r.Out,
+               StdOut:        r.StdOut,
+               StdErr:        r.StdErr,
                StoreErrors:   running,
        }
 
index f362e22f1afcb05c27b7c25740d5fc83b1473383..42f3408ba092bca29eaf70633c748b46fbea8aa3 100644 (file)
@@ -57,7 +57,7 @@ func newListCommand() *listCommand {
                        return err
                }
 
-               writer := csv.NewWriter(r.Out)
+               writer := csv.NewWriter(r.StdOut)
                defer writer.Flush()
 
                writer.Write([]string{
index c5f8fcce8d49cffb276365b082e0ae52db397801..c6a86d3a2f14c49aa050700c05525a2d8e83aa1b 100644 (file)
@@ -40,8 +40,8 @@ func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool,
 
 type noAnsiEscapeHandler struct {
        mu            sync.Mutex
-       outWriter     io.Writer // Defaults to os.Stdout.
-       errWriter     io.Writer // Defaults to os.Stderr.
+       outWriter     io.Writer
+       errWriter     io.Writer
        predicate     func(*logg.Entry) bool
        noLevelPrefix bool
 }
index 75c8102c714e85024255e53882cf4a5063cc107d..77a21458a4a01e44f2ee3e42709a6c0197edb496 100644 (file)
@@ -38,8 +38,8 @@ var (
 // Options defines options for the logger.
 type Options struct {
        Level              logg.Level
-       Stdout             io.Writer
-       Stderr             io.Writer
+       StdOut             io.Writer
+       StdErr             io.Writer
        DistinctLevel      logg.Level
        StoreErrors        bool
        HandlerPost        func(e *logg.Entry) error
@@ -48,21 +48,22 @@ type Options struct {
 
 // New creates a new logger with the given options.
 func New(opts Options) Logger {
-       if opts.Stdout == nil {
-               opts.Stdout = os.Stdout
+       if opts.StdOut == nil {
+               opts.StdOut = os.Stdout
        }
-       if opts.Stderr == nil {
-               opts.Stderr = os.Stdout
+       if opts.StdErr == nil {
+               opts.StdErr = os.Stderr
        }
+
        if opts.Level == 0 {
                opts.Level = logg.LevelWarn
        }
 
        var logHandler logg.Handler
-       if terminal.PrintANSIColors(os.Stdout) {
-               logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
+       if terminal.PrintANSIColors(os.Stderr) {
+               logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
        } else {
-               logHandler = newNoAnsiEscapeHandler(opts.Stdout, opts.Stderr, false, nil)
+               logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
        }
 
        errorsw := &strings.Builder{}
@@ -137,7 +138,8 @@ func New(opts Options) Logger {
                logCounters: logCounters,
                errors:      errorsw,
                reset:       reset,
-               out:         opts.Stdout,
+               stdOut:      opts.StdOut,
+               stdErr:      opts.StdErr,
                level:       opts.Level,
                logger:      logger,
                tracel:      l.WithLevel(logg.LevelTrace),
@@ -153,8 +155,6 @@ func NewDefault() Logger {
        opts := Options{
                DistinctLevel: logg.LevelWarn,
                Level:         logg.LevelWarn,
-               Stdout:        os.Stdout,
-               Stderr:        os.Stdout,
        }
        return New(opts)
 }
@@ -163,8 +163,6 @@ func NewTrace() Logger {
        opts := Options{
                DistinctLevel: logg.LevelWarn,
                Level:         logg.LevelTrace,
-               Stdout:        os.Stdout,
-               Stderr:        os.Stdout,
        }
        return New(opts)
 }
@@ -189,7 +187,8 @@ type Logger interface {
        Level() logg.Level
        LoggCount(logg.Level) int
        Logger() logg.Logger
-       Out() io.Writer
+       StdOut() io.Writer
+       StdErr() io.Writer
        Printf(format string, v ...any)
        Println(v ...any)
        PrintTimerIfDelayed(start time.Time, name string)
@@ -207,7 +206,8 @@ type logAdapter struct {
        logCounters *logLevelCounter
        errors      *strings.Builder
        reset       func()
-       out         io.Writer
+       stdOut      io.Writer
+       stdErr      io.Writer
        level       logg.Level
        logger      logg.Logger
        tracel      logg.LevelLogger
@@ -259,8 +259,12 @@ func (l *logAdapter) Logger() logg.Logger {
        return l.logger
 }
 
-func (l *logAdapter) Out() io.Writer {
-       return l.out
+func (l *logAdapter) StdOut() io.Writer {
+       return l.stdOut
+}
+
+func (l *logAdapter) StdErr() io.Writer {
+       return l.stdErr
 }
 
 // PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
@@ -279,11 +283,11 @@ func (l *logAdapter) Printf(format string, v ...any) {
        if !strings.HasSuffix(format, "\n") {
                format += "\n"
        }
-       fmt.Fprintf(l.out, format, v...)
+       fmt.Fprintf(l.stdOut, format, v...)
 }
 
 func (l *logAdapter) Println(v ...any) {
-       fmt.Fprintln(l.out, v...)
+       fmt.Fprintln(l.stdOut, v...)
 }
 
 func (l *logAdapter) Reset() {
index dcf94b12313c460e41c352050204e3c17f44f658..b03e6d903cdea324a3566201c7157191a6356e5a 100644 (file)
@@ -31,8 +31,8 @@ func TestLogDistinct(t *testing.T) {
        opts := loggers.Options{
                DistinctLevel: logg.LevelWarn,
                StoreErrors:   true,
-               Stdout:        io.Discard,
-               Stderr:        io.Discard,
+               StdOut:        io.Discard,
+               StdErr:        io.Discard,
        }
 
        l := loggers.New(opts)
@@ -54,8 +54,8 @@ func TestHookLast(t *testing.T) {
                HandlerPost: func(e *logg.Entry) error {
                        panic(e.Message)
                },
-               Stdout: io.Discard,
-               Stderr: io.Discard,
+               StdOut: io.Discard,
+               StdErr: io.Discard,
        }
 
        l := loggers.New(opts)
@@ -70,8 +70,8 @@ func TestOptionStoreErrors(t *testing.T) {
 
        opts := loggers.Options{
                StoreErrors: true,
-               Stderr:      &sb,
-               Stdout:      &sb,
+               StdErr:      &sb,
+               StdOut:      &sb,
        }
 
        l := loggers.New(opts)
@@ -131,8 +131,8 @@ func TestReset(t *testing.T) {
        opts := loggers.Options{
                StoreErrors:   true,
                DistinctLevel: logg.LevelWarn,
-               Stdout:        io.Discard,
-               Stderr:        io.Discard,
+               StdOut:        io.Discard,
+               StdErr:        io.Discard,
        }
 
        l := loggers.New(opts)
index ca0f5ee3b47c47b5fb49133e0e6cc07833540b99..8e9ec42d8b60e949d6e743deb415731ab045d97c 100644 (file)
@@ -405,9 +405,11 @@ type DepsCfg struct {
        // The logging level to use.
        LogLevel logg.Level
 
-       // Where to write the logs.
-       // Currently we typically write everything to stdout.
-       LogOut io.Writer
+       // Logging output.
+       StdErr io.Writer
+
+       // The console output.
+       StdOut io.Writer
 
        // The file systems to use
        Fs *hugofs.Fs
index bc83d65b37c1af4de8fd939ab26b038ec9d11964..b593cd965778376c83c644fd2b17bfe42c82214a 100644 (file)
@@ -660,8 +660,8 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 
                logger := loggers.New(
                        loggers.Options{
-                               Stdout:        w,
-                               Stderr:        w,
+                               StdOut:        w,
+                               StdErr:        w,
                                Level:         s.Cfg.LogLevel,
                                DistinctLevel: logg.LevelWarn,
                        },
@@ -685,7 +685,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 
                s.Assert(err, qt.IsNil)
 
-               depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), LogOut: logger.Out()}
+               depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), StdErr: logger.StdErr()}
                sites, err := NewHugoSites(depsCfg)
                if err != nil {
                        initErr = err
index 7ec70eb6c82abf087bb2e762a6ee69a0a25e52d7..f73bd2517e17c2b758f330afc0d2f5fa43dbc492 100644 (file)
@@ -145,8 +145,11 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
                if cfg.Configs.Base.PanicOnWarning {
                        logHookLast = loggers.PanicOnWarningHook
                }
-               if cfg.LogOut == nil {
-                       cfg.LogOut = os.Stdout
+               if cfg.StdOut == nil {
+                       cfg.StdOut = os.Stdout
+               }
+               if cfg.StdErr == nil {
+                       cfg.StdErr = os.Stderr
                }
                if cfg.LogLevel == 0 {
                        cfg.LogLevel = logg.LevelWarn
@@ -156,8 +159,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
                        Level:              cfg.LogLevel,
                        DistinctLevel:      logg.LevelWarn, // This will drop duplicate log warning and errors.
                        HandlerPost:        logHookLast,
-                       Stdout:             cfg.LogOut,
-                       Stderr:             cfg.LogOut,
+                       StdOut:             cfg.StdOut,
+                       StdErr:             cfg.StdErr,
                        StoreErrors:        conf.Watching(),
                        SuppressStatements: conf.IgnoredLogs(),
                }
index 404605c8c1665a1ab3638112a194f8c5a499b48c..d16af2d351bffc424df627a2546498b33004baab 100644 (file)
@@ -365,7 +365,7 @@ func (c *Client) Get(args ...string) error {
 }
 
 func (c *Client) get(args ...string) error {
-       if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil {
+       if err := c.runGo(context.Background(), c.logger.StdOut(), append([]string{"get"}, args...)...); err != nil {
                return fmt.Errorf("failed to get %q: %w", args, err)
        }
        return nil
@@ -375,7 +375,7 @@ func (c *Client) get(args ...string) error {
 // If path is empty, Go will try to guess.
 // If this succeeds, this project will be marked as Go Module.
 func (c *Client) Init(path string) error {
-       err := c.runGo(context.Background(), c.logger.Out(), "mod", "init", path)
+       err := c.runGo(context.Background(), c.logger.StdOut(), "mod", "init", path)
        if err != nil {
                return fmt.Errorf("failed to init modules: %w", err)
        }
index 3be4976d56921a8d773fb032c8f6771e71a4c691..8791c3a78748a2af7666de721ed082ab75cb8442 100644 (file)
@@ -1,13 +1,13 @@
 
 # Test deprecation logging.
 hugo -e info --logLevel info
-stdout 'INFO  deprecated: item was deprecated in Hugo'
+stderr 'INFO  deprecated: item was deprecated in Hugo'
 
 hugo -e warn --logLevel warn
-stdout 'WARN  deprecated: item was deprecated in Hugo'
+stderr 'WARN  deprecated: item was deprecated in Hugo'
 
 ! hugo -e error --logLevel warn
-stdout 'ERROR deprecated: item was deprecated in Hugo'
+stderr 'ERROR deprecated: item was deprecated in Hugo'
 
 -- hugo.toml --
 baseURL = "https://example.com/"
index 4da62ade5955101da0745d9c43af978ae8ff1784..148523e9fd0dc4feab3269df40dcd394083ac3fe 100644 (file)
@@ -3,4 +3,5 @@ hugo
 ! stderr .
 
 -- config/_default/hugo.toml --
-baseURL = "https://example.com/"
\ No newline at end of file
+baseURL = "https://example.com/"
+disableKinds = ["RSS", "page", "sitemap", "robotsTXT", "404", "taxonomy", "term", "home"]
\ No newline at end of file
index f7e3acd952c9ee0a43332d71e591cb35fc0d4186..8eccb6567c70fc370569782094db1701eb39e4fc 100644 (file)
@@ -1,6 +1,6 @@
 hugo --printPathWarnings
 
-stdout 'Duplicate'
+stderr 'Duplicate'
 
 -- hugo.toml --
 -- assets/css/styles.css --
index f4c76ebab94fb6c5663652b37be92c8fe2eae1fe..51eb46d910c6b1f496e01c9f690ec26162708b50 100644 (file)
@@ -1,6 +1,6 @@
 hugo  --printPathWarnings
 
-stdout 'Duplicate target paths: .index.html \(2\)'
+stderr 'Duplicate target paths: .index.html \(2\)'
 
 -- hugo.toml --
 disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section"]
index 312e4920d66321573346fb4453482afefbe90940..eb9e068d72d08b0f91d018086438eef5e0564a90 100644 (file)
@@ -1,6 +1,6 @@
 hugo  --printUnusedTemplates
 
-stdout 'Template _default/list.html is unused'
+stderr 'Template _default/list.html is unused'
 
 -- hugo.toml --
 disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section", "page"]
diff --git a/testscripts/commands/warnf_stderr.txt b/testscripts/commands/warnf_stderr.txt
new file mode 100644 (file)
index 0000000..f899253
--- /dev/null
@@ -0,0 +1,13 @@
+# Issue #13074
+
+hugo
+stderr 'warning'
+! stdout 'warning'
+
+-- hugo.toml --
+baseURL = "http://example.org/"
+disableKinds = ["RSS", "page", "sitemap", "robotsTXT", "404", "taxonomy", "term"]
+-- layouts/index.html --
+Home
+{{ warnf "This is a warning" }}
+